@topolo/cli 0.1.6 → 0.1.8
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.
- package/README.md +57 -5
- package/dist/index.js +2216 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -55,13 +55,13 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
55
55
|
body;
|
|
56
56
|
service;
|
|
57
57
|
path;
|
|
58
|
-
constructor(service,
|
|
59
|
-
super("http_error", message ?? `HTTP ${status} from ${service}${
|
|
58
|
+
constructor(service, path2, status, body, message) {
|
|
59
|
+
super("http_error", message ?? `HTTP ${status} from ${service}${path2}`);
|
|
60
60
|
this.name = "TopoloHttpError";
|
|
61
61
|
this.status = status;
|
|
62
62
|
this.body = body;
|
|
63
63
|
this.service = service;
|
|
64
|
-
this.path =
|
|
64
|
+
this.path = path2;
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
var SERVICES = {
|
|
@@ -69,12 +69,14 @@ var SERVICES = {
|
|
|
69
69
|
auth: "https://auth.topolo.app",
|
|
70
70
|
bugfix: "https://bugfix.topolo.app",
|
|
71
71
|
bytes: "https://r2-media-manager-worker.topolo.workers.dev",
|
|
72
|
+
calendar: "https://calendar.topolo.app",
|
|
72
73
|
chat: "https://chat.topolo.app",
|
|
73
74
|
commerce: "https://topolo-commerce-api.topolo.workers.dev",
|
|
74
75
|
crm: "https://crm-api.topolo.app",
|
|
75
76
|
forecast: "https://forecast-worker-production.topolo.workers.dev",
|
|
76
77
|
learn: "https://topololearn-api.topolo.workers.dev",
|
|
77
|
-
|
|
78
|
+
mail: "https://mail.topolo.app",
|
|
79
|
+
mdm: "https://topolo-mdm-api.topolo.app",
|
|
78
80
|
messaging: "https://messages.topolo.app",
|
|
79
81
|
nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
80
82
|
one: "https://topoloone-worker.topolo.workers.dev",
|
|
@@ -85,12 +87,1174 @@ var SERVICES = {
|
|
|
85
87
|
social_studio: "https://topolo-social-studio-api.topolo.workers.dev",
|
|
86
88
|
socialize: "https://socialize-prod-api.topolo.workers.dev",
|
|
87
89
|
support: "https://support.topolo.app",
|
|
88
|
-
|
|
89
|
-
"web.runtime": "https://sites.topolo.app"
|
|
90
|
+
web: "https://web-api.topolo.app"
|
|
90
91
|
};
|
|
91
92
|
var PLATFORM_SERVICE_IDS = {
|
|
92
93
|
crm: "srv_iCwM4jGXcwlj"
|
|
93
94
|
};
|
|
95
|
+
var APPLICATIONS = {
|
|
96
|
+
"admin": {
|
|
97
|
+
"id": "admin",
|
|
98
|
+
"appDir": "TopoloAdmin",
|
|
99
|
+
"name": "topoloadmin",
|
|
100
|
+
"packageName": "topolo-admin",
|
|
101
|
+
"productionUrl": "https://admin.topolo.app",
|
|
102
|
+
"services": [],
|
|
103
|
+
"deployTargets": [
|
|
104
|
+
{
|
|
105
|
+
"name": "topoloadmin",
|
|
106
|
+
"kind": "pages",
|
|
107
|
+
"apiUrl": null,
|
|
108
|
+
"domains": [],
|
|
109
|
+
"routes": [],
|
|
110
|
+
"serviceId": null
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"agent": {
|
|
115
|
+
"id": "agent",
|
|
116
|
+
"appDir": "TopoloAgent",
|
|
117
|
+
"name": "topolo-agent",
|
|
118
|
+
"packageName": "topolo-agent",
|
|
119
|
+
"productionUrl": "https://agent.topolo.app",
|
|
120
|
+
"services": [
|
|
121
|
+
"agent"
|
|
122
|
+
],
|
|
123
|
+
"deployTargets": [
|
|
124
|
+
{
|
|
125
|
+
"name": "topolo-agent",
|
|
126
|
+
"kind": "pages",
|
|
127
|
+
"apiUrl": null,
|
|
128
|
+
"domains": [],
|
|
129
|
+
"routes": [],
|
|
130
|
+
"serviceId": null
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "topolo-agent-worker",
|
|
134
|
+
"kind": "worker",
|
|
135
|
+
"apiUrl": "https://topolo-agent-worker.topolo.workers.dev",
|
|
136
|
+
"domains": [],
|
|
137
|
+
"routes": [],
|
|
138
|
+
"serviceId": "agent"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
"auth": {
|
|
143
|
+
"id": "auth",
|
|
144
|
+
"appDir": "TopoloAuth",
|
|
145
|
+
"name": "topolo-auth",
|
|
146
|
+
"packageName": "topolo-auth",
|
|
147
|
+
"productionUrl": "https://auth.topolo.app/login",
|
|
148
|
+
"services": [
|
|
149
|
+
"auth"
|
|
150
|
+
],
|
|
151
|
+
"deployTargets": [
|
|
152
|
+
{
|
|
153
|
+
"name": "topolo-auth",
|
|
154
|
+
"kind": "worker",
|
|
155
|
+
"apiUrl": "https://auth.topolo.app",
|
|
156
|
+
"domains": [
|
|
157
|
+
"auth.topolo.app"
|
|
158
|
+
],
|
|
159
|
+
"routes": [
|
|
160
|
+
"auth.topolo.app"
|
|
161
|
+
],
|
|
162
|
+
"serviceId": "auth"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"bugfix": {
|
|
167
|
+
"id": "bugfix",
|
|
168
|
+
"appDir": "TopoloBugFix",
|
|
169
|
+
"name": "topolo-bugfix",
|
|
170
|
+
"packageName": "topolo-bugfix",
|
|
171
|
+
"productionUrl": "https://bugfix.topolo.app",
|
|
172
|
+
"services": [
|
|
173
|
+
"bugfix"
|
|
174
|
+
],
|
|
175
|
+
"deployTargets": [
|
|
176
|
+
{
|
|
177
|
+
"name": "topolo-bugfix",
|
|
178
|
+
"kind": "worker",
|
|
179
|
+
"apiUrl": "https://bugfix.topolo.app",
|
|
180
|
+
"domains": [
|
|
181
|
+
"bugfix.topolo.app"
|
|
182
|
+
],
|
|
183
|
+
"routes": [
|
|
184
|
+
"bugfix.topolo.app"
|
|
185
|
+
],
|
|
186
|
+
"serviceId": "bugfix"
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"bytes": {
|
|
191
|
+
"id": "bytes",
|
|
192
|
+
"appDir": "TopoloBytes",
|
|
193
|
+
"name": "topolo-bytes",
|
|
194
|
+
"packageName": "topolo-bytes",
|
|
195
|
+
"productionUrl": "https://bytes.topolo.app",
|
|
196
|
+
"services": [
|
|
197
|
+
"bytes"
|
|
198
|
+
],
|
|
199
|
+
"deployTargets": [
|
|
200
|
+
{
|
|
201
|
+
"name": "bytes",
|
|
202
|
+
"kind": "pages",
|
|
203
|
+
"apiUrl": null,
|
|
204
|
+
"domains": [],
|
|
205
|
+
"routes": [],
|
|
206
|
+
"serviceId": null
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "r2-media-manager-worker",
|
|
210
|
+
"kind": "worker",
|
|
211
|
+
"apiUrl": "https://r2-media-manager-worker.topolo.workers.dev",
|
|
212
|
+
"domains": [],
|
|
213
|
+
"routes": [],
|
|
214
|
+
"serviceId": "bytes"
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
"calendar": {
|
|
219
|
+
"id": "calendar",
|
|
220
|
+
"appDir": "TopoloCalendar",
|
|
221
|
+
"name": "topolo-calendar",
|
|
222
|
+
"packageName": "topolo-calendar",
|
|
223
|
+
"productionUrl": "https://calendar.topolo.app",
|
|
224
|
+
"services": [
|
|
225
|
+
"calendar"
|
|
226
|
+
],
|
|
227
|
+
"deployTargets": [
|
|
228
|
+
{
|
|
229
|
+
"name": "topolo-calendar",
|
|
230
|
+
"kind": "worker",
|
|
231
|
+
"apiUrl": "https://calendar.topolo.app",
|
|
232
|
+
"domains": [
|
|
233
|
+
"calendar.topolo.app"
|
|
234
|
+
],
|
|
235
|
+
"routes": [
|
|
236
|
+
"calendar.topolo.app"
|
|
237
|
+
],
|
|
238
|
+
"serviceId": "calendar"
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
"chat": {
|
|
243
|
+
"id": "chat",
|
|
244
|
+
"appDir": "TopoloChat",
|
|
245
|
+
"name": "topolo-chat",
|
|
246
|
+
"packageName": "topolo-chat",
|
|
247
|
+
"productionUrl": "https://chat.topolo.app",
|
|
248
|
+
"services": [
|
|
249
|
+
"chat"
|
|
250
|
+
],
|
|
251
|
+
"deployTargets": [
|
|
252
|
+
{
|
|
253
|
+
"name": "topolo-chat",
|
|
254
|
+
"kind": "worker",
|
|
255
|
+
"apiUrl": "https://chat.topolo.app",
|
|
256
|
+
"domains": [
|
|
257
|
+
"chat.topolo.app"
|
|
258
|
+
],
|
|
259
|
+
"routes": [
|
|
260
|
+
"chat.topolo.app"
|
|
261
|
+
],
|
|
262
|
+
"serviceId": "chat"
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
},
|
|
266
|
+
"cli": {
|
|
267
|
+
"id": "cli",
|
|
268
|
+
"appDir": "TopoloCli",
|
|
269
|
+
"name": "@topolo/cli",
|
|
270
|
+
"packageName": "@topolo/cli",
|
|
271
|
+
"productionUrl": null,
|
|
272
|
+
"services": [],
|
|
273
|
+
"deployTargets": []
|
|
274
|
+
},
|
|
275
|
+
"cloudcontrol": {
|
|
276
|
+
"id": "cloudcontrol",
|
|
277
|
+
"appDir": "TopoloCloudControl",
|
|
278
|
+
"name": "topolo-cloudcontrol",
|
|
279
|
+
"packageName": "topolo-cloudcontrol",
|
|
280
|
+
"productionUrl": null,
|
|
281
|
+
"services": [],
|
|
282
|
+
"deployTargets": []
|
|
283
|
+
},
|
|
284
|
+
"commerce": {
|
|
285
|
+
"id": "commerce",
|
|
286
|
+
"appDir": "TopoloCommerce",
|
|
287
|
+
"name": "topolo-commerce",
|
|
288
|
+
"packageName": "topolo-commerce",
|
|
289
|
+
"productionUrl": "https://commerce.topolo.app",
|
|
290
|
+
"services": [
|
|
291
|
+
"commerce"
|
|
292
|
+
],
|
|
293
|
+
"deployTargets": [
|
|
294
|
+
{
|
|
295
|
+
"name": "topolo-commerce-api",
|
|
296
|
+
"kind": "worker",
|
|
297
|
+
"apiUrl": "https://topolo-commerce-api.topolo.workers.dev",
|
|
298
|
+
"domains": [
|
|
299
|
+
"topolo-commerce-api.topolo.workers.dev"
|
|
300
|
+
],
|
|
301
|
+
"routes": [],
|
|
302
|
+
"serviceId": "commerce"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"name": "topolo-commerce-guest-web",
|
|
306
|
+
"kind": "pages",
|
|
307
|
+
"apiUrl": null,
|
|
308
|
+
"domains": [
|
|
309
|
+
"guest.topolo.app",
|
|
310
|
+
"topolo-commerce-guest-web.pages.dev"
|
|
311
|
+
],
|
|
312
|
+
"routes": [],
|
|
313
|
+
"serviceId": null
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"name": "topolo-commerce-ops-web",
|
|
317
|
+
"kind": "pages",
|
|
318
|
+
"apiUrl": null,
|
|
319
|
+
"domains": [
|
|
320
|
+
"commerce.topolo.app",
|
|
321
|
+
"topolo-commerce-ops-web.pages.dev"
|
|
322
|
+
],
|
|
323
|
+
"routes": [],
|
|
324
|
+
"serviceId": null
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"name": "topolo-commerce-station-web",
|
|
328
|
+
"kind": "pages",
|
|
329
|
+
"apiUrl": null,
|
|
330
|
+
"domains": [
|
|
331
|
+
"station-commerce.topolo.app",
|
|
332
|
+
"topolo-commerce-station-web.pages.dev"
|
|
333
|
+
],
|
|
334
|
+
"routes": [],
|
|
335
|
+
"serviceId": null
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
"crm": {
|
|
340
|
+
"id": "crm",
|
|
341
|
+
"appDir": "TopoloCRM",
|
|
342
|
+
"name": "topolo-crm",
|
|
343
|
+
"packageName": "topolo-crm",
|
|
344
|
+
"productionUrl": "https://crm.topolo.app",
|
|
345
|
+
"services": [
|
|
346
|
+
"crm"
|
|
347
|
+
],
|
|
348
|
+
"deployTargets": [
|
|
349
|
+
{
|
|
350
|
+
"name": "topolo-crm-frontend",
|
|
351
|
+
"kind": "pages",
|
|
352
|
+
"apiUrl": null,
|
|
353
|
+
"domains": [],
|
|
354
|
+
"routes": [],
|
|
355
|
+
"serviceId": null
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"name": "topolo-crm-worker",
|
|
359
|
+
"kind": "worker",
|
|
360
|
+
"apiUrl": "https://crm-api.topolo.app",
|
|
361
|
+
"domains": [],
|
|
362
|
+
"routes": [],
|
|
363
|
+
"serviceId": "crm"
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
"developers": {
|
|
368
|
+
"id": "developers",
|
|
369
|
+
"appDir": "TopoloDevelopers",
|
|
370
|
+
"name": "topolo-developers",
|
|
371
|
+
"packageName": "topolo-developers",
|
|
372
|
+
"productionUrl": "https://developers.topolo.app",
|
|
373
|
+
"services": [],
|
|
374
|
+
"deployTargets": [
|
|
375
|
+
{
|
|
376
|
+
"name": "topolo-developers",
|
|
377
|
+
"kind": "pages",
|
|
378
|
+
"apiUrl": null,
|
|
379
|
+
"domains": [
|
|
380
|
+
"developers.topolo.app"
|
|
381
|
+
],
|
|
382
|
+
"routes": [],
|
|
383
|
+
"serviceId": null
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
"docs": {
|
|
388
|
+
"id": "docs",
|
|
389
|
+
"appDir": "TopoloDocs",
|
|
390
|
+
"name": "topolo-docs",
|
|
391
|
+
"packageName": "topolo-docs",
|
|
392
|
+
"productionUrl": "https://docs.topolo.app",
|
|
393
|
+
"services": [],
|
|
394
|
+
"deployTargets": [
|
|
395
|
+
{
|
|
396
|
+
"name": "topolo-docs",
|
|
397
|
+
"kind": "pages",
|
|
398
|
+
"apiUrl": null,
|
|
399
|
+
"domains": [
|
|
400
|
+
"docs.topolo.app"
|
|
401
|
+
],
|
|
402
|
+
"routes": [],
|
|
403
|
+
"serviceId": null
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
},
|
|
407
|
+
"feed": {
|
|
408
|
+
"id": "feed",
|
|
409
|
+
"appDir": "TopoloFeed",
|
|
410
|
+
"name": "TopoloFeed",
|
|
411
|
+
"packageName": null,
|
|
412
|
+
"productionUrl": null,
|
|
413
|
+
"services": [],
|
|
414
|
+
"deployTargets": [
|
|
415
|
+
{
|
|
416
|
+
"name": "topolo-feed-analytics-api",
|
|
417
|
+
"kind": "worker",
|
|
418
|
+
"apiUrl": null,
|
|
419
|
+
"domains": [],
|
|
420
|
+
"routes": [],
|
|
421
|
+
"serviceId": null
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"name": "topolo-feed-api",
|
|
425
|
+
"kind": "worker",
|
|
426
|
+
"apiUrl": null,
|
|
427
|
+
"domains": [],
|
|
428
|
+
"routes": [],
|
|
429
|
+
"serviceId": null
|
|
430
|
+
}
|
|
431
|
+
]
|
|
432
|
+
},
|
|
433
|
+
"forecast": {
|
|
434
|
+
"id": "forecast",
|
|
435
|
+
"appDir": "TopoloForecast",
|
|
436
|
+
"name": "forecast-cloud",
|
|
437
|
+
"packageName": "topolo-forecast",
|
|
438
|
+
"productionUrl": "https://forecast.topolo.app",
|
|
439
|
+
"services": [
|
|
440
|
+
"forecast"
|
|
441
|
+
],
|
|
442
|
+
"deployTargets": [
|
|
443
|
+
{
|
|
444
|
+
"name": "forecast",
|
|
445
|
+
"kind": "pages",
|
|
446
|
+
"apiUrl": null,
|
|
447
|
+
"domains": [],
|
|
448
|
+
"routes": [],
|
|
449
|
+
"serviceId": null
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"name": "forecast-worker-production",
|
|
453
|
+
"kind": "worker",
|
|
454
|
+
"apiUrl": "https://forecast-worker-production.topolo.workers.dev",
|
|
455
|
+
"domains": [],
|
|
456
|
+
"routes": [],
|
|
457
|
+
"serviceId": "forecast"
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
"learn": {
|
|
462
|
+
"id": "learn",
|
|
463
|
+
"appDir": "TopoloLearn",
|
|
464
|
+
"name": "topololearn",
|
|
465
|
+
"packageName": "topolo-learn",
|
|
466
|
+
"productionUrl": "https://learn.topolo.app",
|
|
467
|
+
"services": [
|
|
468
|
+
"learn"
|
|
469
|
+
],
|
|
470
|
+
"deployTargets": [
|
|
471
|
+
{
|
|
472
|
+
"name": "topololearn",
|
|
473
|
+
"kind": "pages",
|
|
474
|
+
"apiUrl": null,
|
|
475
|
+
"domains": [
|
|
476
|
+
"learn.topolo.app"
|
|
477
|
+
],
|
|
478
|
+
"routes": [],
|
|
479
|
+
"serviceId": null
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"name": "topololearn-api",
|
|
483
|
+
"kind": "worker",
|
|
484
|
+
"apiUrl": "https://topololearn-api.topolo.workers.dev",
|
|
485
|
+
"domains": [
|
|
486
|
+
"topololearn-api.topolo.workers.dev"
|
|
487
|
+
],
|
|
488
|
+
"routes": [],
|
|
489
|
+
"serviceId": "learn"
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
},
|
|
493
|
+
"mail": {
|
|
494
|
+
"id": "mail",
|
|
495
|
+
"appDir": "TopoloMail",
|
|
496
|
+
"name": "topolo-mail",
|
|
497
|
+
"packageName": "topolo-mail",
|
|
498
|
+
"productionUrl": "https://mail.topolo.app",
|
|
499
|
+
"services": [
|
|
500
|
+
"mail"
|
|
501
|
+
],
|
|
502
|
+
"deployTargets": [
|
|
503
|
+
{
|
|
504
|
+
"name": "topolo-mail",
|
|
505
|
+
"kind": "workers",
|
|
506
|
+
"apiUrl": "https://mail.topolo.app",
|
|
507
|
+
"domains": [
|
|
508
|
+
"mail.topolo.app"
|
|
509
|
+
],
|
|
510
|
+
"routes": [
|
|
511
|
+
"mail.topolo.app"
|
|
512
|
+
],
|
|
513
|
+
"serviceId": "mail"
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
},
|
|
517
|
+
"mcp": {
|
|
518
|
+
"id": "mcp",
|
|
519
|
+
"appDir": "TopoloMCP",
|
|
520
|
+
"name": "@topolo/mcp",
|
|
521
|
+
"packageName": "@topolo/mcp",
|
|
522
|
+
"productionUrl": null,
|
|
523
|
+
"services": [],
|
|
524
|
+
"deployTargets": []
|
|
525
|
+
},
|
|
526
|
+
"mdm": {
|
|
527
|
+
"id": "mdm",
|
|
528
|
+
"appDir": "TopoloMDM",
|
|
529
|
+
"name": "TopoloMDM",
|
|
530
|
+
"packageName": null,
|
|
531
|
+
"productionUrl": "https://mdm.topolo.app",
|
|
532
|
+
"services": [
|
|
533
|
+
"mdm"
|
|
534
|
+
],
|
|
535
|
+
"deployTargets": [
|
|
536
|
+
{
|
|
537
|
+
"name": "topolo-mdm-api",
|
|
538
|
+
"kind": "worker",
|
|
539
|
+
"apiUrl": "https://topolo-mdm-api.topolo.app",
|
|
540
|
+
"domains": [
|
|
541
|
+
"topolo-mdm-api.topolo.app"
|
|
542
|
+
],
|
|
543
|
+
"routes": [
|
|
544
|
+
"topolo-mdm-api.topolo.app"
|
|
545
|
+
],
|
|
546
|
+
"serviceId": "mdm"
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
"name": "topolo-mdm-console",
|
|
550
|
+
"kind": "pages",
|
|
551
|
+
"apiUrl": null,
|
|
552
|
+
"domains": [
|
|
553
|
+
"mdm.topolo.app",
|
|
554
|
+
"topolo-mdm-console.pages.dev"
|
|
555
|
+
],
|
|
556
|
+
"routes": [
|
|
557
|
+
"mdm.topolo.app"
|
|
558
|
+
],
|
|
559
|
+
"serviceId": null
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
},
|
|
563
|
+
"messaging": {
|
|
564
|
+
"id": "messaging",
|
|
565
|
+
"appDir": "TopoloMessaging",
|
|
566
|
+
"name": "topolo-messaging",
|
|
567
|
+
"packageName": "topolo-messaging",
|
|
568
|
+
"productionUrl": "https://messages.topolo.app",
|
|
569
|
+
"services": [
|
|
570
|
+
"messaging"
|
|
571
|
+
],
|
|
572
|
+
"deployTargets": [
|
|
573
|
+
{
|
|
574
|
+
"name": "topolo-messaging",
|
|
575
|
+
"kind": "workers",
|
|
576
|
+
"apiUrl": "https://messages.topolo.app",
|
|
577
|
+
"domains": [
|
|
578
|
+
"messages.topolo.app"
|
|
579
|
+
],
|
|
580
|
+
"routes": [],
|
|
581
|
+
"serviceId": "messaging"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"name": "topolo-messaging-dispatch",
|
|
585
|
+
"kind": "workers",
|
|
586
|
+
"apiUrl": null,
|
|
587
|
+
"domains": [],
|
|
588
|
+
"routes": [],
|
|
589
|
+
"serviceId": null
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
},
|
|
593
|
+
"nexus": {
|
|
594
|
+
"id": "nexus",
|
|
595
|
+
"appDir": "TopoloNexus",
|
|
596
|
+
"name": "topolo-nexus",
|
|
597
|
+
"packageName": "topolo-nexus",
|
|
598
|
+
"productionUrl": "https://nexus.topolo.app",
|
|
599
|
+
"services": [
|
|
600
|
+
"nexus"
|
|
601
|
+
],
|
|
602
|
+
"deployTargets": [
|
|
603
|
+
{
|
|
604
|
+
"name": "topolo-nexus-dashboard",
|
|
605
|
+
"kind": "pages",
|
|
606
|
+
"apiUrl": null,
|
|
607
|
+
"domains": [
|
|
608
|
+
"nexus.topolo.app",
|
|
609
|
+
"topolo-nexus-dashboard.pages.dev"
|
|
610
|
+
],
|
|
611
|
+
"routes": [],
|
|
612
|
+
"serviceId": null
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
"name": "topolo-nexus-gateway",
|
|
616
|
+
"kind": "worker",
|
|
617
|
+
"apiUrl": "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
618
|
+
"domains": [
|
|
619
|
+
"topolo-nexus-gateway.topolo.workers.dev"
|
|
620
|
+
],
|
|
621
|
+
"routes": [],
|
|
622
|
+
"serviceId": "nexus"
|
|
623
|
+
}
|
|
624
|
+
]
|
|
625
|
+
},
|
|
626
|
+
"one": {
|
|
627
|
+
"id": "one",
|
|
628
|
+
"appDir": "TopoloOne",
|
|
629
|
+
"name": "topolo-oneclick-sso",
|
|
630
|
+
"packageName": "topolo-oneclick-sso",
|
|
631
|
+
"productionUrl": "https://one.topolo.app",
|
|
632
|
+
"services": [
|
|
633
|
+
"one"
|
|
634
|
+
],
|
|
635
|
+
"deployTargets": [
|
|
636
|
+
{
|
|
637
|
+
"name": "ai-chat-rag",
|
|
638
|
+
"kind": "worker",
|
|
639
|
+
"apiUrl": null,
|
|
640
|
+
"domains": [
|
|
641
|
+
"ai-chat-rag.topolo.workers.dev"
|
|
642
|
+
],
|
|
643
|
+
"routes": [],
|
|
644
|
+
"serviceId": null
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
"name": "topoloone",
|
|
648
|
+
"kind": "pages",
|
|
649
|
+
"apiUrl": null,
|
|
650
|
+
"domains": [
|
|
651
|
+
"oneclick-dashboard.pages.dev"
|
|
652
|
+
],
|
|
653
|
+
"routes": [],
|
|
654
|
+
"serviceId": null
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
"name": "topoloone-website",
|
|
658
|
+
"kind": "worker",
|
|
659
|
+
"apiUrl": null,
|
|
660
|
+
"domains": [
|
|
661
|
+
"one.topolo.io"
|
|
662
|
+
],
|
|
663
|
+
"routes": [],
|
|
664
|
+
"serviceId": null
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
"name": "topoloone-worker",
|
|
668
|
+
"kind": "worker",
|
|
669
|
+
"apiUrl": "https://topoloone-worker.topolo.workers.dev",
|
|
670
|
+
"domains": [],
|
|
671
|
+
"routes": [
|
|
672
|
+
"one.topolo.app/*"
|
|
673
|
+
],
|
|
674
|
+
"serviceId": "one"
|
|
675
|
+
}
|
|
676
|
+
]
|
|
677
|
+
},
|
|
678
|
+
"pay": {
|
|
679
|
+
"id": "pay",
|
|
680
|
+
"appDir": "TopoloPay",
|
|
681
|
+
"name": "topolo-pay",
|
|
682
|
+
"packageName": "topolo-pay",
|
|
683
|
+
"productionUrl": "https://pay.topolo.app",
|
|
684
|
+
"services": [
|
|
685
|
+
"pay"
|
|
686
|
+
],
|
|
687
|
+
"deployTargets": [
|
|
688
|
+
{
|
|
689
|
+
"name": "topolo-pay",
|
|
690
|
+
"kind": "worker",
|
|
691
|
+
"apiUrl": "https://topolo-pay.topolo.workers.dev",
|
|
692
|
+
"domains": [],
|
|
693
|
+
"routes": [],
|
|
694
|
+
"serviceId": "pay"
|
|
695
|
+
}
|
|
696
|
+
]
|
|
697
|
+
},
|
|
698
|
+
"quro": {
|
|
699
|
+
"id": "quro",
|
|
700
|
+
"appDir": "TopoloQuro",
|
|
701
|
+
"name": "TopoloQuro",
|
|
702
|
+
"packageName": null,
|
|
703
|
+
"productionUrl": "https://quro.topolo.app",
|
|
704
|
+
"services": [
|
|
705
|
+
"quro.api",
|
|
706
|
+
"quro.redirect"
|
|
707
|
+
],
|
|
708
|
+
"deployTargets": [
|
|
709
|
+
{
|
|
710
|
+
"name": "quro-api",
|
|
711
|
+
"kind": "worker",
|
|
712
|
+
"apiUrl": "https://api.ol0.me",
|
|
713
|
+
"domains": [],
|
|
714
|
+
"routes": [],
|
|
715
|
+
"serviceId": "quro.api"
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
"name": "quro-redirect-system",
|
|
719
|
+
"kind": "worker",
|
|
720
|
+
"apiUrl": "https://ol0.me",
|
|
721
|
+
"domains": [],
|
|
722
|
+
"routes": [
|
|
723
|
+
"ol0.me/*"
|
|
724
|
+
],
|
|
725
|
+
"serviceId": "quro.redirect"
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"name": "quro-ui",
|
|
729
|
+
"kind": "pages",
|
|
730
|
+
"apiUrl": null,
|
|
731
|
+
"domains": [],
|
|
732
|
+
"routes": [],
|
|
733
|
+
"serviceId": null
|
|
734
|
+
}
|
|
735
|
+
]
|
|
736
|
+
},
|
|
737
|
+
"roadmapper": {
|
|
738
|
+
"id": "roadmapper",
|
|
739
|
+
"appDir": "TopoloRoadmapper",
|
|
740
|
+
"name": "topolo-roadmapper",
|
|
741
|
+
"packageName": "topolo-roadmapper",
|
|
742
|
+
"productionUrl": "https://roadmapper.topolo.app",
|
|
743
|
+
"services": [
|
|
744
|
+
"roadmapper"
|
|
745
|
+
],
|
|
746
|
+
"deployTargets": [
|
|
747
|
+
{
|
|
748
|
+
"name": "roadmapper",
|
|
749
|
+
"kind": "pages",
|
|
750
|
+
"apiUrl": null,
|
|
751
|
+
"domains": [
|
|
752
|
+
"roadmapper.topolo.app"
|
|
753
|
+
],
|
|
754
|
+
"routes": [],
|
|
755
|
+
"serviceId": null
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
"name": "topolo-production-roadmapper",
|
|
759
|
+
"kind": "worker",
|
|
760
|
+
"apiUrl": "https://roadmapper-api.topolo.app",
|
|
761
|
+
"domains": [
|
|
762
|
+
"roadmapper-api.topolo.app"
|
|
763
|
+
],
|
|
764
|
+
"routes": [],
|
|
765
|
+
"serviceId": "roadmapper"
|
|
766
|
+
}
|
|
767
|
+
]
|
|
768
|
+
},
|
|
769
|
+
"social_studio": {
|
|
770
|
+
"id": "social_studio",
|
|
771
|
+
"appDir": "TopoloSocialStudio",
|
|
772
|
+
"name": "topolo-social-studio",
|
|
773
|
+
"packageName": "topolo-social-studio",
|
|
774
|
+
"productionUrl": "https://studio.topolo.app",
|
|
775
|
+
"services": [
|
|
776
|
+
"social_studio"
|
|
777
|
+
],
|
|
778
|
+
"deployTargets": [
|
|
779
|
+
{
|
|
780
|
+
"name": "topolo-social-studio",
|
|
781
|
+
"kind": "pages",
|
|
782
|
+
"apiUrl": null,
|
|
783
|
+
"domains": [],
|
|
784
|
+
"routes": [],
|
|
785
|
+
"serviceId": null
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
"name": "topolo-social-studio-api",
|
|
789
|
+
"kind": "worker",
|
|
790
|
+
"apiUrl": "https://topolo-social-studio-api.topolo.workers.dev",
|
|
791
|
+
"domains": [],
|
|
792
|
+
"routes": [],
|
|
793
|
+
"serviceId": "social_studio"
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
"name": "topolo-social-studio-queue",
|
|
797
|
+
"kind": "worker",
|
|
798
|
+
"apiUrl": null,
|
|
799
|
+
"domains": [],
|
|
800
|
+
"routes": [],
|
|
801
|
+
"serviceId": null
|
|
802
|
+
}
|
|
803
|
+
]
|
|
804
|
+
},
|
|
805
|
+
"socialize": {
|
|
806
|
+
"id": "socialize",
|
|
807
|
+
"appDir": "TopoloSocialize",
|
|
808
|
+
"name": "socialize",
|
|
809
|
+
"packageName": "topolo-socialize",
|
|
810
|
+
"productionUrl": "https://socialize.topolo.app",
|
|
811
|
+
"services": [
|
|
812
|
+
"socialize"
|
|
813
|
+
],
|
|
814
|
+
"deployTargets": [
|
|
815
|
+
{
|
|
816
|
+
"name": "socialize",
|
|
817
|
+
"kind": "pages",
|
|
818
|
+
"apiUrl": null,
|
|
819
|
+
"domains": [],
|
|
820
|
+
"routes": [],
|
|
821
|
+
"serviceId": null
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
"name": "socialize-prod-api",
|
|
825
|
+
"kind": "worker",
|
|
826
|
+
"apiUrl": "https://socialize-prod-api.topolo.workers.dev",
|
|
827
|
+
"domains": [],
|
|
828
|
+
"routes": [],
|
|
829
|
+
"serviceId": "socialize"
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"name": "socialize-prod-scheduler",
|
|
833
|
+
"kind": "worker",
|
|
834
|
+
"apiUrl": null,
|
|
835
|
+
"domains": [],
|
|
836
|
+
"routes": [],
|
|
837
|
+
"serviceId": null
|
|
838
|
+
}
|
|
839
|
+
]
|
|
840
|
+
},
|
|
841
|
+
"support": {
|
|
842
|
+
"id": "support",
|
|
843
|
+
"appDir": "TopoloSupport",
|
|
844
|
+
"name": "topolo-support",
|
|
845
|
+
"packageName": "topolo-support",
|
|
846
|
+
"productionUrl": "https://support.topolo.app",
|
|
847
|
+
"services": [
|
|
848
|
+
"support"
|
|
849
|
+
],
|
|
850
|
+
"deployTargets": [
|
|
851
|
+
{
|
|
852
|
+
"name": "topolo-support",
|
|
853
|
+
"kind": "workers",
|
|
854
|
+
"apiUrl": "https://support.topolo.app",
|
|
855
|
+
"domains": [
|
|
856
|
+
"support.topolo.app"
|
|
857
|
+
],
|
|
858
|
+
"routes": [
|
|
859
|
+
"support.topolo.app/*"
|
|
860
|
+
],
|
|
861
|
+
"serviceId": "support"
|
|
862
|
+
}
|
|
863
|
+
]
|
|
864
|
+
},
|
|
865
|
+
"web": {
|
|
866
|
+
"id": "web",
|
|
867
|
+
"appDir": "TopoloWeb",
|
|
868
|
+
"name": "topoloweb",
|
|
869
|
+
"packageName": "topolo-web",
|
|
870
|
+
"productionUrl": "https://web.topolo.app",
|
|
871
|
+
"services": [
|
|
872
|
+
"web"
|
|
873
|
+
],
|
|
874
|
+
"deployTargets": [
|
|
875
|
+
{
|
|
876
|
+
"name": "topoloweb-api",
|
|
877
|
+
"kind": "worker",
|
|
878
|
+
"apiUrl": "https://web-api.topolo.app",
|
|
879
|
+
"domains": [
|
|
880
|
+
"web-api.topolo.app"
|
|
881
|
+
],
|
|
882
|
+
"routes": [],
|
|
883
|
+
"serviceId": "web"
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
"name": "topoloweb-runtime",
|
|
887
|
+
"kind": "worker",
|
|
888
|
+
"apiUrl": "https://sites.topolo.app",
|
|
889
|
+
"domains": [
|
|
890
|
+
"sites.topolo.app"
|
|
891
|
+
],
|
|
892
|
+
"routes": [],
|
|
893
|
+
"serviceId": null
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
"name": "topoloweb-studio",
|
|
897
|
+
"kind": "pages",
|
|
898
|
+
"apiUrl": null,
|
|
899
|
+
"domains": [
|
|
900
|
+
"web.topolo.app"
|
|
901
|
+
],
|
|
902
|
+
"routes": [],
|
|
903
|
+
"serviceId": null
|
|
904
|
+
}
|
|
905
|
+
]
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
var APPLICATION_REQUIREMENTS_VERSION = "2026-04-22.1";
|
|
909
|
+
var APPLICATION_REQUIREMENTS = [
|
|
910
|
+
{
|
|
911
|
+
id: "platform-metadata",
|
|
912
|
+
title: "Register the application in platform metadata",
|
|
913
|
+
summary: "Every app must have a stable application ID, CloudControl metadata for deployed surfaces, and package metadata where code is shipped.",
|
|
914
|
+
appliesTo: ["all"],
|
|
915
|
+
evidence: [
|
|
916
|
+
"topolo.cloudcontrol.json exists for every deployed app surface",
|
|
917
|
+
"package.json names the package or app when the repo ships code",
|
|
918
|
+
"The SDK application catalog exposes the app through topolo apps and MCP discovery"
|
|
919
|
+
],
|
|
920
|
+
implementation: [
|
|
921
|
+
"Add the app to the SDK application catalog generator before exposing it to agents",
|
|
922
|
+
"Keep production URLs, Worker names, Pages projects, routes, domains, and bindings current in CloudControl metadata",
|
|
923
|
+
"Do not leave a new Topolo* app directory without an explicit application ID"
|
|
924
|
+
]
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
id: "canonical-docs",
|
|
928
|
+
title: "Update canonical docs with the code change",
|
|
929
|
+
summary: "TopoloDocs is the source of truth for application behavior, ownership, deployment shape, auth boundaries, and operations.",
|
|
930
|
+
appliesTo: ["all"],
|
|
931
|
+
evidence: [
|
|
932
|
+
"Matching system registry entry exists in TopoloDocs",
|
|
933
|
+
"Internal handbook coverage exists for the system",
|
|
934
|
+
"npm run validate passes in TopoloDocs when docs are touched"
|
|
935
|
+
],
|
|
936
|
+
implementation: [
|
|
937
|
+
"Read the matching TopoloDocs system entry before coding",
|
|
938
|
+
"Update docs in the same change when behavior, API, auth, data ownership, deployment, or operations change",
|
|
939
|
+
"Update last_verified dates on touched canonical pages"
|
|
940
|
+
]
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
id: "shared-auth-boundary",
|
|
944
|
+
title: "Use the shared Topolo auth boundary",
|
|
945
|
+
summary: "Applications must derive identity, organization, scopes, and service entitlement from TopoloAuth rather than inventing parallel auth state.",
|
|
946
|
+
appliesTo: ["browser", "api", "tooling", "agent_surface"],
|
|
947
|
+
evidence: [
|
|
948
|
+
"@topolo/auth-client or @topolo/auth-middleware is used where applicable",
|
|
949
|
+
"No public command, tool, or SDK method accepts orgId",
|
|
950
|
+
"Service slug and Auth service registration are documented for protected services"
|
|
951
|
+
],
|
|
952
|
+
implementation: [
|
|
953
|
+
"Use shared auth packages instead of copying token parsing or browser storage flows",
|
|
954
|
+
"Derive organization from the credential on every request",
|
|
955
|
+
"Route OAuth, refresh, logout, and SSO paths according to the shared Auth contract"
|
|
956
|
+
]
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
id: "shared-shell-and-launcher",
|
|
960
|
+
title: "Use the shared shell, launcher, and account menu",
|
|
961
|
+
summary: "Browser applications should feel like one platform and should not create isolated navigation or account surfaces.",
|
|
962
|
+
appliesTo: ["browser"],
|
|
963
|
+
evidence: [
|
|
964
|
+
"@topolo/ui-kit is used for the platform shell where the app has authenticated UI",
|
|
965
|
+
"The shared launcher/app switcher is reachable from authenticated app chrome",
|
|
966
|
+
"Account menu and sign-out behavior use the shared platform affordance"
|
|
967
|
+
],
|
|
968
|
+
implementation: [
|
|
969
|
+
"Use TopoloShell or the current shared shell contract for authenticated app layouts",
|
|
970
|
+
"Use shared design tokens and platform UI primitives before creating local equivalents",
|
|
971
|
+
"Keep mobile and desktop shell behavior aligned with the platform UI kit"
|
|
972
|
+
]
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
id: "service-registration-and-scopes",
|
|
976
|
+
title: "Register protected APIs as platform services",
|
|
977
|
+
summary: "Callable app APIs need a stable service ID, service slug, permissions, role bundles, and API-key scopes before agents or users rely on them.",
|
|
978
|
+
appliesTo: ["api"],
|
|
979
|
+
evidence: [
|
|
980
|
+
"Auth service catalog contains the service ID and slug",
|
|
981
|
+
"Permissions, role bundles, and API-key scopes exist for the service",
|
|
982
|
+
"SDK, CLI, and MCP service discovery include the callable production API"
|
|
983
|
+
],
|
|
984
|
+
implementation: [
|
|
985
|
+
"Add production Worker api_url metadata before adding an SDK service ID",
|
|
986
|
+
"Expose stable service IDs through DEFAULT_SERVICE_URLS",
|
|
987
|
+
"Keep typed SDK/CLI/MCP surfaces in sync when an API contract stabilizes"
|
|
988
|
+
]
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
id: "organization-scoped-data",
|
|
992
|
+
title: "Keep application data organization-scoped",
|
|
993
|
+
summary: "Protected data access must be scoped by the authenticated organization at the backend boundary, not by client-provided hints.",
|
|
994
|
+
appliesTo: ["api"],
|
|
995
|
+
evidence: [
|
|
996
|
+
"Backend request handlers derive org membership from credential validation",
|
|
997
|
+
"Queries and Durable Object keys include the authenticated organization boundary where data is tenant-owned",
|
|
998
|
+
"MCP and CLI inputs do not include orgId overrides"
|
|
999
|
+
],
|
|
1000
|
+
implementation: [
|
|
1001
|
+
"Reject or ignore organization hints supplied by clients",
|
|
1002
|
+
"Bind SQL, KV, R2, queue, and Durable Object access to the authenticated organization",
|
|
1003
|
+
"Document service-local exceptions explicitly in the system security assurance record"
|
|
1004
|
+
]
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
id: "cloudflare-deployment-contract",
|
|
1008
|
+
title: "Keep Cloudflare deployment metadata complete",
|
|
1009
|
+
summary: "Workers, Pages, bindings, routes, env vars, queues, cron triggers, and deployment commands must be discoverable through CloudControl metadata.",
|
|
1010
|
+
appliesTo: ["browser", "api"],
|
|
1011
|
+
evidence: [
|
|
1012
|
+
"topolo.cloudcontrol.json lists production deploy targets and URLs",
|
|
1013
|
+
"Wrangler/Pages configuration matches CloudControl metadata",
|
|
1014
|
+
"Secrets and environment variables are not committed"
|
|
1015
|
+
],
|
|
1016
|
+
implementation: [
|
|
1017
|
+
"Update CloudControl metadata whenever deployment topology changes",
|
|
1018
|
+
"Prefer Cloudflare-native runtime surfaces for new Topolo app infrastructure",
|
|
1019
|
+
"Keep Worker bindings and Pages domains explicit enough for agents to inspect before deploying"
|
|
1020
|
+
]
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
id: "agent-and-operator-surfaces",
|
|
1024
|
+
title: "Expose agent-safe operations through SDK, CLI, and MCP",
|
|
1025
|
+
summary: "Agent-accessible functionality should be discoverable, scope-gated, and write-confirmed instead of hidden behind ad hoc HTTP calls.",
|
|
1026
|
+
appliesTo: ["api", "tooling", "agent_surface"],
|
|
1027
|
+
evidence: [
|
|
1028
|
+
"Read operations have typed SDK/CLI/MCP surfaces once their contract stabilizes",
|
|
1029
|
+
"Mutating generic calls require --confirm or confirm:true",
|
|
1030
|
+
"MCP tools never accept orgId and advertise only appropriate scope-gated tools"
|
|
1031
|
+
],
|
|
1032
|
+
implementation: [
|
|
1033
|
+
"Add SDK types first, then CLI commands and MCP tools for durable workflows",
|
|
1034
|
+
"Use generic API passthrough only as a temporary escape hatch",
|
|
1035
|
+
"Keep command output JSON-stable for agents and human-readable with --no-json"
|
|
1036
|
+
]
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
id: "observability-and-audit",
|
|
1040
|
+
title: "Emit platform audit and debugging signals",
|
|
1041
|
+
summary: "Requests, agent actions, and operational workflows need traceable IDs, client labels, and useful failure evidence.",
|
|
1042
|
+
appliesTo: ["api", "tooling", "agent_surface"],
|
|
1043
|
+
evidence: [
|
|
1044
|
+
"Requests carry X-Topolo-Client and X-Topolo-Request-Id where the SDK is involved",
|
|
1045
|
+
"Agent-originated calls can include X-Topolo-Agent",
|
|
1046
|
+
"Failure modes are documented with request IDs or deployment/log lookup paths"
|
|
1047
|
+
],
|
|
1048
|
+
implementation: [
|
|
1049
|
+
"Use TopoloClient for platform API calls rather than raw fetch where practical",
|
|
1050
|
+
"Keep logs free of secrets and tokens",
|
|
1051
|
+
"Document live smoke commands and request-id correlation in canonical docs"
|
|
1052
|
+
]
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
id: "verification-gates",
|
|
1056
|
+
title: "Ship with build, test, docs, and smoke verification",
|
|
1057
|
+
summary: "A Topolo app change is not complete until the relevant code checks and docs checks have been run or explicitly reported as blocked.",
|
|
1058
|
+
appliesTo: ["all"],
|
|
1059
|
+
evidence: [
|
|
1060
|
+
"Relevant package build/typecheck/test commands pass",
|
|
1061
|
+
"TopoloDocs validation passes when docs are touched",
|
|
1062
|
+
"Live or local smoke evidence is captured for user-visible or API behavior"
|
|
1063
|
+
],
|
|
1064
|
+
implementation: [
|
|
1065
|
+
"Prefer existing package scripts before adding new tooling",
|
|
1066
|
+
"Run app-specific checks and shared docs validation for behavior changes",
|
|
1067
|
+
"Report any skipped verification with the blocker and residual risk"
|
|
1068
|
+
]
|
|
1069
|
+
}
|
|
1070
|
+
];
|
|
1071
|
+
function applicationRequirementScopes(application) {
|
|
1072
|
+
const scopes = /* @__PURE__ */ new Set(["all"]);
|
|
1073
|
+
const hasBrowserSurface = Boolean(application.productionUrl) || application.deployTargets.some((target) => target.kind === "pages");
|
|
1074
|
+
const hasApiSurface = application.services.length > 0 || application.deployTargets.some((target) => Boolean(target.apiUrl));
|
|
1075
|
+
if (hasBrowserSurface) scopes.add("browser");
|
|
1076
|
+
if (hasApiSurface) scopes.add("api");
|
|
1077
|
+
if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
|
|
1078
|
+
scopes.add("tooling");
|
|
1079
|
+
}
|
|
1080
|
+
if (application.id === "cli" || application.id === "mcp" || hasApiSurface) {
|
|
1081
|
+
scopes.add("agent_surface");
|
|
1082
|
+
}
|
|
1083
|
+
return Array.from(scopes);
|
|
1084
|
+
}
|
|
1085
|
+
function requirementsForApplication(applicationOrId) {
|
|
1086
|
+
const application = typeof applicationOrId === "string" ? APPLICATIONS[applicationOrId] : applicationOrId;
|
|
1087
|
+
const scopes = new Set(applicationRequirementScopes(application));
|
|
1088
|
+
return APPLICATION_REQUIREMENTS.filter(
|
|
1089
|
+
(requirement) => requirement.appliesTo.some((scope) => scopes.has(scope))
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
function auditApplicationRequirements(applicationOrId) {
|
|
1093
|
+
const application = typeof applicationOrId === "string" ? APPLICATIONS[applicationOrId] : applicationOrId;
|
|
1094
|
+
const scopes = applicationRequirementScopes(application);
|
|
1095
|
+
const findings = requirementsForApplication(application).map(
|
|
1096
|
+
(requirement) => evaluateRequirement(application, requirement)
|
|
1097
|
+
);
|
|
1098
|
+
return {
|
|
1099
|
+
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
1100
|
+
application,
|
|
1101
|
+
scopes,
|
|
1102
|
+
score: scoreFindings(findings),
|
|
1103
|
+
findings
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function auditAllApplicationRequirements(applicationIds = Object.keys(APPLICATIONS).sort()) {
|
|
1107
|
+
const applications = applicationIds.map(
|
|
1108
|
+
(applicationId) => auditApplicationRequirements(applicationId)
|
|
1109
|
+
);
|
|
1110
|
+
const migrationQueue = applications.flatMap(
|
|
1111
|
+
(audit) => audit.findings.filter((finding2) => finding2.status !== "met").map((finding2) => ({
|
|
1112
|
+
applicationId: audit.application.id,
|
|
1113
|
+
applicationName: audit.application.name,
|
|
1114
|
+
requirementId: finding2.requirementId,
|
|
1115
|
+
title: finding2.title,
|
|
1116
|
+
status: finding2.status,
|
|
1117
|
+
reason: finding2.reason,
|
|
1118
|
+
nextActions: finding2.nextActions
|
|
1119
|
+
}))
|
|
1120
|
+
).sort((a, b) => {
|
|
1121
|
+
const statusOrder = statusRank(a.status) - statusRank(b.status);
|
|
1122
|
+
if (statusOrder !== 0) return statusOrder;
|
|
1123
|
+
const appOrder = a.applicationId.localeCompare(b.applicationId);
|
|
1124
|
+
if (appOrder !== 0) return appOrder;
|
|
1125
|
+
return a.requirementId.localeCompare(b.requirementId);
|
|
1126
|
+
});
|
|
1127
|
+
return {
|
|
1128
|
+
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
1129
|
+
applications,
|
|
1130
|
+
migrationQueue
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
function evaluateRequirement(application, requirement) {
|
|
1134
|
+
const evidence = [];
|
|
1135
|
+
switch (requirement.id) {
|
|
1136
|
+
case "platform-metadata": {
|
|
1137
|
+
if (application.packageName) evidence.push(`package=${application.packageName}`);
|
|
1138
|
+
if (application.productionUrl) evidence.push(`productionUrl=${application.productionUrl}`);
|
|
1139
|
+
if (application.deployTargets.length > 0) {
|
|
1140
|
+
evidence.push(`deployTargets=${application.deployTargets.length}`);
|
|
1141
|
+
}
|
|
1142
|
+
if (application.services.length > 0) {
|
|
1143
|
+
evidence.push(`services=${application.services.join(",")}`);
|
|
1144
|
+
}
|
|
1145
|
+
if (evidence.length === 0) {
|
|
1146
|
+
return finding(requirement, "missing", "No package, production URL, deploy target, or service metadata is available.", evidence);
|
|
1147
|
+
}
|
|
1148
|
+
return finding(requirement, "met", "Catalog metadata exists for this application.", evidence);
|
|
1149
|
+
}
|
|
1150
|
+
case "canonical-docs":
|
|
1151
|
+
return finding(
|
|
1152
|
+
requirement,
|
|
1153
|
+
"needs_review",
|
|
1154
|
+
"Catalog metadata cannot verify matching TopoloDocs system and handbook coverage.",
|
|
1155
|
+
evidence
|
|
1156
|
+
);
|
|
1157
|
+
case "shared-auth-boundary":
|
|
1158
|
+
return finding(
|
|
1159
|
+
requirement,
|
|
1160
|
+
"needs_review",
|
|
1161
|
+
"Catalog metadata cannot verify that implementation uses shared Topolo auth packages and derives org from credentials.",
|
|
1162
|
+
evidence
|
|
1163
|
+
);
|
|
1164
|
+
case "shared-shell-and-launcher":
|
|
1165
|
+
return finding(
|
|
1166
|
+
requirement,
|
|
1167
|
+
"needs_review",
|
|
1168
|
+
"Catalog metadata cannot verify shared shell, launcher, and account menu adoption.",
|
|
1169
|
+
evidence
|
|
1170
|
+
);
|
|
1171
|
+
case "service-registration-and-scopes": {
|
|
1172
|
+
if (application.services.length > 0) {
|
|
1173
|
+
evidence.push(`services=${application.services.join(",")}`);
|
|
1174
|
+
return finding(
|
|
1175
|
+
requirement,
|
|
1176
|
+
"partial",
|
|
1177
|
+
"SDK service discovery exists, but Auth catalog permissions, role bundles, and API-key scopes still need confirmation.",
|
|
1178
|
+
evidence
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
return finding(requirement, "missing", "No SDK service ID is registered for this callable API surface.", evidence);
|
|
1182
|
+
}
|
|
1183
|
+
case "organization-scoped-data":
|
|
1184
|
+
return finding(
|
|
1185
|
+
requirement,
|
|
1186
|
+
"needs_review",
|
|
1187
|
+
"Catalog metadata cannot verify backend tenant filters, storage keys, or Durable Object boundaries.",
|
|
1188
|
+
evidence
|
|
1189
|
+
);
|
|
1190
|
+
case "cloudflare-deployment-contract": {
|
|
1191
|
+
if (application.deployTargets.length === 0) {
|
|
1192
|
+
return finding(requirement, "missing", "No CloudControl deploy targets are registered.", evidence);
|
|
1193
|
+
}
|
|
1194
|
+
evidence.push(`deployTargets=${application.deployTargets.length}`);
|
|
1195
|
+
if (application.productionUrl) evidence.push(`productionUrl=${application.productionUrl}`);
|
|
1196
|
+
return finding(requirement, "met", "CloudControl deploy target metadata is present.", evidence);
|
|
1197
|
+
}
|
|
1198
|
+
case "agent-and-operator-surfaces": {
|
|
1199
|
+
if (application.services.length > 0) {
|
|
1200
|
+
evidence.push(`services=${application.services.join(",")}`);
|
|
1201
|
+
return finding(requirement, "partial", "SDK service discovery exists; typed SDK/CLI/MCP workflow coverage should be checked as APIs stabilize.", evidence);
|
|
1202
|
+
}
|
|
1203
|
+
if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
|
|
1204
|
+
evidence.push(`tooling=${application.id}`);
|
|
1205
|
+
return finding(requirement, "met", "This application is itself an agent/operator surface.", evidence);
|
|
1206
|
+
}
|
|
1207
|
+
return finding(requirement, "missing", "No SDK service or tooling surface is registered.", evidence);
|
|
1208
|
+
}
|
|
1209
|
+
case "observability-and-audit":
|
|
1210
|
+
return finding(
|
|
1211
|
+
requirement,
|
|
1212
|
+
"needs_review",
|
|
1213
|
+
"Catalog metadata cannot verify runtime request IDs, client labels, or log redaction.",
|
|
1214
|
+
evidence
|
|
1215
|
+
);
|
|
1216
|
+
case "verification-gates":
|
|
1217
|
+
return finding(
|
|
1218
|
+
requirement,
|
|
1219
|
+
"needs_review",
|
|
1220
|
+
"Catalog metadata cannot verify package checks, docs validation, or smoke evidence.",
|
|
1221
|
+
evidence
|
|
1222
|
+
);
|
|
1223
|
+
default:
|
|
1224
|
+
return finding(requirement, "needs_review", "No automated evaluator exists for this requirement yet.", evidence);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function finding(requirement, status, reason, evidence) {
|
|
1228
|
+
return {
|
|
1229
|
+
requirementId: requirement.id,
|
|
1230
|
+
title: requirement.title,
|
|
1231
|
+
status,
|
|
1232
|
+
reason,
|
|
1233
|
+
evidence,
|
|
1234
|
+
nextActions: status === "met" ? [] : requirement.implementation
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function scoreFindings(findings) {
|
|
1238
|
+
const met = findings.filter((finding2) => finding2.status === "met").length;
|
|
1239
|
+
const partial = findings.filter((finding2) => finding2.status === "partial").length;
|
|
1240
|
+
const missing = findings.filter((finding2) => finding2.status === "missing").length;
|
|
1241
|
+
const needsReview = findings.filter((finding2) => finding2.status === "needs_review").length;
|
|
1242
|
+
const total = findings.length;
|
|
1243
|
+
const weighted = met + partial * 0.5;
|
|
1244
|
+
return {
|
|
1245
|
+
met,
|
|
1246
|
+
partial,
|
|
1247
|
+
missing,
|
|
1248
|
+
needsReview,
|
|
1249
|
+
total,
|
|
1250
|
+
percent: total === 0 ? 100 : Math.round(weighted / total * 100)
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
function statusRank(status) {
|
|
1254
|
+
if (status === "missing") return 0;
|
|
1255
|
+
if (status === "partial") return 1;
|
|
1256
|
+
return 2;
|
|
1257
|
+
}
|
|
94
1258
|
var DEFAULT_SERVICE_URLS = SERVICES;
|
|
95
1259
|
function resolveServiceUrl(service, overrides) {
|
|
96
1260
|
const override = overrides?.[service];
|
|
@@ -407,8 +1571,8 @@ var TopoloOAuth = class {
|
|
|
407
1571
|
if (params.clientSecret) body.set("client_secret", params.clientSecret);
|
|
408
1572
|
return this.#postForm("/api/developer-oauth/token", body);
|
|
409
1573
|
}
|
|
410
|
-
async #postForm(
|
|
411
|
-
const url = new URL(
|
|
1574
|
+
async #postForm(path2, body) {
|
|
1575
|
+
const url = new URL(path2, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
|
|
412
1576
|
const res = await this.fetchImpl(url.toString(), {
|
|
413
1577
|
method: "POST",
|
|
414
1578
|
headers: {
|
|
@@ -424,7 +1588,7 @@ var TopoloOAuth = class {
|
|
|
424
1588
|
const err = parsed;
|
|
425
1589
|
throw new TopoloAuthError(err.error_description || err.error, err.error);
|
|
426
1590
|
}
|
|
427
|
-
throw new TopoloHttpError("auth",
|
|
1591
|
+
throw new TopoloHttpError("auth", path2, res.status, parsed);
|
|
428
1592
|
}
|
|
429
1593
|
return parsed;
|
|
430
1594
|
}
|
|
@@ -450,22 +1614,22 @@ function configPath() {
|
|
|
450
1614
|
return join(base, "topolo", "config.json");
|
|
451
1615
|
}
|
|
452
1616
|
async function readConfig() {
|
|
453
|
-
const
|
|
454
|
-
if (!existsSync(
|
|
1617
|
+
const path2 = configPath();
|
|
1618
|
+
if (!existsSync(path2)) return {};
|
|
455
1619
|
try {
|
|
456
|
-
const raw = await readFile(
|
|
1620
|
+
const raw = await readFile(path2, "utf8");
|
|
457
1621
|
return JSON.parse(raw);
|
|
458
1622
|
} catch {
|
|
459
1623
|
return {};
|
|
460
1624
|
}
|
|
461
1625
|
}
|
|
462
1626
|
async function writeConfig(config) {
|
|
463
|
-
const
|
|
464
|
-
await mkdir(dirname(
|
|
465
|
-
await writeFile(
|
|
1627
|
+
const path2 = configPath();
|
|
1628
|
+
await mkdir(dirname(path2), { recursive: true });
|
|
1629
|
+
await writeFile(path2, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
466
1630
|
if (platform() !== "win32") {
|
|
467
1631
|
try {
|
|
468
|
-
await chmod(
|
|
1632
|
+
await chmod(path2, 384);
|
|
469
1633
|
} catch {
|
|
470
1634
|
}
|
|
471
1635
|
}
|
|
@@ -765,7 +1929,7 @@ function sleep(ms) {
|
|
|
765
1929
|
}
|
|
766
1930
|
|
|
767
1931
|
// src/version.ts
|
|
768
|
-
var CLI_VERSION = "0.1.
|
|
1932
|
+
var CLI_VERSION = "0.1.8";
|
|
769
1933
|
|
|
770
1934
|
// src/client.ts
|
|
771
1935
|
async function buildClient(options) {
|
|
@@ -916,7 +2080,7 @@ function registerApiCommand(program2) {
|
|
|
916
2080
|
"-q, --query <kv...>",
|
|
917
2081
|
"Query params as key=value (repeatable), e.g. --query source=cli page=2"
|
|
918
2082
|
).option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
|
|
919
|
-
async (service, method,
|
|
2083
|
+
async (service, method, path2, opts) => {
|
|
920
2084
|
const mode = resolveOutputMode(opts.json);
|
|
921
2085
|
try {
|
|
922
2086
|
const upperMethod = method.toUpperCase();
|
|
@@ -952,7 +2116,7 @@ function registerApiCommand(program2) {
|
|
|
952
2116
|
const result = await topolo.client.request({
|
|
953
2117
|
service,
|
|
954
2118
|
method: httpMethod,
|
|
955
|
-
path,
|
|
2119
|
+
path: path2,
|
|
956
2120
|
body,
|
|
957
2121
|
...query ? { query } : {},
|
|
958
2122
|
confirm: opts.confirm === true
|
|
@@ -979,6 +2143,1036 @@ function parseQueryPairs(pairs) {
|
|
|
979
2143
|
return out;
|
|
980
2144
|
}
|
|
981
2145
|
|
|
2146
|
+
// src/commands/app-audit-local.ts
|
|
2147
|
+
import {
|
|
2148
|
+
existsSync as existsSync2,
|
|
2149
|
+
readdirSync,
|
|
2150
|
+
readFileSync,
|
|
2151
|
+
statSync
|
|
2152
|
+
} from "fs";
|
|
2153
|
+
import path from "path";
|
|
2154
|
+
var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
2155
|
+
".git",
|
|
2156
|
+
".next",
|
|
2157
|
+
".turbo",
|
|
2158
|
+
".wrangler",
|
|
2159
|
+
"build",
|
|
2160
|
+
"coverage",
|
|
2161
|
+
"dist",
|
|
2162
|
+
"node_modules"
|
|
2163
|
+
]);
|
|
2164
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2165
|
+
".cjs",
|
|
2166
|
+
".dart",
|
|
2167
|
+
".js",
|
|
2168
|
+
".jsx",
|
|
2169
|
+
".kt",
|
|
2170
|
+
".mjs",
|
|
2171
|
+
".rs",
|
|
2172
|
+
".swift",
|
|
2173
|
+
".ts",
|
|
2174
|
+
".tsx"
|
|
2175
|
+
]);
|
|
2176
|
+
function enrichApplicationAuditWithLocalEvidence(report, options = {}) {
|
|
2177
|
+
const repoRoot = resolveTopoloRepoRoot(options.repoRoot, options.cwd ?? process.cwd());
|
|
2178
|
+
if (!repoRoot) {
|
|
2179
|
+
throw new Error("Could not locate the Topolo checkout. Pass --repo-root with the path that contains PlatformApplications.");
|
|
2180
|
+
}
|
|
2181
|
+
const applications = report.applications.map(
|
|
2182
|
+
(audit) => enrichApplicationAudit(audit, collectEvidence(repoRoot, audit.application))
|
|
2183
|
+
);
|
|
2184
|
+
return {
|
|
2185
|
+
...report,
|
|
2186
|
+
applications,
|
|
2187
|
+
migrationQueue: buildMigrationQueue(applications),
|
|
2188
|
+
localEvidence: {
|
|
2189
|
+
mode: "local",
|
|
2190
|
+
repoRoot
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
function resolveTopoloRepoRoot(explicitRoot, cwd = process.cwd()) {
|
|
2195
|
+
const start = path.resolve(explicitRoot ?? cwd);
|
|
2196
|
+
const direct = normalizeTopoloRoot(start);
|
|
2197
|
+
if (direct) return direct;
|
|
2198
|
+
let current = start;
|
|
2199
|
+
while (true) {
|
|
2200
|
+
const candidate = normalizeTopoloRoot(current);
|
|
2201
|
+
if (candidate) return candidate;
|
|
2202
|
+
const parent = path.dirname(current);
|
|
2203
|
+
if (parent === current) return null;
|
|
2204
|
+
current = parent;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
function normalizeTopoloRoot(candidate) {
|
|
2208
|
+
if (isDirectory(path.join(candidate, "PlatformApplications"))) return candidate;
|
|
2209
|
+
if (path.basename(candidate) === "PlatformApplications" && isDirectory(path.join(candidate, "TopoloCli"))) {
|
|
2210
|
+
return path.dirname(candidate);
|
|
2211
|
+
}
|
|
2212
|
+
return null;
|
|
2213
|
+
}
|
|
2214
|
+
function enrichApplicationAudit(audit, evidence) {
|
|
2215
|
+
const findings = audit.findings.map(
|
|
2216
|
+
(finding2) => enrichFinding(audit.application, finding2, evidence)
|
|
2217
|
+
);
|
|
2218
|
+
return {
|
|
2219
|
+
...audit,
|
|
2220
|
+
findings,
|
|
2221
|
+
score: scoreFindings2(findings)
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
function enrichFinding(application, finding2, evidence) {
|
|
2225
|
+
switch (finding2.requirementId) {
|
|
2226
|
+
case "platform-metadata":
|
|
2227
|
+
return mergeFinding(finding2, {
|
|
2228
|
+
status: evidence.appExists ? "met" : finding2.status,
|
|
2229
|
+
reason: evidence.appExists ? "Catalog metadata is present and the local application directory was found." : finding2.reason,
|
|
2230
|
+
evidence: [
|
|
2231
|
+
evidence.appExists ? `appDir=${relativeTopoloPath(evidence.appRoot)}` : null,
|
|
2232
|
+
evidence.packageJson ? `packageJson=${relativeTopoloPath(evidence.packageJsonPath)}` : null,
|
|
2233
|
+
evidence.hasCloudControl ? `cloudcontrol=${relativeTopoloPath(evidence.cloudcontrolPath)}` : null
|
|
2234
|
+
]
|
|
2235
|
+
});
|
|
2236
|
+
case "canonical-docs": {
|
|
2237
|
+
if (evidence.systems.length === 0) {
|
|
2238
|
+
return mergeFinding(finding2, {
|
|
2239
|
+
status: "missing",
|
|
2240
|
+
reason: "No matching TopoloDocs system registry entry was found in the local checkout.",
|
|
2241
|
+
evidence: []
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
return mergeFinding(finding2, {
|
|
2245
|
+
status: evidence.existingDocPaths.length > 0 ? "partial" : "missing",
|
|
2246
|
+
reason: evidence.existingDocPaths.length > 0 ? "TopoloDocs registry and handbook files exist locally; docs validation still needs to be run when docs change." : "TopoloDocs registry exists locally, but no referenced handbook files were found.",
|
|
2247
|
+
evidence: [
|
|
2248
|
+
...evidence.systems.map((system) => `system=${system.id ?? system.name ?? "unknown"}`),
|
|
2249
|
+
...evidence.existingDocPaths.map((docPath) => `doc=${docPath}`)
|
|
2250
|
+
]
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
case "shared-auth-boundary": {
|
|
2254
|
+
const authEvidence = sharedAuthEvidence(evidence);
|
|
2255
|
+
const operatorLocalEvidence = documentedOperatorLocalAuthBoundaryEvidence(evidence);
|
|
2256
|
+
if (authEvidence.length === 0 && operatorLocalEvidence.length > 0) {
|
|
2257
|
+
return mergeFinding(finding2, {
|
|
2258
|
+
status: "partial",
|
|
2259
|
+
reason: "Canonical system security assurance documents an operator-local auth boundary; shared auth package usage does not apply to this local control-plane surface.",
|
|
2260
|
+
evidence: operatorLocalEvidence
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
if (authEvidence.length === 0 && application.id !== "auth") return finding2;
|
|
2264
|
+
return mergeFinding(finding2, {
|
|
2265
|
+
status: "partial",
|
|
2266
|
+
reason: application.id === "auth" ? "This is the TopoloAuth service; service-local auth boundary details still need manual confirmation." : "Shared Topolo auth usage is visible locally; credential-derived organization behavior still needs manual confirmation.",
|
|
2267
|
+
evidence: application.id === "auth" ? ["service=auth"] : authEvidence
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
case "shared-shell-and-launcher": {
|
|
2271
|
+
const shellEvidence = sharedShellEvidence(evidence);
|
|
2272
|
+
if (shellEvidence.length === 0 && hasDocumentationSurface(application, evidence)) {
|
|
2273
|
+
return mergeFinding(finding2, {
|
|
2274
|
+
status: "met",
|
|
2275
|
+
reason: "This is a documentation surface rather than an authenticated app workspace, so the shared application shell requirement does not apply.",
|
|
2276
|
+
evidence: ["surface=documentation-site"]
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
if (shellEvidence.length === 0 && !hasLocalBrowserSurface(evidence)) {
|
|
2280
|
+
return mergeFinding(finding2, {
|
|
2281
|
+
status: "met",
|
|
2282
|
+
reason: "No local browser UI surface was found; the shared shell requirement does not apply to this API or tooling app.",
|
|
2283
|
+
evidence: ["surface=api-or-tooling"]
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
if (shellEvidence.length === 0) return finding2;
|
|
2287
|
+
if (!shellEvidence.includes("source=TopoloAppShell")) {
|
|
2288
|
+
return mergeFinding(finding2, {
|
|
2289
|
+
status: "needs_review",
|
|
2290
|
+
reason: "Shared UI-kit or primitive shell usage is visible locally, but authenticated browser apps must use TopoloAppShell so launcher, account-menu, command, theme, and bug-reporting chrome stay on the shared platform surface.",
|
|
2291
|
+
evidence: shellEvidence,
|
|
2292
|
+
nextActions: [
|
|
2293
|
+
"Use TopoloAppShell for authenticated first-party app layouts",
|
|
2294
|
+
"Keep launcher, account menu, command palette, theme toggle, and bug reporter on the shared shell surface",
|
|
2295
|
+
"Keep mobile and desktop shell behavior aligned with the platform UI kit"
|
|
2296
|
+
]
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
return mergeFinding(finding2, {
|
|
2300
|
+
status: "partial",
|
|
2301
|
+
reason: "Shared TopoloAppShell usage is visible locally; launcher and account-menu behavior still need UI verification.",
|
|
2302
|
+
evidence: shellEvidence,
|
|
2303
|
+
nextActions: [
|
|
2304
|
+
"Use TopoloAppShell for authenticated first-party app layouts",
|
|
2305
|
+
"Keep launcher, account menu, command palette, theme toggle, and bug reporter on the shared shell surface",
|
|
2306
|
+
"Keep mobile and desktop shell behavior aligned with the platform UI kit"
|
|
2307
|
+
]
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
case "organization-scoped-data": {
|
|
2311
|
+
const orgEvidence = organizationScopeEvidence(evidence);
|
|
2312
|
+
if (orgEvidence.length === 0) return finding2;
|
|
2313
|
+
return mergeFinding(finding2, {
|
|
2314
|
+
status: "partial",
|
|
2315
|
+
reason: "Organization-boundary terms are visible in local backend code; request-derived scoping still needs code review.",
|
|
2316
|
+
evidence: orgEvidence
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
case "cloudflare-deployment-contract": {
|
|
2320
|
+
const deploymentEvidence = [
|
|
2321
|
+
evidence.hasCloudControl ? `cloudcontrol=${relativeTopoloPath(evidence.cloudcontrolPath)}` : null,
|
|
2322
|
+
...evidence.wranglerFiles.map((file) => `wrangler=${file}`)
|
|
2323
|
+
].filter((item) => Boolean(item));
|
|
2324
|
+
if (deploymentEvidence.length === 0) return finding2;
|
|
2325
|
+
return mergeFinding(finding2, {
|
|
2326
|
+
status: "met",
|
|
2327
|
+
reason: "CloudControl and Cloudflare deployment metadata are present in the local app directory.",
|
|
2328
|
+
evidence: deploymentEvidence
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
case "observability-and-audit": {
|
|
2332
|
+
const observabilityEvidence = observabilityEvidenceFor(evidence);
|
|
2333
|
+
if (observabilityEvidence.length === 0) return finding2;
|
|
2334
|
+
return mergeFinding(finding2, {
|
|
2335
|
+
status: "partial",
|
|
2336
|
+
reason: "Platform request trace or audit-event signals are visible locally; runtime log correlation still needs live verification.",
|
|
2337
|
+
evidence: observabilityEvidence
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
case "verification-gates": {
|
|
2341
|
+
const verificationEvidence = verificationEvidenceFor(evidence);
|
|
2342
|
+
if (verificationEvidence.length === 0) return finding2;
|
|
2343
|
+
return mergeFinding(finding2, {
|
|
2344
|
+
status: "partial",
|
|
2345
|
+
reason: "Package verification scripts are present locally; the latest run results and smoke evidence still need confirmation.",
|
|
2346
|
+
evidence: verificationEvidence
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
default:
|
|
2350
|
+
return finding2;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function collectEvidence(repoRoot, application) {
|
|
2354
|
+
const appRoot = path.join(repoRoot, "PlatformApplications", application.appDir);
|
|
2355
|
+
const packageJsonPath = path.join(appRoot, "package.json");
|
|
2356
|
+
const packageJson = readPackageJson(packageJsonPath);
|
|
2357
|
+
const cloudcontrolPath = path.join(appRoot, "topolo.cloudcontrol.json");
|
|
2358
|
+
const systems = matchingSystems(repoRoot, application);
|
|
2359
|
+
return {
|
|
2360
|
+
appRoot,
|
|
2361
|
+
appExists: isDirectory(appRoot),
|
|
2362
|
+
packageJsonPath,
|
|
2363
|
+
packageJson,
|
|
2364
|
+
packageJsons: collectPackageJsons(appRoot),
|
|
2365
|
+
cloudcontrolPath,
|
|
2366
|
+
hasCloudControl: isFile(cloudcontrolPath),
|
|
2367
|
+
wranglerFiles: findWranglerFiles(appRoot),
|
|
2368
|
+
source: readSourceIndex(appRoot),
|
|
2369
|
+
systems,
|
|
2370
|
+
existingDocPaths: existingDocs(repoRoot, systems)
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
function mergeFinding(finding2, update) {
|
|
2374
|
+
const status = update.status;
|
|
2375
|
+
return {
|
|
2376
|
+
...finding2,
|
|
2377
|
+
status,
|
|
2378
|
+
reason: update.reason,
|
|
2379
|
+
evidence: unique([...finding2.evidence, ...update.evidence.filter((item) => Boolean(item))]),
|
|
2380
|
+
nextActions: status === "met" ? [] : update.nextActions ?? finding2.nextActions
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
function sharedAuthEvidence(evidence) {
|
|
2384
|
+
const deps = dependencyNames(evidence);
|
|
2385
|
+
const text = evidence.source.text;
|
|
2386
|
+
const items = [];
|
|
2387
|
+
if (deps.has("@topolo/auth-client")) items.push("dependency=@topolo/auth-client");
|
|
2388
|
+
if (deps.has("@topolo/auth-middleware")) items.push("dependency=@topolo/auth-middleware");
|
|
2389
|
+
if (deps.has("@topolo/sdk")) items.push("dependency=@topolo/sdk");
|
|
2390
|
+
if (text.includes("@topolo/auth-client")) items.push("source=@topolo/auth-client");
|
|
2391
|
+
if (text.includes("@topolo/auth-middleware")) items.push("source=@topolo/auth-middleware");
|
|
2392
|
+
if (text.includes("topolo-auth-client")) items.push("source=topolo-auth-client");
|
|
2393
|
+
if (text.includes("topolo-auth-middleware")) items.push("source=topolo-auth-middleware");
|
|
2394
|
+
if (text.includes("TopoloAuthMiddleware")) items.push("source=TopoloAuthMiddleware");
|
|
2395
|
+
if (text.includes("TopoloClient")) items.push("source=TopoloClient");
|
|
2396
|
+
if (text.includes("TopoloOAuth")) items.push("source=TopoloOAuth");
|
|
2397
|
+
return unique(items);
|
|
2398
|
+
}
|
|
2399
|
+
function documentedOperatorLocalAuthBoundaryEvidence(evidence) {
|
|
2400
|
+
return evidence.systems.flatMap((system) => {
|
|
2401
|
+
const serviceIds = [...system.service_ids ?? [], ...system.serviceIds ?? []];
|
|
2402
|
+
const securityAssurance = system.security_assurance ?? system.securityAssurance;
|
|
2403
|
+
const authBoundary = securityAssurance?.auth_boundary ?? securityAssurance?.authBoundary;
|
|
2404
|
+
if (system.visibility !== "internal" || serviceIds.length > 0 || typeof authBoundary !== "string" || !/\boperator-local\b|\blocal\b.*\bcontrol-plane\b/i.test(authBoundary)) {
|
|
2405
|
+
return [];
|
|
2406
|
+
}
|
|
2407
|
+
return [`systemAuthBoundary=${system.id ?? system.name ?? "unknown"}:operator-local`];
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
function sharedShellEvidence(evidence) {
|
|
2411
|
+
const deps = dependencyNames(evidence);
|
|
2412
|
+
const text = evidence.source.text;
|
|
2413
|
+
const items = [];
|
|
2414
|
+
if (deps.has("@topolo/ui-kit")) items.push("dependency=@topolo/ui-kit");
|
|
2415
|
+
if (text.includes("@topolo/ui-kit")) items.push("source=@topolo/ui-kit");
|
|
2416
|
+
if (text.includes("TopoloShell")) items.push("source=TopoloShell");
|
|
2417
|
+
if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
|
|
2418
|
+
if (text.includes("TopoloLauncher")) items.push("source=TopoloLauncher");
|
|
2419
|
+
if (text.includes("AppSwitcher")) items.push("source=AppSwitcher");
|
|
2420
|
+
return unique(items);
|
|
2421
|
+
}
|
|
2422
|
+
function hasDocumentationSurface(application, evidence) {
|
|
2423
|
+
if (application.id === "docs") return true;
|
|
2424
|
+
return dependencyNames(evidence).has("astro") && evidence.source.files.some((file) => file.endsWith("/src/layouts/DocsLayout.astro"));
|
|
2425
|
+
}
|
|
2426
|
+
function hasLocalBrowserSurface(evidence) {
|
|
2427
|
+
const deps = dependencyNames(evidence);
|
|
2428
|
+
if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo/ui-kit")) {
|
|
2429
|
+
return true;
|
|
2430
|
+
}
|
|
2431
|
+
return evidence.source.files.some(
|
|
2432
|
+
(file) => file.endsWith(".tsx") || file.endsWith(".jsx") || file.includes("/src/client/") || file.includes("/client/")
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
function organizationScopeEvidence(evidence) {
|
|
2436
|
+
const text = evidence.source.text;
|
|
2437
|
+
const items = [];
|
|
2438
|
+
if (/\borgId\b/.test(text)) items.push("source=orgId");
|
|
2439
|
+
if (/\borganizationId\b/.test(text)) items.push("source=organizationId");
|
|
2440
|
+
if (/\borganization_id\b/.test(text)) items.push("source=organization_id");
|
|
2441
|
+
if (/\btenantId\b/.test(text)) items.push("source=tenantId");
|
|
2442
|
+
if (/\btenant_id\b/.test(text)) items.push("source=tenant_id");
|
|
2443
|
+
if (/\borganization\b/i.test(text) && /\bcredential|auth|membership|tenant\b/i.test(text)) {
|
|
2444
|
+
items.push("source=organization-auth-boundary");
|
|
2445
|
+
}
|
|
2446
|
+
return unique(items);
|
|
2447
|
+
}
|
|
2448
|
+
function observabilityEvidenceFor(evidence) {
|
|
2449
|
+
const text = evidence.source.text;
|
|
2450
|
+
const items = [];
|
|
2451
|
+
if (text.includes("X-Topolo-Client")) items.push("header=X-Topolo-Client");
|
|
2452
|
+
if (text.includes("X-Topolo-Request-Id")) items.push("header=X-Topolo-Request-Id");
|
|
2453
|
+
if (text.includes("X-Topolo-Agent")) items.push("header=X-Topolo-Agent");
|
|
2454
|
+
if (text.includes("TopoloClient")) items.push("source=TopoloClient");
|
|
2455
|
+
if (text.includes("createTopolo")) items.push("source=createTopolo");
|
|
2456
|
+
if (/\brequestId\b/.test(text)) items.push("source=requestId");
|
|
2457
|
+
if (/\brequest_id\b/.test(text)) items.push("source=request_id");
|
|
2458
|
+
if (/\btraceId\b/.test(text)) items.push("source=traceId");
|
|
2459
|
+
if (/\btrace_id\b/.test(text)) items.push("source=trace_id");
|
|
2460
|
+
if (/\bcorrelationId\b/.test(text)) items.push("source=correlationId");
|
|
2461
|
+
if (/\bcorrelation_id\b/.test(text)) items.push("source=correlation_id");
|
|
2462
|
+
if (/\blogEvent\b/.test(text)) items.push("source=logEvent");
|
|
2463
|
+
if (/\baudit_events\b/.test(text)) items.push("source=audit_events");
|
|
2464
|
+
if (/\brecord_audit_event\b/.test(text)) items.push("source=record_audit_event");
|
|
2465
|
+
if (/\bcreate_audit_event\b/.test(text)) items.push("source=create_audit_event");
|
|
2466
|
+
if (/\bcreateTicketEvent\b/.test(text)) items.push("source=createTicketEvent");
|
|
2467
|
+
if (/\bcreateWebhookEventRecord\b/.test(text)) items.push("source=createWebhookEventRecord");
|
|
2468
|
+
if (/\bguest_share_activity\b/.test(text)) items.push("source=guest_share_activity");
|
|
2469
|
+
if (/\blogGuestShareActivity\b/.test(text)) items.push("source=logGuestShareActivity");
|
|
2470
|
+
if (/\bauditLog\b/.test(text)) items.push("source=auditLog");
|
|
2471
|
+
if (/\baudit_log\b/.test(text)) items.push("source=audit_log");
|
|
2472
|
+
if (/\bx-request-id\b/i.test(text)) items.push("header=x-request-id");
|
|
2473
|
+
if (/\bcf-ray\b/i.test(text)) items.push("header=cf-ray");
|
|
2474
|
+
return unique(items);
|
|
2475
|
+
}
|
|
2476
|
+
function verificationEvidenceFor(evidence) {
|
|
2477
|
+
const items = [];
|
|
2478
|
+
for (const record of evidence.packageJsons) {
|
|
2479
|
+
const scripts = record.packageJson.scripts ?? {};
|
|
2480
|
+
const isRootPackage = record.path === evidence.packageJsonPath;
|
|
2481
|
+
for (const script of ["build", "typecheck", "test", "validate"]) {
|
|
2482
|
+
if (!scripts[script]) continue;
|
|
2483
|
+
items.push(isRootPackage ? `script=${script}` : `script=${packageLabel(record)}:${script}`);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
return unique(items);
|
|
2487
|
+
}
|
|
2488
|
+
function dependencyNames(evidence) {
|
|
2489
|
+
return new Set(
|
|
2490
|
+
evidence.packageJsons.flatMap((record) => [
|
|
2491
|
+
...Object.keys(record.packageJson.dependencies ?? {}),
|
|
2492
|
+
...Object.keys(record.packageJson.devDependencies ?? {}),
|
|
2493
|
+
...Object.keys(record.packageJson.peerDependencies ?? {})
|
|
2494
|
+
])
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
function matchingSystems(repoRoot, application) {
|
|
2498
|
+
const docsRoot = topoloDocsRoot(repoRoot);
|
|
2499
|
+
const systemsRoot = path.join(docsRoot, "src", "content", "systems");
|
|
2500
|
+
if (!isDirectory(systemsRoot)) return [];
|
|
2501
|
+
const appRepoPath = `PlatformApplications/${application.appDir}`;
|
|
2502
|
+
return readdirSync(systemsRoot).filter((entry) => entry.endsWith(".json")).map((entry) => readJson(path.join(systemsRoot, entry))).filter((system) => Boolean(system)).filter((system) => {
|
|
2503
|
+
const repoPaths = [...system.repo_paths ?? [], ...system.repoPaths ?? []];
|
|
2504
|
+
return repoPaths.some(
|
|
2505
|
+
(repoPath) => repoPath === appRepoPath || repoPath.startsWith(`${appRepoPath}/`) || appRepoPath.startsWith(`${repoPath}/`)
|
|
2506
|
+
);
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
function existingDocs(repoRoot, systems) {
|
|
2510
|
+
const contentRoot = path.join(topoloDocsRoot(repoRoot), "src", "content");
|
|
2511
|
+
const docs = systems.flatMap((system) => [
|
|
2512
|
+
...system.doc_paths ?? [],
|
|
2513
|
+
...system.docPaths ?? [],
|
|
2514
|
+
...system.internal_docs ?? [],
|
|
2515
|
+
...system.internalDocs ?? []
|
|
2516
|
+
]);
|
|
2517
|
+
return unique(
|
|
2518
|
+
docs.map((docPath) => docPath.replace(/^\/+/, "")).filter((docPath) => docExists(contentRoot, docPath))
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
function topoloDocsRoot(repoRoot) {
|
|
2522
|
+
const platformDocs = path.join(repoRoot, "PlatformApplications", "TopoloDocs");
|
|
2523
|
+
if (isDirectory(platformDocs)) return platformDocs;
|
|
2524
|
+
return path.join(repoRoot, "TopoloDocs");
|
|
2525
|
+
}
|
|
2526
|
+
function docExists(contentRoot, docPath) {
|
|
2527
|
+
return [
|
|
2528
|
+
docPath,
|
|
2529
|
+
`${docPath}.mdx`,
|
|
2530
|
+
`${docPath}.md`,
|
|
2531
|
+
`${docPath}.json`
|
|
2532
|
+
].some((candidate) => isFile(path.join(contentRoot, candidate)));
|
|
2533
|
+
}
|
|
2534
|
+
function findWranglerFiles(appRoot) {
|
|
2535
|
+
if (!isDirectory(appRoot)) return [];
|
|
2536
|
+
return ["wrangler.json", "wrangler.jsonc", "wrangler.toml"].filter((file) => isFile(path.join(appRoot, file))).map((file) => relativeTopoloPath(path.join(appRoot, file)));
|
|
2537
|
+
}
|
|
2538
|
+
function readSourceIndex(root) {
|
|
2539
|
+
if (!isDirectory(root)) return { files: [], text: "" };
|
|
2540
|
+
const files = collectSourceFiles(root, 1200);
|
|
2541
|
+
const chunks = files.map((file) => {
|
|
2542
|
+
try {
|
|
2543
|
+
return readFileSync(file, "utf8");
|
|
2544
|
+
} catch {
|
|
2545
|
+
return "";
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
return {
|
|
2549
|
+
files: files.map(relativeTopoloPath),
|
|
2550
|
+
text: chunks.join("\n")
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
function collectSourceFiles(root, limit) {
|
|
2554
|
+
const results = [];
|
|
2555
|
+
const visit = (dir) => {
|
|
2556
|
+
if (results.length >= limit) return;
|
|
2557
|
+
for (const entry of sortedDirEntries(dir)) {
|
|
2558
|
+
if (results.length >= limit) return;
|
|
2559
|
+
if (entry.isDirectory()) {
|
|
2560
|
+
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
2561
|
+
visit(path.join(dir, entry.name));
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
if (!entry.isFile()) continue;
|
|
2565
|
+
const ext = path.extname(entry.name);
|
|
2566
|
+
if (SOURCE_EXTENSIONS.has(ext)) results.push(path.join(dir, entry.name));
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
visit(root);
|
|
2570
|
+
return results;
|
|
2571
|
+
}
|
|
2572
|
+
function collectPackageJsons(root) {
|
|
2573
|
+
if (!isDirectory(root)) return [];
|
|
2574
|
+
const records = [];
|
|
2575
|
+
const visit = (dir) => {
|
|
2576
|
+
for (const entry of sortedDirEntries(dir)) {
|
|
2577
|
+
const entryPath = path.join(dir, entry.name);
|
|
2578
|
+
if (entry.isDirectory()) {
|
|
2579
|
+
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
2580
|
+
visit(entryPath);
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
if (!entry.isFile() || entry.name !== "package.json") continue;
|
|
2584
|
+
const packageJson = readPackageJson(entryPath);
|
|
2585
|
+
if (packageJson) records.push({ path: entryPath, packageJson });
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
visit(root);
|
|
2589
|
+
return records;
|
|
2590
|
+
}
|
|
2591
|
+
function readPackageJson(filePath) {
|
|
2592
|
+
return readJson(filePath);
|
|
2593
|
+
}
|
|
2594
|
+
function packageLabel(record) {
|
|
2595
|
+
return record.packageJson.name ?? relativeTopoloPath(path.dirname(record.path));
|
|
2596
|
+
}
|
|
2597
|
+
function sortedDirEntries(dir) {
|
|
2598
|
+
return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
2599
|
+
}
|
|
2600
|
+
function readJson(filePath) {
|
|
2601
|
+
if (!isFile(filePath)) return null;
|
|
2602
|
+
try {
|
|
2603
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
2604
|
+
} catch {
|
|
2605
|
+
return null;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
function buildMigrationQueue(applications) {
|
|
2609
|
+
return applications.flatMap(
|
|
2610
|
+
(audit) => audit.findings.filter((finding2) => finding2.status !== "met").map((finding2) => ({
|
|
2611
|
+
applicationId: audit.application.id,
|
|
2612
|
+
applicationName: audit.application.name,
|
|
2613
|
+
requirementId: finding2.requirementId,
|
|
2614
|
+
title: finding2.title,
|
|
2615
|
+
status: finding2.status,
|
|
2616
|
+
reason: finding2.reason,
|
|
2617
|
+
nextActions: finding2.nextActions
|
|
2618
|
+
}))
|
|
2619
|
+
).sort((a, b) => {
|
|
2620
|
+
const statusOrder = statusRank2(a.status) - statusRank2(b.status);
|
|
2621
|
+
if (statusOrder !== 0) return statusOrder;
|
|
2622
|
+
const appOrder = a.applicationId.localeCompare(b.applicationId);
|
|
2623
|
+
if (appOrder !== 0) return appOrder;
|
|
2624
|
+
return a.requirementId.localeCompare(b.requirementId);
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
function scoreFindings2(findings) {
|
|
2628
|
+
const met = findings.filter((finding2) => finding2.status === "met").length;
|
|
2629
|
+
const partial = findings.filter((finding2) => finding2.status === "partial").length;
|
|
2630
|
+
const missing = findings.filter((finding2) => finding2.status === "missing").length;
|
|
2631
|
+
const needsReview = findings.filter((finding2) => finding2.status === "needs_review").length;
|
|
2632
|
+
const total = findings.length;
|
|
2633
|
+
const weighted = met + partial * 0.5;
|
|
2634
|
+
return {
|
|
2635
|
+
met,
|
|
2636
|
+
partial,
|
|
2637
|
+
missing,
|
|
2638
|
+
needsReview,
|
|
2639
|
+
total,
|
|
2640
|
+
percent: total === 0 ? 100 : Math.round(weighted / total * 100)
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
function statusRank2(status) {
|
|
2644
|
+
if (status === "missing") return 0;
|
|
2645
|
+
if (status === "partial") return 1;
|
|
2646
|
+
return 2;
|
|
2647
|
+
}
|
|
2648
|
+
function unique(items) {
|
|
2649
|
+
return Array.from(new Set(items));
|
|
2650
|
+
}
|
|
2651
|
+
function relativeTopoloPath(filePath) {
|
|
2652
|
+
const index = filePath.indexOf("PlatformApplications/");
|
|
2653
|
+
if (index >= 0) return filePath.slice(index);
|
|
2654
|
+
const docsIndex = filePath.indexOf("TopoloDocs/");
|
|
2655
|
+
if (docsIndex >= 0) return filePath.slice(docsIndex);
|
|
2656
|
+
return filePath;
|
|
2657
|
+
}
|
|
2658
|
+
function isDirectory(filePath) {
|
|
2659
|
+
try {
|
|
2660
|
+
return statSync(filePath).isDirectory();
|
|
2661
|
+
} catch {
|
|
2662
|
+
return false;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
function isFile(filePath) {
|
|
2666
|
+
try {
|
|
2667
|
+
return existsSync2(filePath) && statSync(filePath).isFile();
|
|
2668
|
+
} catch {
|
|
2669
|
+
return false;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// src/commands/app-scaffold.ts
|
|
2674
|
+
function applicationScaffoldPayload(application, options = {}) {
|
|
2675
|
+
const id = normalizeApplicationId(application);
|
|
2676
|
+
const catalogApplication = APPLICATIONS[id];
|
|
2677
|
+
const browser = options.browser !== false;
|
|
2678
|
+
const api = options.api !== false;
|
|
2679
|
+
const appDir = options.appDir ?? catalogApplication?.appDir ?? `Topolo${toPascalCase(id)}`;
|
|
2680
|
+
const name = options.name ?? catalogApplication?.name ?? `topolo-${id}`;
|
|
2681
|
+
const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${id}`;
|
|
2682
|
+
const serviceId = api ? options.service ?? catalogApplication?.services[0] ?? id : null;
|
|
2683
|
+
const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${id}.topolo.app` : null;
|
|
2684
|
+
const scopes = scaffoldScopes({ browser, api });
|
|
2685
|
+
return {
|
|
2686
|
+
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
2687
|
+
application: {
|
|
2688
|
+
id,
|
|
2689
|
+
name,
|
|
2690
|
+
appDir,
|
|
2691
|
+
packageName,
|
|
2692
|
+
productionUrl,
|
|
2693
|
+
serviceId
|
|
2694
|
+
},
|
|
2695
|
+
scopes,
|
|
2696
|
+
requirements: APPLICATION_REQUIREMENTS.filter(
|
|
2697
|
+
(requirement) => requirement.appliesTo.some((scope) => scopes.includes(scope))
|
|
2698
|
+
),
|
|
2699
|
+
packages: scaffoldPackages({ browser, api }),
|
|
2700
|
+
files: scaffoldFiles({ id, appDir, browser, api }),
|
|
2701
|
+
commands: scaffoldCommands(id),
|
|
2702
|
+
ciWorkflowPath: ".github/workflows/topolo-conformance.yml",
|
|
2703
|
+
ciWorkflow: topoloConformanceWorkflow({ id, appDir })
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
function scaffoldScopes(options) {
|
|
2707
|
+
const scopes = ["all"];
|
|
2708
|
+
if (options.browser) scopes.push("browser");
|
|
2709
|
+
if (options.api) {
|
|
2710
|
+
scopes.push("api", "agent_surface");
|
|
2711
|
+
}
|
|
2712
|
+
return scopes;
|
|
2713
|
+
}
|
|
2714
|
+
function scaffoldPackages(options) {
|
|
2715
|
+
const packages = /* @__PURE__ */ new Set();
|
|
2716
|
+
if (options.browser) {
|
|
2717
|
+
packages.add("@topolo/ui-kit");
|
|
2718
|
+
packages.add("@topolo/auth-client");
|
|
2719
|
+
}
|
|
2720
|
+
if (options.api) {
|
|
2721
|
+
packages.add("@topolo/auth-middleware");
|
|
2722
|
+
packages.add("@topolo/sdk");
|
|
2723
|
+
}
|
|
2724
|
+
return Array.from(packages);
|
|
2725
|
+
}
|
|
2726
|
+
function scaffoldFiles(options) {
|
|
2727
|
+
const base = `PlatformApplications/${options.appDir}`;
|
|
2728
|
+
const files = [
|
|
2729
|
+
{
|
|
2730
|
+
path: `${base}/package.json`,
|
|
2731
|
+
purpose: "Package metadata and verification commands.",
|
|
2732
|
+
requiredSignals: [
|
|
2733
|
+
"name matches the packageName in the SDK application catalog",
|
|
2734
|
+
"scripts include build plus typecheck or test where the stack supports them"
|
|
2735
|
+
]
|
|
2736
|
+
},
|
|
2737
|
+
{
|
|
2738
|
+
path: `${base}/topolo.cloudcontrol.json`,
|
|
2739
|
+
purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
|
|
2740
|
+
requiredSignals: [
|
|
2741
|
+
"production URLs and Cloudflare targets are explicit",
|
|
2742
|
+
"resource bindings and required secrets are declared without secret values"
|
|
2743
|
+
]
|
|
2744
|
+
},
|
|
2745
|
+
{
|
|
2746
|
+
path: `${base}/.github/workflows/topolo-conformance.yml`,
|
|
2747
|
+
purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
|
|
2748
|
+
requiredSignals: [
|
|
2749
|
+
"checks out this repo under its PlatformApplications app directory",
|
|
2750
|
+
"checks out TopoloDocs under PlatformApplications/TopoloDocs",
|
|
2751
|
+
"checks out topolo-sdk and TopoloCli from main so the gate does not wait for an npm publish",
|
|
2752
|
+
"runs topolo apps audit with --local and --fail-on needs_review"
|
|
2753
|
+
]
|
|
2754
|
+
},
|
|
2755
|
+
{
|
|
2756
|
+
path: `PlatformApplications/TopoloDocs/src/content/systems/topolo-${options.id}.json`,
|
|
2757
|
+
purpose: "Canonical system registry entry.",
|
|
2758
|
+
requiredSignals: [
|
|
2759
|
+
`repo_paths includes ${base}`,
|
|
2760
|
+
"doc_paths includes internal handbook coverage",
|
|
2761
|
+
"security_assurance records the auth, tenancy, deployment, and verification boundary"
|
|
2762
|
+
]
|
|
2763
|
+
},
|
|
2764
|
+
{
|
|
2765
|
+
path: `PlatformApplications/TopoloDocs/src/content/internal/apps/${options.id}.mdx`,
|
|
2766
|
+
purpose: "Internal operator handbook.",
|
|
2767
|
+
requiredSignals: [
|
|
2768
|
+
"documents ownership, routes, auth boundary, deployment, operations, and verification",
|
|
2769
|
+
"last_verified is updated when behavior changes"
|
|
2770
|
+
]
|
|
2771
|
+
}
|
|
2772
|
+
];
|
|
2773
|
+
if (options.browser) {
|
|
2774
|
+
files.push({
|
|
2775
|
+
path: `${base}/src/App.tsx`,
|
|
2776
|
+
purpose: "Authenticated browser surface.",
|
|
2777
|
+
requiredSignals: [
|
|
2778
|
+
"imports shared TopoloAppShell from @topolo/ui-kit",
|
|
2779
|
+
"uses @topolo/auth-client for app-origin auth flows",
|
|
2780
|
+
"keeps launcher and account-menu behavior on the shared platform surface"
|
|
2781
|
+
]
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
if (options.api) {
|
|
2785
|
+
files.push(
|
|
2786
|
+
{
|
|
2787
|
+
path: `${base}/wrangler.toml`,
|
|
2788
|
+
purpose: "Cloudflare Worker runtime configuration.",
|
|
2789
|
+
requiredSignals: [
|
|
2790
|
+
"bindings match topolo.cloudcontrol.json",
|
|
2791
|
+
"secrets are referenced by name only"
|
|
2792
|
+
]
|
|
2793
|
+
},
|
|
2794
|
+
{
|
|
2795
|
+
path: `${base}/src/worker.ts`,
|
|
2796
|
+
purpose: "Protected service boundary.",
|
|
2797
|
+
requiredSignals: [
|
|
2798
|
+
"uses @topolo/auth-middleware or Auth validation for protected routes",
|
|
2799
|
+
"derives organization from the credential rather than client-supplied orgId",
|
|
2800
|
+
"emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
|
|
2801
|
+
]
|
|
2802
|
+
}
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
return files;
|
|
2806
|
+
}
|
|
2807
|
+
function scaffoldCommands(id) {
|
|
2808
|
+
return [
|
|
2809
|
+
`topolo apps scaffold ${id} --json`,
|
|
2810
|
+
"topolo apps requirements --json",
|
|
2811
|
+
`topolo apps audit ${id} --local --repo-root /Users/ashes/Projects/topolo --fail-on needs_review --json`,
|
|
2812
|
+
`topolo apps audit ${id} --local --repo-root /Users/ashes/Projects/topolo --fail-on partial --json`
|
|
2813
|
+
];
|
|
2814
|
+
}
|
|
2815
|
+
function topoloConformanceWorkflow(options) {
|
|
2816
|
+
const docsCheckoutStep = options.appDir === "TopoloDocs" ? [] : [
|
|
2817
|
+
"",
|
|
2818
|
+
" - name: Checkout TopoloDocs",
|
|
2819
|
+
" uses: actions/checkout@v4",
|
|
2820
|
+
" with:",
|
|
2821
|
+
" repository: Topolo-io/docs",
|
|
2822
|
+
" path: PlatformApplications/TopoloDocs",
|
|
2823
|
+
" token: ${{ secrets.TOPOLO_CI_REPO_TOKEN || github.token }}"
|
|
2824
|
+
];
|
|
2825
|
+
const cliCheckoutStep = options.appDir === "TopoloCli" ? [] : [
|
|
2826
|
+
"",
|
|
2827
|
+
" - name: Checkout Topolo CLI",
|
|
2828
|
+
" uses: actions/checkout@v4",
|
|
2829
|
+
" with:",
|
|
2830
|
+
" repository: Topolo-io/TopoloCli",
|
|
2831
|
+
" path: PlatformApplications/TopoloCli",
|
|
2832
|
+
" token: ${{ secrets.TOPOLO_CI_REPO_TOKEN || github.token }}"
|
|
2833
|
+
];
|
|
2834
|
+
return [
|
|
2835
|
+
"name: Topolo App Conformance",
|
|
2836
|
+
"",
|
|
2837
|
+
"on:",
|
|
2838
|
+
" pull_request:",
|
|
2839
|
+
" push:",
|
|
2840
|
+
" branches:",
|
|
2841
|
+
" - main",
|
|
2842
|
+
" workflow_dispatch:",
|
|
2843
|
+
"",
|
|
2844
|
+
"permissions:",
|
|
2845
|
+
" contents: read",
|
|
2846
|
+
"",
|
|
2847
|
+
"jobs:",
|
|
2848
|
+
" conformance:",
|
|
2849
|
+
" runs-on: ubuntu-latest",
|
|
2850
|
+
" steps:",
|
|
2851
|
+
" - name: Checkout application",
|
|
2852
|
+
" uses: actions/checkout@v4",
|
|
2853
|
+
" with:",
|
|
2854
|
+
` path: PlatformApplications/${options.appDir}`,
|
|
2855
|
+
...docsCheckoutStep,
|
|
2856
|
+
"",
|
|
2857
|
+
" - name: Checkout Topolo SDK",
|
|
2858
|
+
" uses: actions/checkout@v4",
|
|
2859
|
+
" with:",
|
|
2860
|
+
" repository: Topolo-io/topolo-sdk",
|
|
2861
|
+
" path: PlatformApplications/packages/topolo-sdk",
|
|
2862
|
+
" token: ${{ secrets.TOPOLO_CI_REPO_TOKEN || github.token }}",
|
|
2863
|
+
...cliCheckoutStep,
|
|
2864
|
+
"",
|
|
2865
|
+
" - name: Setup Node.js",
|
|
2866
|
+
" uses: actions/setup-node@v4",
|
|
2867
|
+
" with:",
|
|
2868
|
+
" node-version: 20",
|
|
2869
|
+
"",
|
|
2870
|
+
" - name: Build Topolo SDK",
|
|
2871
|
+
" working-directory: PlatformApplications/packages/topolo-sdk",
|
|
2872
|
+
" run: |",
|
|
2873
|
+
" npm install --no-audit --no-fund",
|
|
2874
|
+
" npm run build",
|
|
2875
|
+
"",
|
|
2876
|
+
" - name: Build Topolo CLI",
|
|
2877
|
+
" working-directory: PlatformApplications/TopoloCli",
|
|
2878
|
+
" run: |",
|
|
2879
|
+
" npm install --no-audit --no-fund",
|
|
2880
|
+
" npm run build",
|
|
2881
|
+
"",
|
|
2882
|
+
" - name: Run Topolo app conformance audit",
|
|
2883
|
+
" run: |",
|
|
2884
|
+
` node PlatformApplications/TopoloCli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`
|
|
2885
|
+
].join("\n");
|
|
2886
|
+
}
|
|
2887
|
+
function normalizeApplicationId(application) {
|
|
2888
|
+
const id = application.trim().toLowerCase();
|
|
2889
|
+
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, id)) {
|
|
2890
|
+
return id;
|
|
2891
|
+
}
|
|
2892
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(id)) {
|
|
2893
|
+
throw new Error("Application id must be kebab-case using lowercase letters, numbers, and hyphens.");
|
|
2894
|
+
}
|
|
2895
|
+
return id;
|
|
2896
|
+
}
|
|
2897
|
+
function toPascalCase(value) {
|
|
2898
|
+
return value.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join("");
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
// src/commands/apps.ts
|
|
2902
|
+
var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
|
|
2903
|
+
function registerAppsCommand(program2) {
|
|
2904
|
+
const apps = program2.command("apps").description("List every Topolo platform application known to the SDK catalog").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action((opts) => {
|
|
2905
|
+
const mode = resolveOutputMode(opts.json);
|
|
2906
|
+
try {
|
|
2907
|
+
const rows = applicationRows();
|
|
2908
|
+
emit({ applications: rows }, mode, () => formatRows(rows));
|
|
2909
|
+
} catch (err) {
|
|
2910
|
+
fail(err, mode);
|
|
2911
|
+
}
|
|
2912
|
+
});
|
|
2913
|
+
apps.command("get <application>").description(`Show one Topolo application. Known applications: ${APPLICATION_IDS.join(", ")}.`).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action((application, opts) => {
|
|
2914
|
+
const mode = resolveOutputMode(opts.json);
|
|
2915
|
+
try {
|
|
2916
|
+
const app = resolveApplication(application);
|
|
2917
|
+
emit({ application: app }, mode, () => formatApplication(app));
|
|
2918
|
+
} catch (err) {
|
|
2919
|
+
fail(err, mode);
|
|
2920
|
+
}
|
|
2921
|
+
});
|
|
2922
|
+
apps.command("requirements [application]").description(
|
|
2923
|
+
"Show the versioned Topolo application-build requirements, optionally filtered to one app."
|
|
2924
|
+
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action((application, opts) => {
|
|
2925
|
+
const mode = resolveOutputMode(opts.json);
|
|
2926
|
+
try {
|
|
2927
|
+
const payload = applicationRequirementsPayload(application);
|
|
2928
|
+
emit(payload, mode, () => formatRequirements(payload));
|
|
2929
|
+
} catch (err) {
|
|
2930
|
+
fail(err, mode);
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2933
|
+
apps.command("audit [application]").description(
|
|
2934
|
+
"Audit Topolo applications against the versioned application-build requirements."
|
|
2935
|
+
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").option("--local", "Inspect the local Topolo checkout for filesystem-backed evidence").option("--repo-root <path>", "Topolo checkout root to inspect when --local is set").option(
|
|
2936
|
+
"--fail-on <status>",
|
|
2937
|
+
"Exit non-zero when audit has findings at this severity or worse: missing, needs_review, partial"
|
|
2938
|
+
).action((application, opts) => {
|
|
2939
|
+
const mode = resolveOutputMode(opts.json);
|
|
2940
|
+
try {
|
|
2941
|
+
const payload = applicationAuditPayload(application, {
|
|
2942
|
+
local: opts.local,
|
|
2943
|
+
repoRoot: opts.repoRoot
|
|
2944
|
+
});
|
|
2945
|
+
const gate = opts.failOn ? evaluateApplicationAuditGate(payload, opts.failOn) : null;
|
|
2946
|
+
const output = gate ? { ...payload, conformanceGate: gate } : payload;
|
|
2947
|
+
emit(output, mode, () => formatAudit(output));
|
|
2948
|
+
if (gate && !gate.passed) {
|
|
2949
|
+
if (mode === "json") {
|
|
2950
|
+
process.stderr.write(JSON.stringify({ error: gate.message, conformanceGate: gate }, null, 2) + "\n");
|
|
2951
|
+
} else {
|
|
2952
|
+
process.stderr.write(`Conformance gate failed: ${gate.message}
|
|
2953
|
+
`);
|
|
2954
|
+
}
|
|
2955
|
+
process.exitCode = 1;
|
|
2956
|
+
}
|
|
2957
|
+
} catch (err) {
|
|
2958
|
+
fail(err, mode);
|
|
2959
|
+
}
|
|
2960
|
+
});
|
|
2961
|
+
apps.command("scaffold <application>").description(
|
|
2962
|
+
"Generate the required Topolo new-application scaffold, including a conformance workflow."
|
|
2963
|
+
).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under PlatformApplications").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth service id for API-backed apps").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option("--workflow-only", "Output only the generated GitHub Actions workflow in human mode").action((application, opts) => {
|
|
2964
|
+
const mode = opts.json === false || process.argv.includes("--no-json") ? "pretty" : resolveOutputMode(opts.json);
|
|
2965
|
+
try {
|
|
2966
|
+
const payload = applicationScaffoldPayload(application, {
|
|
2967
|
+
appDir: opts.appDir,
|
|
2968
|
+
name: opts.name,
|
|
2969
|
+
packageName: opts.packageName,
|
|
2970
|
+
productionUrl: opts.productionUrl,
|
|
2971
|
+
service: opts.service,
|
|
2972
|
+
api: opts.api,
|
|
2973
|
+
browser: opts.browser
|
|
2974
|
+
});
|
|
2975
|
+
const output = opts.workflowOnly ? { application: payload.application, ciWorkflowPath: payload.ciWorkflowPath, ciWorkflow: payload.ciWorkflow } : payload;
|
|
2976
|
+
emit(output, mode, () => opts.workflowOnly ? payload.ciWorkflow : formatScaffold(payload));
|
|
2977
|
+
} catch (err) {
|
|
2978
|
+
fail(err, mode);
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
function applicationRows() {
|
|
2983
|
+
return APPLICATION_IDS.map((id) => {
|
|
2984
|
+
const app = APPLICATIONS[id];
|
|
2985
|
+
return {
|
|
2986
|
+
id,
|
|
2987
|
+
name: app.name,
|
|
2988
|
+
appDir: app.appDir,
|
|
2989
|
+
packageName: app.packageName,
|
|
2990
|
+
productionUrl: app.productionUrl,
|
|
2991
|
+
services: [...app.services],
|
|
2992
|
+
deployTargets: app.deployTargets.length
|
|
2993
|
+
};
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
function resolveApplication(application) {
|
|
2997
|
+
const id = application.trim().toLowerCase();
|
|
2998
|
+
if (!APPLICATION_IDS.includes(id)) {
|
|
2999
|
+
throw new Error(`Unknown application "${application}". Known applications: ${APPLICATION_IDS.join(", ")}`);
|
|
3000
|
+
}
|
|
3001
|
+
return APPLICATIONS[id];
|
|
3002
|
+
}
|
|
3003
|
+
function applicationRequirementsPayload(application) {
|
|
3004
|
+
if (!application) {
|
|
3005
|
+
return {
|
|
3006
|
+
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
3007
|
+
application: null,
|
|
3008
|
+
scopes: ["all", "browser", "api", "tooling", "agent_surface"],
|
|
3009
|
+
requirements: [...APPLICATION_REQUIREMENTS]
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
const app = resolveApplication(application);
|
|
3013
|
+
return {
|
|
3014
|
+
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
3015
|
+
application: app,
|
|
3016
|
+
scopes: applicationRequirementScopes(app),
|
|
3017
|
+
requirements: requirementsForApplication(app)
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
function applicationAuditPayload(application, options = {}) {
|
|
3021
|
+
const report = application ? auditAllApplicationRequirements([resolveApplication(application).id]) : auditAllApplicationRequirements();
|
|
3022
|
+
if (!options.local) return report;
|
|
3023
|
+
return enrichApplicationAuditWithLocalEvidence(report, { repoRoot: options.repoRoot });
|
|
3024
|
+
}
|
|
3025
|
+
function evaluateApplicationAuditGate(payload, failOn) {
|
|
3026
|
+
const threshold = normalizeFailOn(failOn);
|
|
3027
|
+
const counts = {
|
|
3028
|
+
met: 0,
|
|
3029
|
+
partial: 0,
|
|
3030
|
+
missing: 0,
|
|
3031
|
+
needs_review: 0
|
|
3032
|
+
};
|
|
3033
|
+
for (const audit of payload.applications) {
|
|
3034
|
+
for (const finding2 of audit.findings) {
|
|
3035
|
+
counts[finding2.status] += 1;
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
const failingStatuses = statusesAtOrWorse(threshold);
|
|
3039
|
+
const failingFindings = failingStatuses.reduce((total, status) => total + counts[status], 0);
|
|
3040
|
+
const passed = failingFindings === 0;
|
|
3041
|
+
return {
|
|
3042
|
+
threshold,
|
|
3043
|
+
passed,
|
|
3044
|
+
message: passed ? `No findings at or above ${threshold}.` : `${failingFindings} finding(s) at or above ${threshold}.`,
|
|
3045
|
+
failingFindings,
|
|
3046
|
+
counts
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
function formatRows(rows) {
|
|
3050
|
+
const idWidth = Math.max(2, ...rows.map((r) => r.id.length));
|
|
3051
|
+
const nameWidth = Math.max(4, ...rows.map((r) => r.name.length));
|
|
3052
|
+
const urlWidth = Math.max(3, ...rows.map((r) => (r.productionUrl ?? "-").length));
|
|
3053
|
+
const servicesWidth = Math.max(
|
|
3054
|
+
8,
|
|
3055
|
+
...rows.map((r) => r.services.length ? r.services.join(",").length : 1)
|
|
3056
|
+
);
|
|
3057
|
+
const lines = [];
|
|
3058
|
+
lines.push(
|
|
3059
|
+
`${"ID".padEnd(idWidth)} ${"NAME".padEnd(nameWidth)} ${"URL".padEnd(urlWidth)} ${"SERVICES".padEnd(servicesWidth)} TARGETS`
|
|
3060
|
+
);
|
|
3061
|
+
lines.push(
|
|
3062
|
+
`${"-".repeat(idWidth)} ${"-".repeat(nameWidth)} ${"-".repeat(urlWidth)} ${"-".repeat(servicesWidth)} -------`
|
|
3063
|
+
);
|
|
3064
|
+
for (const r of rows) {
|
|
3065
|
+
const services = r.services.length ? r.services.join(",") : "-";
|
|
3066
|
+
lines.push(
|
|
3067
|
+
`${r.id.padEnd(idWidth)} ${r.name.padEnd(nameWidth)} ${(r.productionUrl ?? "-").padEnd(urlWidth)} ${services.padEnd(servicesWidth)} ${r.deployTargets}`
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3070
|
+
return lines.join("\n");
|
|
3071
|
+
}
|
|
3072
|
+
function formatApplication(application) {
|
|
3073
|
+
const lines = [
|
|
3074
|
+
`ID: ${application.id}`,
|
|
3075
|
+
`Name: ${application.name}`,
|
|
3076
|
+
`Directory: ${application.appDir}`,
|
|
3077
|
+
`Package: ${application.packageName ?? "-"}`,
|
|
3078
|
+
`URL: ${application.productionUrl ?? "-"}`,
|
|
3079
|
+
`Services: ${application.services.length ? application.services.join(", ") : "-"}`,
|
|
3080
|
+
"Targets:"
|
|
3081
|
+
];
|
|
3082
|
+
if (application.deployTargets.length === 0) {
|
|
3083
|
+
lines.push(" - none");
|
|
3084
|
+
} else {
|
|
3085
|
+
for (const target of application.deployTargets) {
|
|
3086
|
+
const parts = [`${target.name} (${target.kind})`];
|
|
3087
|
+
if (target.serviceId) parts.push(`service=${target.serviceId}`);
|
|
3088
|
+
if (target.apiUrl) parts.push(`api=${target.apiUrl}`);
|
|
3089
|
+
lines.push(` - ${parts.join(" ")}`);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
return lines.join("\n");
|
|
3093
|
+
}
|
|
3094
|
+
function formatRequirements(payload) {
|
|
3095
|
+
const lines = [
|
|
3096
|
+
`Version: ${payload.version}`,
|
|
3097
|
+
`Application: ${payload.application?.id ?? "all"}`,
|
|
3098
|
+
`Scopes: ${payload.scopes.join(", ")}`,
|
|
3099
|
+
"Requirements:"
|
|
3100
|
+
];
|
|
3101
|
+
for (const requirement of payload.requirements) {
|
|
3102
|
+
lines.push(` - ${requirement.id}: ${requirement.title}`);
|
|
3103
|
+
lines.push(` ${requirement.summary}`);
|
|
3104
|
+
}
|
|
3105
|
+
return lines.join("\n");
|
|
3106
|
+
}
|
|
3107
|
+
function formatAudit(payload) {
|
|
3108
|
+
const rows = payload.applications.map((audit) => ({
|
|
3109
|
+
id: audit.application.id,
|
|
3110
|
+
score: `${audit.score.percent}%`,
|
|
3111
|
+
met: audit.score.met,
|
|
3112
|
+
partial: audit.score.partial,
|
|
3113
|
+
missing: audit.score.missing,
|
|
3114
|
+
review: audit.score.needsReview
|
|
3115
|
+
}));
|
|
3116
|
+
const idWidth = Math.max(2, ...rows.map((row) => row.id.length));
|
|
3117
|
+
const lines = [
|
|
3118
|
+
`Version: ${payload.version}`,
|
|
3119
|
+
`${"ID".padEnd(idWidth)} SCORE MET PARTIAL MISSING REVIEW`,
|
|
3120
|
+
`${"-".repeat(idWidth)} ----- --- ------- ------- ------`
|
|
3121
|
+
];
|
|
3122
|
+
for (const row of rows) {
|
|
3123
|
+
lines.push(
|
|
3124
|
+
`${row.id.padEnd(idWidth)} ${row.score.padEnd(5)} ${String(row.met).padStart(3)} ${String(row.partial).padStart(7)} ${String(row.missing).padStart(7)} ${String(row.review).padStart(6)}`
|
|
3125
|
+
);
|
|
3126
|
+
}
|
|
3127
|
+
lines.push(`Migration queue: ${payload.migrationQueue.length}`);
|
|
3128
|
+
if ("localEvidence" in payload && payload.localEvidence) {
|
|
3129
|
+
lines.push(`Local evidence: ${payload.localEvidence.repoRoot}`);
|
|
3130
|
+
}
|
|
3131
|
+
if ("conformanceGate" in payload && payload.conformanceGate) {
|
|
3132
|
+
const gate = payload.conformanceGate;
|
|
3133
|
+
lines.push(`Gate: ${gate.passed ? "pass" : "fail"} (${gate.message})`);
|
|
3134
|
+
}
|
|
3135
|
+
return lines.join("\n");
|
|
3136
|
+
}
|
|
3137
|
+
function formatScaffold(payload) {
|
|
3138
|
+
const lines = [
|
|
3139
|
+
`Version: ${payload.version}`,
|
|
3140
|
+
`Application: ${payload.application.id}`,
|
|
3141
|
+
`Directory: ${payload.application.appDir}`,
|
|
3142
|
+
`Package: ${payload.application.packageName}`,
|
|
3143
|
+
`URL: ${payload.application.productionUrl ?? "-"}`,
|
|
3144
|
+
`Service: ${payload.application.serviceId ?? "-"}`,
|
|
3145
|
+
`Scopes: ${payload.scopes.join(", ")}`,
|
|
3146
|
+
"Packages:"
|
|
3147
|
+
];
|
|
3148
|
+
for (const packageName of payload.packages) {
|
|
3149
|
+
lines.push(` - ${packageName}`);
|
|
3150
|
+
}
|
|
3151
|
+
lines.push("Files:");
|
|
3152
|
+
for (const file of payload.files) {
|
|
3153
|
+
lines.push(` - ${file.path}`);
|
|
3154
|
+
lines.push(` ${file.purpose}`);
|
|
3155
|
+
}
|
|
3156
|
+
lines.push("Commands:");
|
|
3157
|
+
for (const command of payload.commands) {
|
|
3158
|
+
lines.push(` - ${command}`);
|
|
3159
|
+
}
|
|
3160
|
+
lines.push(`CI workflow: ${payload.ciWorkflowPath}`);
|
|
3161
|
+
return lines.join("\n");
|
|
3162
|
+
}
|
|
3163
|
+
function normalizeFailOn(value) {
|
|
3164
|
+
const normalized = value.trim().toLowerCase().replace(/-/g, "_");
|
|
3165
|
+
if (normalized === "missing" || normalized === "needs_review" || normalized === "partial") {
|
|
3166
|
+
return normalized;
|
|
3167
|
+
}
|
|
3168
|
+
throw new Error("Invalid --fail-on value. Use missing, needs_review, or partial.");
|
|
3169
|
+
}
|
|
3170
|
+
function statusesAtOrWorse(threshold) {
|
|
3171
|
+
if (threshold === "missing") return ["missing"];
|
|
3172
|
+
if (threshold === "needs_review") return ["missing", "needs_review"];
|
|
3173
|
+
return ["missing", "needs_review", "partial"];
|
|
3174
|
+
}
|
|
3175
|
+
|
|
982
3176
|
// src/commands/services.ts
|
|
983
3177
|
function registerServicesCommand(program2) {
|
|
984
3178
|
program2.command("services").description("List every Topolo service the SDK can route to").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action((opts) => {
|
|
@@ -994,13 +3188,13 @@ function registerServicesCommand(program2) {
|
|
|
994
3188
|
overridden: defaultUrl !== effectiveUrl
|
|
995
3189
|
};
|
|
996
3190
|
});
|
|
997
|
-
emit({ services: rows }, mode, () =>
|
|
3191
|
+
emit({ services: rows }, mode, () => formatRows2(rows));
|
|
998
3192
|
} catch (err) {
|
|
999
3193
|
fail(err, mode);
|
|
1000
3194
|
}
|
|
1001
3195
|
});
|
|
1002
3196
|
}
|
|
1003
|
-
function
|
|
3197
|
+
function formatRows2(rows) {
|
|
1004
3198
|
const idWidth = Math.max(3, ...rows.map((r) => r.id.length));
|
|
1005
3199
|
const urlWidth = Math.max(7, ...rows.map((r) => r.effectiveUrl.length));
|
|
1006
3200
|
const lines = [];
|
|
@@ -1023,6 +3217,7 @@ registerAuthCommands(program);
|
|
|
1023
3217
|
registerWhoamiCommand(program);
|
|
1024
3218
|
registerCrmCommands(program);
|
|
1025
3219
|
registerApiCommand(program);
|
|
3220
|
+
registerAppsCommand(program);
|
|
1026
3221
|
registerServicesCommand(program);
|
|
1027
3222
|
program.parseAsync(process.argv).catch((err) => {
|
|
1028
3223
|
process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
|