clearotron 0.3.0-beta.1 → 0.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -2
- package/CONTRIBUTING.md +1 -1
- package/INSTALL.md +12 -10
- package/README.md +3 -2
- package/bin/brandowner.mjs +94 -1
- package/bin/onboard.mjs +156 -55
- package/bin/passphrase.mjs +23 -4
- package/bin/start.mjs +177 -38
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +5 -2
- package/docs/architecture/05-config-governance.md +3 -2
- package/driver/CHANGELOG.md +95 -0
- package/driver/demo-posture.mjs +59 -8
- package/driver/driver.config.mjs +36 -7
- package/driver/engine/child-record.mjs +93 -0
- package/driver/findings-model.mjs +6 -1
- package/driver/knockout-assess-record.mjs +6 -3
- package/driver/package.json +1 -1
- package/driver/portal-local-auth.mjs +98 -3
- package/driver/portal-service.mjs +65 -29
- package/driver/publish/knockout.mjs +12 -1
- package/driver/publish/office-record-links.mjs +61 -10
- package/driver/publish/render-knockout.mjs +31 -11
- package/driver/publish/xlsx.mjs +33 -1
- package/driver/register-records.mjs +6 -0
- package/driver/run-requirements.mjs +24 -1
- package/driver/runner.mjs +10 -5
- package/driver/skills/knockout-assess/SKILL.md +1 -1
- package/driver/suite-census.json +174 -30
- package/driver/unit-inventory.mjs +109 -5
- package/driver/updater-identity.mjs +178 -0
- package/driver/usage-ledger.mjs +5 -5
- package/mcp-server/CHANGELOG.md +6 -0
- package/mcp-server/http-server.mjs +16 -13
- package/mcp-server/lib/driver.mjs +7 -0
- package/mcp-server/lib/knockout.mjs +14 -2
- package/mcp-server/lib/ops.mjs +38 -16
- package/mcp-server/package.json +2 -2
- package/mcp-server/server.mjs +1 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-CWTHP0sH.js → index-CcFjgM78.js} +32 -17
- package/portal-ui/dist/assets/{index-KpytsmNH.css → index-CsCuPshD.css} +7 -2
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +3 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +2 -2
- package/scripts/changelog-plain-language.mjs +7 -30
- package/scripts/e2e.mjs +13 -2
- package/scripts/env-audit.mjs +10 -3
- package/scripts/env-classify.mjs +205 -12
- package/scripts/live-surface-check.mjs +74 -2
- package/scripts/plain-language-rules.mjs +103 -0
- package/scripts/release-note-required.mjs +118 -24
- package/scripts/release-notes-lint.mjs +22 -43
- package/scripts/release-version.mjs +59 -0
- package/scripts/revisit-render-check.mjs +6 -2
- package/scripts/text-difference.mjs +22 -0
- package/shared/env-local.mjs +25 -2
- package/shared/names-in-force.mjs +1 -0
- package/shared/reap-on-exit.mjs +27 -14
- package/shared/store-in-repo.mjs +147 -0
- package/shared/withheld-paths-access.mjs +6 -6
- package/shared/yes-no-echo.mjs +35 -0
package/driver/suite-census.json
CHANGED
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"todos": 0
|
|
65
65
|
},
|
|
66
66
|
"a-browser-run-gets-its-own-temp-root.test.mjs": {
|
|
67
|
-
"tests":
|
|
68
|
-
"asserts":
|
|
67
|
+
"tests": 12,
|
|
68
|
+
"asserts": 29,
|
|
69
69
|
"skips": 0,
|
|
70
70
|
"todos": 0
|
|
71
71
|
},
|
|
@@ -106,9 +106,9 @@
|
|
|
106
106
|
"todos": 0
|
|
107
107
|
},
|
|
108
108
|
"a-change-a-reader-can-see-arrives-with-something-to-read.test.mjs": {
|
|
109
|
-
"tests":
|
|
110
|
-
"asserts":
|
|
111
|
-
"skips":
|
|
109
|
+
"tests": 19,
|
|
110
|
+
"asserts": 45,
|
|
111
|
+
"skips": 1,
|
|
112
112
|
"todos": 0
|
|
113
113
|
},
|
|
114
114
|
"a-check-does-not-read-another-accounts-processes.test.mjs": {
|
|
@@ -135,6 +135,12 @@
|
|
|
135
135
|
"skips": 0,
|
|
136
136
|
"todos": 0
|
|
137
137
|
},
|
|
138
|
+
"a-company-can-be-given-a-framework-after-it-exists.test.mjs": {
|
|
139
|
+
"tests": 8,
|
|
140
|
+
"asserts": 19,
|
|
141
|
+
"skips": 0,
|
|
142
|
+
"todos": 0
|
|
143
|
+
},
|
|
138
144
|
"a-company-created-after-boot-can-still-search.test.mjs": {
|
|
139
145
|
"tests": 3,
|
|
140
146
|
"asserts": 10,
|
|
@@ -165,6 +171,12 @@
|
|
|
165
171
|
"skips": 0,
|
|
166
172
|
"todos": 0
|
|
167
173
|
},
|
|
174
|
+
"a-cut-refuses-a-version-already-out.test.mjs": {
|
|
175
|
+
"tests": 4,
|
|
176
|
+
"asserts": 14,
|
|
177
|
+
"skips": 0,
|
|
178
|
+
"todos": 0
|
|
179
|
+
},
|
|
168
180
|
"a-declared-non-knob-is-not-a-deletion-candidate.test.mjs": {
|
|
169
181
|
"tests": 4,
|
|
170
182
|
"asserts": 7,
|
|
@@ -202,8 +214,14 @@
|
|
|
202
214
|
"todos": 0
|
|
203
215
|
},
|
|
204
216
|
"a-demo-says-what-it-is-not-what-it-lacks.test.mjs": {
|
|
205
|
-
"tests":
|
|
206
|
-
"asserts":
|
|
217
|
+
"tests": 5,
|
|
218
|
+
"asserts": 32,
|
|
219
|
+
"skips": 0,
|
|
220
|
+
"todos": 0
|
|
221
|
+
},
|
|
222
|
+
"a-depth-check-that-read-nothing-is-on-the-investigate-list.test.mjs": {
|
|
223
|
+
"tests": 3,
|
|
224
|
+
"asserts": 8,
|
|
207
225
|
"skips": 0,
|
|
208
226
|
"todos": 0
|
|
209
227
|
},
|
|
@@ -339,12 +357,30 @@
|
|
|
339
357
|
"skips": 0,
|
|
340
358
|
"todos": 0
|
|
341
359
|
},
|
|
360
|
+
"a-key-pasted-at-a-yes-no-prompt-is-taken-and-not-shown.test.mjs": {
|
|
361
|
+
"tests": 6,
|
|
362
|
+
"asserts": 31,
|
|
363
|
+
"skips": 0,
|
|
364
|
+
"todos": 0
|
|
365
|
+
},
|
|
342
366
|
"a-killed-browser-takes-its-children.test.mjs": {
|
|
343
367
|
"tests": 2,
|
|
344
368
|
"asserts": 4,
|
|
345
369
|
"skips": 0,
|
|
346
370
|
"todos": 0
|
|
347
371
|
},
|
|
372
|
+
"a-knockout-filing-links-to-its-office.test.mjs": {
|
|
373
|
+
"tests": 12,
|
|
374
|
+
"asserts": 56,
|
|
375
|
+
"skips": 0,
|
|
376
|
+
"todos": 0
|
|
377
|
+
},
|
|
378
|
+
"a-knockout-finding-carries-every-key-its-doctrine-teaches.test.mjs": {
|
|
379
|
+
"tests": 7,
|
|
380
|
+
"asserts": 17,
|
|
381
|
+
"skips": 0,
|
|
382
|
+
"todos": 0
|
|
383
|
+
},
|
|
348
384
|
"a-mark-name-cannot-lie-about-how-it-reads.test.mjs": {
|
|
349
385
|
"tests": 9,
|
|
350
386
|
"asserts": 37,
|
|
@@ -381,12 +417,36 @@
|
|
|
381
417
|
"skips": 0,
|
|
382
418
|
"todos": 0
|
|
383
419
|
},
|
|
420
|
+
"a-name-documented-in-any-example-file-is-documented.test.mjs": {
|
|
421
|
+
"tests": 7,
|
|
422
|
+
"asserts": 16,
|
|
423
|
+
"skips": 1,
|
|
424
|
+
"todos": 0
|
|
425
|
+
},
|
|
426
|
+
"a-name-the-wizard-returns-is-a-setup-name.test.mjs": {
|
|
427
|
+
"tests": 5,
|
|
428
|
+
"asserts": 9,
|
|
429
|
+
"skips": 0,
|
|
430
|
+
"todos": 0
|
|
431
|
+
},
|
|
384
432
|
"a-new-citation-carries-something-that-can-be-checked.test.mjs": {
|
|
385
433
|
"tests": 7,
|
|
386
434
|
"asserts": 15,
|
|
387
435
|
"skips": 0,
|
|
388
436
|
"todos": 0
|
|
389
437
|
},
|
|
438
|
+
"a-new-install-signs-in-with-its-own-passphrase.test.mjs": {
|
|
439
|
+
"tests": 9,
|
|
440
|
+
"asserts": 47,
|
|
441
|
+
"skips": 1,
|
|
442
|
+
"todos": 0
|
|
443
|
+
},
|
|
444
|
+
"a-note-the-release-refuses-is-refused-on-its-own-pull-request.test.mjs": {
|
|
445
|
+
"tests": 2,
|
|
446
|
+
"asserts": 3,
|
|
447
|
+
"skips": 1,
|
|
448
|
+
"todos": 0
|
|
449
|
+
},
|
|
390
450
|
"a-packaged-install-resolves-every-workspace-dependency.test.mjs": {
|
|
391
451
|
"tests": 2,
|
|
392
452
|
"asserts": 7,
|
|
@@ -465,6 +525,12 @@
|
|
|
465
525
|
"skips": 0,
|
|
466
526
|
"todos": 0
|
|
467
527
|
},
|
|
528
|
+
"a-real-dispatch-records-the-tool-that-served-it.test.mjs": {
|
|
529
|
+
"tests": 1,
|
|
530
|
+
"asserts": 5,
|
|
531
|
+
"skips": 0,
|
|
532
|
+
"todos": 0
|
|
533
|
+
},
|
|
468
534
|
"a-real-install-starts-with-an-empty-archive.test.mjs": {
|
|
469
535
|
"tests": 6,
|
|
470
536
|
"asserts": 16,
|
|
@@ -657,6 +723,12 @@
|
|
|
657
723
|
"skips": 0,
|
|
658
724
|
"todos": 0
|
|
659
725
|
},
|
|
726
|
+
"a-stale-classification-names-the-rows-that-moved.test.mjs": {
|
|
727
|
+
"tests": 6,
|
|
728
|
+
"asserts": 24,
|
|
729
|
+
"skips": 0,
|
|
730
|
+
"todos": 0
|
|
731
|
+
},
|
|
660
732
|
"a-stop-is-read-once-more-before-anything-is-published.test.mjs": {
|
|
661
733
|
"tests": 4,
|
|
662
734
|
"asserts": 12,
|
|
@@ -669,6 +741,12 @@
|
|
|
669
741
|
"skips": 1,
|
|
670
742
|
"todos": 0
|
|
671
743
|
},
|
|
744
|
+
"a-store-in-a-pushed-checkout-is-told-its-saves-will-be-published.test.mjs": {
|
|
745
|
+
"tests": 9,
|
|
746
|
+
"asserts": 37,
|
|
747
|
+
"skips": 0,
|
|
748
|
+
"todos": 0
|
|
749
|
+
},
|
|
672
750
|
"a-stored-territory-that-does-nothing-is-named.test.mjs": {
|
|
673
751
|
"tests": 6,
|
|
674
752
|
"asserts": 12,
|
|
@@ -837,6 +915,18 @@
|
|
|
837
915
|
"skips": 0,
|
|
838
916
|
"todos": 0
|
|
839
917
|
},
|
|
918
|
+
"an-empty-config-store-overrides-nothing.test.mjs": {
|
|
919
|
+
"tests": 5,
|
|
920
|
+
"asserts": 29,
|
|
921
|
+
"skips": 0,
|
|
922
|
+
"todos": 0
|
|
923
|
+
},
|
|
924
|
+
"an-environment-read-through-optional-chaining-is-a-read.test.mjs": {
|
|
925
|
+
"tests": 6,
|
|
926
|
+
"asserts": 19,
|
|
927
|
+
"skips": 0,
|
|
928
|
+
"todos": 0
|
|
929
|
+
},
|
|
840
930
|
"an-evidence-demand-that-cannot-be-met.test.mjs": {
|
|
841
931
|
"tests": 9,
|
|
842
932
|
"asserts": 21,
|
|
@@ -2704,8 +2794,8 @@
|
|
|
2704
2794
|
"todos": 0
|
|
2705
2795
|
},
|
|
2706
2796
|
"one-node-floor-read-in-one-place.test.mjs": {
|
|
2707
|
-
"tests":
|
|
2708
|
-
"asserts":
|
|
2797
|
+
"tests": 8,
|
|
2798
|
+
"asserts": 26,
|
|
2709
2799
|
"skips": 0,
|
|
2710
2800
|
"todos": 0
|
|
2711
2801
|
},
|
|
@@ -2951,7 +3041,7 @@
|
|
|
2951
3041
|
},
|
|
2952
3042
|
"portal-run-control.test.mjs": {
|
|
2953
3043
|
"tests": 16,
|
|
2954
|
-
"asserts":
|
|
3044
|
+
"asserts": 49,
|
|
2955
3045
|
"skips": 0,
|
|
2956
3046
|
"todos": 0
|
|
2957
3047
|
},
|
|
@@ -3359,7 +3449,7 @@
|
|
|
3359
3449
|
},
|
|
3360
3450
|
"register-count.test.mjs": {
|
|
3361
3451
|
"tests": 20,
|
|
3362
|
-
"asserts":
|
|
3452
|
+
"asserts": 153,
|
|
3363
3453
|
"skips": 0,
|
|
3364
3454
|
"todos": 0
|
|
3365
3455
|
},
|
|
@@ -4084,8 +4174,8 @@
|
|
|
4084
4174
|
"todos": 0
|
|
4085
4175
|
},
|
|
4086
4176
|
"start-command.test.mjs": {
|
|
4087
|
-
"tests":
|
|
4088
|
-
"asserts":
|
|
4177
|
+
"tests": 27,
|
|
4178
|
+
"asserts": 120,
|
|
4089
4179
|
"skips": 0,
|
|
4090
4180
|
"todos": 0
|
|
4091
4181
|
},
|
|
@@ -4115,7 +4205,7 @@
|
|
|
4115
4205
|
},
|
|
4116
4206
|
"stop-is-a-stop.test.mjs": {
|
|
4117
4207
|
"tests": 6,
|
|
4118
|
-
"asserts":
|
|
4208
|
+
"asserts": 25,
|
|
4119
4209
|
"skips": 0,
|
|
4120
4210
|
"todos": 0
|
|
4121
4211
|
},
|
|
@@ -4371,6 +4461,12 @@
|
|
|
4371
4461
|
"skips": 0,
|
|
4372
4462
|
"todos": 0
|
|
4373
4463
|
},
|
|
4464
|
+
"the-demo-is-its-own-install.test.mjs": {
|
|
4465
|
+
"tests": 8,
|
|
4466
|
+
"asserts": 61,
|
|
4467
|
+
"skips": 0,
|
|
4468
|
+
"todos": 0
|
|
4469
|
+
},
|
|
4374
4470
|
"the-door-answers-the-address-it-advertises.test.mjs": {
|
|
4375
4471
|
"tests": 9,
|
|
4376
4472
|
"asserts": 37,
|
|
@@ -4498,14 +4594,14 @@
|
|
|
4498
4594
|
"todos": 0
|
|
4499
4595
|
},
|
|
4500
4596
|
"the-inventory-names-the-units-this-box-runs.test.mjs": {
|
|
4501
|
-
"tests":
|
|
4502
|
-
"asserts":
|
|
4597
|
+
"tests": 21,
|
|
4598
|
+
"asserts": 56,
|
|
4503
4599
|
"skips": 0,
|
|
4504
4600
|
"todos": 0
|
|
4505
4601
|
},
|
|
4506
4602
|
"the-knockout-page-leads-with-the-read.test.mjs": {
|
|
4507
4603
|
"tests": 28,
|
|
4508
|
-
"asserts":
|
|
4604
|
+
"asserts": 69,
|
|
4509
4605
|
"skips": 0,
|
|
4510
4606
|
"todos": 0
|
|
4511
4607
|
},
|
|
@@ -4571,19 +4667,19 @@
|
|
|
4571
4667
|
},
|
|
4572
4668
|
"the-passphrase-is-in-the-block-not-the-log.test.mjs": {
|
|
4573
4669
|
"tests": 6,
|
|
4574
|
-
"asserts":
|
|
4670
|
+
"asserts": 19,
|
|
4575
4671
|
"skips": 0,
|
|
4576
4672
|
"todos": 0
|
|
4577
4673
|
},
|
|
4578
4674
|
"the-passphrase-line-tells-the-truth-on-a-first-start.test.mjs": {
|
|
4579
4675
|
"tests": 2,
|
|
4580
|
-
"asserts":
|
|
4676
|
+
"asserts": 12,
|
|
4581
4677
|
"skips": 0,
|
|
4582
4678
|
"todos": 0
|
|
4583
4679
|
},
|
|
4584
4680
|
"the-person-who-installs-is-a-person-not-a-domain.test.mjs": {
|
|
4585
|
-
"tests":
|
|
4586
|
-
"asserts":
|
|
4681
|
+
"tests": 18,
|
|
4682
|
+
"asserts": 86,
|
|
4587
4683
|
"skips": 0,
|
|
4588
4684
|
"todos": 0
|
|
4589
4685
|
},
|
|
@@ -4625,7 +4721,7 @@
|
|
|
4625
4721
|
},
|
|
4626
4722
|
"the-public-residue-is-a-floor.test.mjs": {
|
|
4627
4723
|
"tests": 15,
|
|
4628
|
-
"asserts":
|
|
4724
|
+
"asserts": 59,
|
|
4629
4725
|
"skips": 5,
|
|
4630
4726
|
"todos": 0
|
|
4631
4727
|
},
|
|
@@ -4647,6 +4743,12 @@
|
|
|
4647
4743
|
"skips": 0,
|
|
4648
4744
|
"todos": 0
|
|
4649
4745
|
},
|
|
4746
|
+
"the-reaper-signals-before-it-removes.test.mjs": {
|
|
4747
|
+
"tests": 2,
|
|
4748
|
+
"asserts": 6,
|
|
4749
|
+
"skips": 0,
|
|
4750
|
+
"todos": 0
|
|
4751
|
+
},
|
|
4650
4752
|
"the-record-cannot-contradict-itself.test.mjs": {
|
|
4651
4753
|
"tests": 12,
|
|
4652
4754
|
"asserts": 48,
|
|
@@ -4665,6 +4767,12 @@
|
|
|
4665
4767
|
"skips": 4,
|
|
4666
4768
|
"todos": 0
|
|
4667
4769
|
},
|
|
4770
|
+
"the-register-probe-reads-the-install-env-file.test.mjs": {
|
|
4771
|
+
"tests": 3,
|
|
4772
|
+
"asserts": 7,
|
|
4773
|
+
"skips": 0,
|
|
4774
|
+
"todos": 0
|
|
4775
|
+
},
|
|
4668
4776
|
"the-repair-order-and-the-tool-schema-agree.test.mjs": {
|
|
4669
4777
|
"tests": 6,
|
|
4670
4778
|
"asserts": 27,
|
|
@@ -4695,6 +4803,12 @@
|
|
|
4695
4803
|
"skips": 0,
|
|
4696
4804
|
"todos": 0
|
|
4697
4805
|
},
|
|
4806
|
+
"the-revisit-check-says-where-two-texts-differ.test.mjs": {
|
|
4807
|
+
"tests": 5,
|
|
4808
|
+
"asserts": 13,
|
|
4809
|
+
"skips": 0,
|
|
4810
|
+
"todos": 0
|
|
4811
|
+
},
|
|
4698
4812
|
"the-root-strip-rule-survives-and-can-only-widen.test.mjs": {
|
|
4699
4813
|
"tests": 5,
|
|
4700
4814
|
"asserts": 21,
|
|
@@ -4797,6 +4911,12 @@
|
|
|
4797
4911
|
"skips": 0,
|
|
4798
4912
|
"todos": 0
|
|
4799
4913
|
},
|
|
4914
|
+
"the-updater-that-deploys-this-box-says-what-it-is.test.mjs": {
|
|
4915
|
+
"tests": 18,
|
|
4916
|
+
"asserts": 59,
|
|
4917
|
+
"skips": 0,
|
|
4918
|
+
"todos": 0
|
|
4919
|
+
},
|
|
4800
4920
|
"the-verb-table-resolves-to-files-that-ship.test.mjs": {
|
|
4801
4921
|
"tests": 6,
|
|
4802
4922
|
"asserts": 12,
|
|
@@ -5086,8 +5206,14 @@
|
|
|
5086
5206
|
"todos": 0
|
|
5087
5207
|
},
|
|
5088
5208
|
"what-doctor-passes-a-search-accepts.test.mjs": {
|
|
5089
|
-
"tests":
|
|
5090
|
-
"asserts":
|
|
5209
|
+
"tests": 12,
|
|
5210
|
+
"asserts": 39,
|
|
5211
|
+
"skips": 0,
|
|
5212
|
+
"todos": 0
|
|
5213
|
+
},
|
|
5214
|
+
"what-was-searched-is-written-in-the-readers-words.test.mjs": {
|
|
5215
|
+
"tests": 6,
|
|
5216
|
+
"asserts": 26,
|
|
5091
5217
|
"skips": 0,
|
|
5092
5218
|
"todos": 0
|
|
5093
5219
|
},
|
|
@@ -5355,8 +5481,8 @@
|
|
|
5355
5481
|
"todos": 0
|
|
5356
5482
|
},
|
|
5357
5483
|
"ops.test.mjs": {
|
|
5358
|
-
"tests":
|
|
5359
|
-
"asserts":
|
|
5484
|
+
"tests": 38,
|
|
5485
|
+
"asserts": 161,
|
|
5360
5486
|
"skips": 0,
|
|
5361
5487
|
"todos": 0
|
|
5362
5488
|
},
|
|
@@ -5432,6 +5558,12 @@
|
|
|
5432
5558
|
"skips": 0,
|
|
5433
5559
|
"todos": 0
|
|
5434
5560
|
},
|
|
5561
|
+
"stop-now-ends-the-step.test.mjs": {
|
|
5562
|
+
"tests": 10,
|
|
5563
|
+
"asserts": 44,
|
|
5564
|
+
"skips": 0,
|
|
5565
|
+
"todos": 0
|
|
5566
|
+
},
|
|
5435
5567
|
"stop-run-names-who-stopped-it.test.mjs": {
|
|
5436
5568
|
"tests": 6,
|
|
5437
5569
|
"asserts": 11,
|
|
@@ -5467,6 +5599,12 @@
|
|
|
5467
5599
|
"skips": 0,
|
|
5468
5600
|
"todos": 0
|
|
5469
5601
|
},
|
|
5602
|
+
"clearancesKeepsItsControls.test.ts": {
|
|
5603
|
+
"tests": 4,
|
|
5604
|
+
"asserts": 8,
|
|
5605
|
+
"skips": 0,
|
|
5606
|
+
"todos": 0
|
|
5607
|
+
},
|
|
5470
5608
|
"companyCreated.test.ts": {
|
|
5471
5609
|
"tests": 5,
|
|
5472
5610
|
"asserts": 16,
|
|
@@ -5480,8 +5618,8 @@
|
|
|
5480
5618
|
"todos": 0
|
|
5481
5619
|
},
|
|
5482
5620
|
"companyOrder.test.ts": {
|
|
5483
|
-
"tests":
|
|
5484
|
-
"asserts":
|
|
5621
|
+
"tests": 12,
|
|
5622
|
+
"asserts": 38,
|
|
5485
5623
|
"skips": 0,
|
|
5486
5624
|
"todos": 0
|
|
5487
5625
|
},
|
|
@@ -5648,8 +5786,8 @@
|
|
|
5648
5786
|
"todos": 0
|
|
5649
5787
|
},
|
|
5650
5788
|
"ownerNames.test.ts": {
|
|
5651
|
-
"tests":
|
|
5652
|
-
"asserts":
|
|
5789
|
+
"tests": 9,
|
|
5790
|
+
"asserts": 23,
|
|
5653
5791
|
"skips": 0,
|
|
5654
5792
|
"todos": 0
|
|
5655
5793
|
},
|
|
@@ -5755,6 +5893,12 @@
|
|
|
5755
5893
|
"skips": 0,
|
|
5756
5894
|
"todos": 0
|
|
5757
5895
|
},
|
|
5896
|
+
"theStopNoticeHasItsOwnRow.test.ts": {
|
|
5897
|
+
"tests": 4,
|
|
5898
|
+
"asserts": 11,
|
|
5899
|
+
"skips": 0,
|
|
5900
|
+
"todos": 0
|
|
5901
|
+
},
|
|
5758
5902
|
"tone.test.ts": {
|
|
5759
5903
|
"tests": 7,
|
|
5760
5904
|
"asserts": 31,
|
|
@@ -123,6 +123,25 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
123
123
|
// exists to make impossible — and a mechanism that installs itself is the easiest kind to ship
|
|
124
124
|
// undeclared.
|
|
125
125
|
unit: "clearotron-deploy", runsOn: ["test"],
|
|
126
|
+
// ── ATTRIBUTED BY ITS OWN STAMP, NOT BY A TREE UNDER IT ────────────────────────────────────────
|
|
127
|
+
//
|
|
128
|
+
// Both halves of the commit attribution fail for this one unit, and it is the unit that performs the
|
|
129
|
+
// deploy. It is a `oneshot` running from a home directory with no checkout under it, so there is no
|
|
130
|
+
// tree to resolve; and its command line is the update script rather than the product entry point, so
|
|
131
|
+
// the running-process half cannot reach it either. It was therefore reported as "could NOT be read
|
|
132
|
+
// and was NOT compared" — the mechanism that places every commit on this box being the one thing the
|
|
133
|
+
// check could not say anything about.
|
|
134
|
+
//
|
|
135
|
+
// IT IS NOT GIVEN THE CHECKOUT'S HEAD, which is the obvious repair and a false one. The updater has
|
|
136
|
+
// no commit of its own: it is a script copied out of a separate store, and handing it the commit of
|
|
137
|
+
// the tree it merely operates on would make "N units, all on <sha>" true of the tree and false of the
|
|
138
|
+
// updater — and it would pass in exactly the case worth catching, since a stale updater still leaves
|
|
139
|
+
// the tree it just deployed on the deployed commit.
|
|
140
|
+
//
|
|
141
|
+
// So it leaves the compared population by name rather than by silence: the commit arm reports it as
|
|
142
|
+
// stamped and says where its verdict lives, and driver/updater-identity.mjs makes that verdict from
|
|
143
|
+
// the digests the updater records about itself.
|
|
144
|
+
attribution: "stamp",
|
|
126
145
|
measured: "2026-09-08, the test deployment: the timer and its service are installed for that deployment's own user, the timer is enabled and ACTIVE, and it last fired within the hour — `systemctl --user list-timers`. The service's own state says nothing about this: it is `inactive` between runs and `inactive` when the timer has been stopped. The timer's ActiveState says the timer itself is still up. It is NOT a next elapse and does not promise one: `active` means UP, never SCHEDULED. Proving something will still fire takes a real next elapse, which `systemctl list-timers --all` supplies and `show -p ActiveState` cannot. Measured in testing 2026-09-09: a timer reading `ActiveState=active` whose `OnCalendar` names a date already past, so `list-timers` reports its next fire as null and it will never run again.",
|
|
127
146
|
tracked: ["clearotron-deploy.service", "clearotron-deploy.timer"],
|
|
128
147
|
note: "the install's own updater. Placed on the test deployment, where it pulls hourly; production takes it when the owner asks.",
|
|
@@ -587,6 +606,18 @@ export const CHECKED_UNITS = Object.freeze(
|
|
|
587
606
|
UNIT_INVENTORY.filter((u) => u.runsOn.length > 0).map((u) => u.unit),
|
|
588
607
|
);
|
|
589
608
|
|
|
609
|
+
/**
|
|
610
|
+
* The units that are attributed by a stamp they write about themselves, not by a tree under them.
|
|
611
|
+
*
|
|
612
|
+
* DERIVED FROM THE INVENTORY RATHER THAN SPELLED AT THE USE SITE. A literal `["clearotron-deploy"]` in
|
|
613
|
+
* the verdict would be a spelling, not a property: it would keep passing after the entry was renamed,
|
|
614
|
+
* retired or given to another box, and the unit would fall back into the could-not-read pile with
|
|
615
|
+
* nothing saying why. The entry that declares the unit is the one place that decides how it is judged.
|
|
616
|
+
*/
|
|
617
|
+
export const STAMP_ATTRIBUTED_UNITS = Object.freeze(
|
|
618
|
+
UNIT_INVENTORY.filter((u) => u.runsOn.length > 0 && u.attribution === "stamp").map((u) => u.unit),
|
|
619
|
+
);
|
|
620
|
+
|
|
590
621
|
/**
|
|
591
622
|
* The TIMER units this deployment claims to have, by name, as systemd spells them.
|
|
592
623
|
*
|
|
@@ -666,6 +697,11 @@ export const ACCOUNTED_FILES = Object.freeze(
|
|
|
666
697
|
*/
|
|
667
698
|
export function unitInventoryVerdict({
|
|
668
699
|
live = [], files = [], collisions = [], filesError = null, box = null, probe = { ok: true },
|
|
700
|
+
// PASSED IN RATHER THAN IMPORTED, so the allowlist stays in one place. This module deliberately has
|
|
701
|
+
// no imports — it is the declaration the rest of the tree reads — and pulling one in to spell two
|
|
702
|
+
// names inside a sentence would put a second copy of that list here to drift from the first. The
|
|
703
|
+
// caller already resolves the box through `shared/deployment-box.mjs`; it hands over the names too.
|
|
704
|
+
boxNames = [],
|
|
669
705
|
// INJECTED so the retirement branch can be PLANTED. `retired:` has no members today — the two it
|
|
670
706
|
// carried left with their files — so `absentByRetirement` and the line it feeds are unreachable
|
|
671
707
|
// against the real table, and a branch nothing exercises is a branch that will not fire the next
|
|
@@ -744,8 +780,27 @@ export function unitInventoryVerdict({
|
|
|
744
780
|
const absentByRetirement = box
|
|
745
781
|
? inventory.filter((u) => declaredHere(u) && u.retired).map((u) => u.unit).sort()
|
|
746
782
|
: [];
|
|
783
|
+
// ── AN UNNAMED BOX IS A FAULT, NOT A FOOTNOTE ─────────────────────────────────────────────────────
|
|
784
|
+
//
|
|
785
|
+
// This sentence used to ride along on a `pass`. It said, correctly, that half the arm did not run and
|
|
786
|
+
// that this is not the same as passing — and then the arm passed. So the one deployment where nobody
|
|
787
|
+
// sets the name gets a green tick beside a paragraph explaining that the check did not happen, which
|
|
788
|
+
// is the state this file argues against everywhere else it appears: a result nobody can act on,
|
|
789
|
+
// shaped exactly like a result that is fine.
|
|
790
|
+
//
|
|
791
|
+
// THE HALF THAT DOES NOT RUN IS THE ONE THAT CATCHES A SERVICE THAT STOPPED AND STAYED STOPPED. The
|
|
792
|
+
// other half enumerates what is alive, so a unit that is no longer alive is invisible to it by
|
|
793
|
+
// construction. Suppressing the mirror question is right — guessing the box would report every other
|
|
794
|
+
// deployment's units missing here — but the suppression must be loud, because while it lasts a
|
|
795
|
+
// declared service can disappear and nothing anywhere produces a finding.
|
|
796
|
+
//
|
|
797
|
+
// Measured 2026-09-10: the test deployment names itself and both halves run, reporting
|
|
798
|
+
// `client-mcp-apikey` as absent by retirement. Production sets no name, so on the deployment where a
|
|
799
|
+
// stopped service matters most, this half has never executed.
|
|
747
800
|
const boxLine = box ? "" : " The box could not be named, so 'declared here and not running' was NOT "
|
|
748
|
-
+ "checked — that half did not run, which is not the same as passing."
|
|
801
|
+
+ "checked — that half did not run, which is not the same as passing. Set CLEAROTRON_BOX to the "
|
|
802
|
+
+ `name this deployment goes by${boxNames.length ? ` (${boxNames.join(" or ")})` : ""}; a deployment that cannot say `
|
|
803
|
+
+ "which one it is cannot be told that a service it declares has gone.";
|
|
749
804
|
|
|
750
805
|
// The tracked-file half of this arm depends on a walk of the tree, and an incomplete walk returns a
|
|
751
806
|
// SHORT list — which quietly satisfies "no file is unaccounted for" and "no entry is misdeclared" at
|
|
@@ -815,6 +870,29 @@ export function unitInventoryVerdict({
|
|
|
815
870
|
return { state: "fail", undeclared, orphaned, absent, misdeclared, claimedAbsent, absentByRetirement,
|
|
816
871
|
message: faults.join(" ") + retiredLine + orphanLine + boxLine + filesLine };
|
|
817
872
|
}
|
|
873
|
+
// NO FAULT FOUND IS NOT A PASS WHEN HALF THE ARM WAS SUPPRESSED. Reported separately from `faults`
|
|
874
|
+
// rather than pushed into it, because it is not a finding ABOUT the deployment — it is this check
|
|
875
|
+
// saying what it could not ask. The message still carries everything the pass carried; only the
|
|
876
|
+
// verdict changes, so a reader loses no detail and gains the one word that makes them act.
|
|
877
|
+
if (!box) {
|
|
878
|
+
return { state: "fail", undeclared, orphaned, absent, misdeclared, claimedAbsent, absentByRetirement,
|
|
879
|
+
message: `${liveBases.length} live unit(s), all declared, and NOTHING WAS COMPARED THE OTHER WAY.`
|
|
880
|
+
+ `${retiredLine}${orphanLine}${boxLine}${filesLine}` };
|
|
881
|
+
}
|
|
882
|
+
// THE FILE HALF, FOR THE SAME REASON. `filesLine` above already says, in the code's own words, that a
|
|
883
|
+
// short list "quietly satisfies" both file arms and that an empty walk "is not a clean bill of health,
|
|
884
|
+
// it is a walk pointed somewhere wrong". It said both of those on a `pass`.
|
|
885
|
+
//
|
|
886
|
+
// AN EMPTY WALK AND AN INCOMPLETE ONE GET THE SAME VERDICT AND KEEP DIFFERENT SENTENCES. They are not
|
|
887
|
+
// equally likely — an empty walk is almost certainly a path pointed wrong, an incomplete one may be a
|
|
888
|
+
// real read error on one directory — but neither is a state in which "no file is unaccounted for"
|
|
889
|
+
// means anything, and that is what the verdict is being asked. The messages already tell them apart;
|
|
890
|
+
// the reader needs the difference, the exit code does not.
|
|
891
|
+
if (filesError || files.length === 0) {
|
|
892
|
+
return { state: "fail", undeclared, orphaned, absent, misdeclared, claimedAbsent, absentByRetirement,
|
|
893
|
+
message: `${liveBases.length} live unit(s), all declared, and THE FILE ARMS DID NOT RUN.`
|
|
894
|
+
+ `${retiredLine}${orphanLine}${boxLine}${filesLine}` };
|
|
895
|
+
}
|
|
818
896
|
return {
|
|
819
897
|
state: "pass", undeclared, orphaned, absent, misdeclared, claimedAbsent, absentByRetirement,
|
|
820
898
|
message: `${liveBases.length} live unit(s), all declared; `
|
|
@@ -942,12 +1020,23 @@ export function unitWorkingDirectory(raw) {
|
|
|
942
1020
|
* @param {boolean} a.ahead the clone holds commits its remote does not
|
|
943
1021
|
* @returns {{state:"pass"|"fail"|"skip", message:string, owned:string[], foreign:string[]}}
|
|
944
1022
|
*/
|
|
945
|
-
export function serviceCommitVerdict({ clones = [], deployClone = null, expectHead = null, ahead = false } = {}) {
|
|
946
|
-
|
|
1023
|
+
export function serviceCommitVerdict({ clones = [], deployClone = null, expectHead = null, ahead = false, stampAttributed = STAMP_ATTRIBUTED_UNITS } = {}) {
|
|
1024
|
+
// — TAKEN OUT OF THE POPULATION BY NAME, BEFORE ANYTHING ELSE READS IT, and reported in its own
|
|
1025
|
+
// clause below. These units have no tree to resolve and are judged by a stamp they write about
|
|
1026
|
+
// themselves; leaving them here put the mechanism that performs the deploy into the "could NOT be
|
|
1027
|
+
// read" pile, where it read as a gap in the check rather than as a unit judged somewhere else.
|
|
1028
|
+
//
|
|
1029
|
+
// THE POPULATION MUST NOT LOSE A MEMBER ON THE WAY THROUGH. Removing a unit here and failing to
|
|
1030
|
+
// report it there is the silently narrowed population this verdict already carries two comments
|
|
1031
|
+
// about — so the four buckets are asserted to account for every row that arrived.
|
|
1032
|
+
const stampedNames = new Set(stampAttributed ?? []);
|
|
1033
|
+
const stamped = (clones ?? []).filter((c) => c && stampedNames.has(c.unit));
|
|
1034
|
+
const rest = (clones ?? []).filter((c) => !(c && stampedNames.has(c.unit)));
|
|
1035
|
+
const running = rest.filter((c) => c?.head);
|
|
947
1036
|
// — ASKED AND FAILED, which is not the same as "reported no WorkingDirectory". Only a unit the
|
|
948
1037
|
// caller actually tried to read and could not lands here; an inactive unit with nothing to compare is
|
|
949
1038
|
// not a gap in the population and is not reported as one.
|
|
950
|
-
const unreadable =
|
|
1039
|
+
const unreadable = rest.filter((c) => c && !c.head && c.unreadable);
|
|
951
1040
|
const norm = (p) => String(p ?? "").replace(/\/+$/, "");
|
|
952
1041
|
const foreign = running.filter((c) => norm(c.clone) !== norm(deployClone));
|
|
953
1042
|
const owned = running.filter((c) => norm(c.clone) === norm(deployClone));
|
|
@@ -955,12 +1044,27 @@ export function serviceCommitVerdict({ clones = [], deployClone = null, expectHe
|
|
|
955
1044
|
foreign.length
|
|
956
1045
|
? `${foreign.length} unit(s) on another clone, not this deploy's to judge: ${foreign.map((c) => c.unit).join(", ")}`
|
|
957
1046
|
: "",
|
|
1047
|
+
stamped.length
|
|
1048
|
+
? `${stamped.length} unit(s) attributed by their own stamp rather than by a tree, and judged on the `
|
|
1049
|
+
+ `updater arm instead of here: ${stamped.map((c) => c.unit).join(", ")}`
|
|
1050
|
+
: "",
|
|
958
1051
|
unreadable.length
|
|
959
1052
|
? `${unreadable.length} unit(s) could NOT be read and were NOT compared: ${unreadable.map((c) => `${c.unit} (${c.unreadable})`).join("; ")}`
|
|
960
1053
|
: "",
|
|
961
1054
|
].filter(Boolean);
|
|
962
1055
|
const aside = asideParts.length ? ` · ${asideParts.join(" · ")}` : "";
|
|
963
|
-
|
|
1056
|
+
// — `idle` IS RETURNED BUT NOT NARRATED, and the split is deliberate on both sides. An inactive unit
|
|
1057
|
+
// with nothing to compare is genuinely not a gap, so the message stays quiet about it as it always
|
|
1058
|
+
// has; but a bucket that exists only inside this function is a bucket no test can count, and the
|
|
1059
|
+
// rows this verdict drops are exactly what a silently narrowed population looks like. Naming it here
|
|
1060
|
+
// lets a test assert that the five buckets account for every row that arrived, without adding a
|
|
1061
|
+
// sentence to a report that is right not to carry one.
|
|
1062
|
+
const idle = rest.filter((c) => c && !c.head && !c.unreadable);
|
|
1063
|
+
const names = {
|
|
1064
|
+
owned: owned.map((c) => c.unit), foreign: foreign.map((c) => c.unit),
|
|
1065
|
+
unreadable: unreadable.map((c) => c.unit), stamped: stamped.map((c) => c.unit),
|
|
1066
|
+
idle: idle.map((c) => c.unit),
|
|
1067
|
+
};
|
|
964
1068
|
|
|
965
1069
|
// COULD NOT SCOPE ⇒ DO NOT JUDGE. Without knowing which clone is being deployed, every unit looks
|
|
966
1070
|
// equally like ours, which is the state that produced the false red. A failure to look is not a
|