@bongos/core 1.21.92 → 1.21.93
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/.bongos-core.json +43 -33
- package/docs/adr/0363-who-decides-what-is-a-table-changed-only-by-amendment.md +1 -0
- package/docs/module-api-changelog.md +7 -0
- package/modules/autonomy/routes/autonomy.js +14 -5
- package/modules/government/decision-board.js +13 -3
- package/modules/government/decision-rules.js +61 -13
- package/modules/lifecycle/routes/done-when.js +2 -2
- package/modules/lifecycle/routes/gate-approvals.js +1 -1
- package/modules/lifecycle/routes/goals.js +4 -4
- package/modules/lifecycle/routes/project-settings.js +2 -2
- package/modules/lifecycle/routes/tasks.js +1 -1
- package/modules/lifecycle/routes/versions.js +1 -1
- package/modules/npm-release/routes/release.js +1 -0
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/provisioning/routes/core-upgrade.js +11 -0
- package/modules/render-deploy/routes/act.js +1 -1
- package/modules/security/routes/reports.js +3 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/gds/fitness.js +7 -0
- package/src/bongos/act-alone.js +84 -0
- package/src/bongos/routes/builders.js +6 -1
- package/src/module-api.js +7 -1
- package/tests/autonomy_fence_priority.mjs +3 -1
- package/tests/government_act_alone_buildings.mjs +430 -0
- package/tests/module_api.mjs +1 -0
- package/tests/npm_release_release.mjs +2 -1
- package/tests/ship_mode.mjs +2 -1
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.21.
|
|
6
|
-
"core_contract": "1.21.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.21.93",
|
|
6
|
+
"core_contract": "1.21.93",
|
|
7
|
+
"source_commit": "4c275841c75ef2bebdaed532f3b5e57c7a789d90",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-10-
|
|
9
|
+
"built_at": "2026-10-09T07:16:47.997Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 590,
|
|
13
13
|
"agent_docs_stubbed": 28,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 3008,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 3627,
|
|
20
|
+
"tree_sha256": "49bb2f826dddbdaabbcfe2ace12bc326ab12234fc2ce4514883ad7aa6ca0e018",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2272,7 +2272,7 @@
|
|
|
2272
2272
|
{
|
|
2273
2273
|
"path": "docs/adr/0363-who-decides-what-is-a-table-changed-only-by-amendment.md",
|
|
2274
2274
|
"mode": "0000644",
|
|
2275
|
-
"sha256": "
|
|
2275
|
+
"sha256": "9668a578579f0f38cc24b1eaa3a87a7306f4cf8ef62df64a7938f07c249954e2"
|
|
2276
2276
|
},
|
|
2277
2277
|
{
|
|
2278
2278
|
"path": "docs/adr/0364-repo-access-is-asked-at-sign-in-and-held-for-the-session.md",
|
|
@@ -2862,7 +2862,7 @@
|
|
|
2862
2862
|
{
|
|
2863
2863
|
"path": "docs/module-api-changelog.md",
|
|
2864
2864
|
"mode": "0000644",
|
|
2865
|
-
"sha256": "
|
|
2865
|
+
"sha256": "63e2775e1139816091d9e9629b5e2b1d16a2862ad7caaacfa817cee89eb90809"
|
|
2866
2866
|
},
|
|
2867
2867
|
{
|
|
2868
2868
|
"path": "docs/modules-contract.md",
|
|
@@ -4247,7 +4247,7 @@
|
|
|
4247
4247
|
{
|
|
4248
4248
|
"path": "modules/autonomy/routes/autonomy.js",
|
|
4249
4249
|
"mode": "0000644",
|
|
4250
|
-
"sha256": "
|
|
4250
|
+
"sha256": "3def3840ed0dd842fa08ef59df0b6af68d3d44ef33b13b3a2ab83c4a3a82c9ed"
|
|
4251
4251
|
},
|
|
4252
4252
|
{
|
|
4253
4253
|
"path": "modules/autonomy/runner-health.js",
|
|
@@ -4927,12 +4927,12 @@
|
|
|
4927
4927
|
{
|
|
4928
4928
|
"path": "modules/government/decision-board.js",
|
|
4929
4929
|
"mode": "0000644",
|
|
4930
|
-
"sha256": "
|
|
4930
|
+
"sha256": "d3fbff112e2da86a12498be1b87904071b556c4939525ccf9bc906a66deb8b72"
|
|
4931
4931
|
},
|
|
4932
4932
|
{
|
|
4933
4933
|
"path": "modules/government/decision-rules.js",
|
|
4934
4934
|
"mode": "0000644",
|
|
4935
|
-
"sha256": "
|
|
4935
|
+
"sha256": "942304d1b77cc7f428a5f4f2462e28a96accc23de2e081ba7c62d9a8a61f44cf"
|
|
4936
4936
|
},
|
|
4937
4937
|
{
|
|
4938
4938
|
"path": "modules/government/docket-floors.js",
|
|
@@ -7297,12 +7297,12 @@
|
|
|
7297
7297
|
{
|
|
7298
7298
|
"path": "modules/lifecycle/routes/done-when.js",
|
|
7299
7299
|
"mode": "0000644",
|
|
7300
|
-
"sha256": "
|
|
7300
|
+
"sha256": "22d015052c9d4870491101ad1170f5e784a372de6e49732b39f449dee1cf2b8e"
|
|
7301
7301
|
},
|
|
7302
7302
|
{
|
|
7303
7303
|
"path": "modules/lifecycle/routes/gate-approvals.js",
|
|
7304
7304
|
"mode": "0000644",
|
|
7305
|
-
"sha256": "
|
|
7305
|
+
"sha256": "7dd64c54a5b35ec849a91361dc5cdc3cba86e0dc102ea283ee86c28caf60efe7"
|
|
7306
7306
|
},
|
|
7307
7307
|
{
|
|
7308
7308
|
"path": "modules/lifecycle/routes/goal-move.js",
|
|
@@ -7322,7 +7322,7 @@
|
|
|
7322
7322
|
{
|
|
7323
7323
|
"path": "modules/lifecycle/routes/goals.js",
|
|
7324
7324
|
"mode": "0000644",
|
|
7325
|
-
"sha256": "
|
|
7325
|
+
"sha256": "3179f77a1117e54c13d75d7af5248cb28a434e1bcce938fb88b2edbe27cd5aac"
|
|
7326
7326
|
},
|
|
7327
7327
|
{
|
|
7328
7328
|
"path": "modules/lifecycle/routes/help-requests.js",
|
|
@@ -7352,7 +7352,7 @@
|
|
|
7352
7352
|
{
|
|
7353
7353
|
"path": "modules/lifecycle/routes/project-settings.js",
|
|
7354
7354
|
"mode": "0000644",
|
|
7355
|
-
"sha256": "
|
|
7355
|
+
"sha256": "e892c118488d028f5a0203b5560fb8efa0047e2a4bbac9faafe5ee2132027c54"
|
|
7356
7356
|
},
|
|
7357
7357
|
{
|
|
7358
7358
|
"path": "modules/lifecycle/routes/recommendations.js",
|
|
@@ -7387,7 +7387,7 @@
|
|
|
7387
7387
|
{
|
|
7388
7388
|
"path": "modules/lifecycle/routes/tasks.js",
|
|
7389
7389
|
"mode": "0000644",
|
|
7390
|
-
"sha256": "
|
|
7390
|
+
"sha256": "564dabb3de1eee96e374160d39f57c61100bca843ebac2b2267aa4693ec5038f"
|
|
7391
7391
|
},
|
|
7392
7392
|
{
|
|
7393
7393
|
"path": "modules/lifecycle/routes/version-route-authz.js",
|
|
@@ -7397,7 +7397,7 @@
|
|
|
7397
7397
|
{
|
|
7398
7398
|
"path": "modules/lifecycle/routes/versions.js",
|
|
7399
7399
|
"mode": "0000644",
|
|
7400
|
-
"sha256": "
|
|
7400
|
+
"sha256": "161c9f5928ff2ba5ec7733e150c8ba86217b428588e2d0322e971c989ce0d71f"
|
|
7401
7401
|
},
|
|
7402
7402
|
{
|
|
7403
7403
|
"path": "modules/lifecycle/routes/visuals.js",
|
|
@@ -7602,7 +7602,7 @@
|
|
|
7602
7602
|
{
|
|
7603
7603
|
"path": "modules/npm-release/routes/release.js",
|
|
7604
7604
|
"mode": "0000644",
|
|
7605
|
-
"sha256": "
|
|
7605
|
+
"sha256": "e1736b4252f720fd2f8d1d42b56e0099fbd3a82f257d96c4aad4ffe8eef982bf"
|
|
7606
7606
|
},
|
|
7607
7607
|
{
|
|
7608
7608
|
"path": "modules/npm-release/routes/task-where.js",
|
|
@@ -7682,7 +7682,7 @@
|
|
|
7682
7682
|
{
|
|
7683
7683
|
"path": "modules/onboarding/routes/access-requests.js",
|
|
7684
7684
|
"mode": "0000644",
|
|
7685
|
-
"sha256": "
|
|
7685
|
+
"sha256": "e54866a2398da3a4f39bd31a9fd122a6045568da1e67c26450121d67456bc769"
|
|
7686
7686
|
},
|
|
7687
7687
|
{
|
|
7688
7688
|
"path": "modules/onboarding/routes/onboarding.js",
|
|
@@ -8552,7 +8552,7 @@
|
|
|
8552
8552
|
{
|
|
8553
8553
|
"path": "modules/provisioning/routes/core-upgrade.js",
|
|
8554
8554
|
"mode": "0000644",
|
|
8555
|
-
"sha256": "
|
|
8555
|
+
"sha256": "48bb22ee49d10e74fd4c01a56f46ca0d19d57eeb3228c1b797e2ba05854b3400"
|
|
8556
8556
|
},
|
|
8557
8557
|
{
|
|
8558
8558
|
"path": "modules/provisioning/routes/demo.js",
|
|
@@ -9377,7 +9377,7 @@
|
|
|
9377
9377
|
{
|
|
9378
9378
|
"path": "modules/render-deploy/routes/act.js",
|
|
9379
9379
|
"mode": "0000644",
|
|
9380
|
-
"sha256": "
|
|
9380
|
+
"sha256": "8a986f38de7fa3d62b27459d12e1d304344bb1575968a445c483f6e3ca5cf0c1"
|
|
9381
9381
|
},
|
|
9382
9382
|
{
|
|
9383
9383
|
"path": "modules/render-deploy/routes/app.js",
|
|
@@ -9517,7 +9517,7 @@
|
|
|
9517
9517
|
{
|
|
9518
9518
|
"path": "modules/security/routes/reports.js",
|
|
9519
9519
|
"mode": "0000644",
|
|
9520
|
-
"sha256": "
|
|
9520
|
+
"sha256": "f38c00f7da0106db5a39fb207206276f1c4c8b6ded3b33f25f3c894fc1558fb7"
|
|
9521
9521
|
},
|
|
9522
9522
|
{
|
|
9523
9523
|
"path": "modules/security/routes/scans.js",
|
|
@@ -10107,12 +10107,12 @@
|
|
|
10107
10107
|
{
|
|
10108
10108
|
"path": "package-lock.json",
|
|
10109
10109
|
"mode": "0000644",
|
|
10110
|
-
"sha256": "
|
|
10110
|
+
"sha256": "c5cb988b5bb2255930679a818cdcc5cf278646176ef15bbd2dc113f36698af02"
|
|
10111
10111
|
},
|
|
10112
10112
|
{
|
|
10113
10113
|
"path": "package.json",
|
|
10114
10114
|
"mode": "0000644",
|
|
10115
|
-
"sha256": "
|
|
10115
|
+
"sha256": "4e05f7203accb56a762ab83c6ad71a8c91dcf58218665625ae993c3825f13da7"
|
|
10116
10116
|
},
|
|
10117
10117
|
{
|
|
10118
10118
|
"path": "public-docs/index.html",
|
|
@@ -10132,7 +10132,7 @@
|
|
|
10132
10132
|
{
|
|
10133
10133
|
"path": "release-notes.json",
|
|
10134
10134
|
"mode": "0000644",
|
|
10135
|
-
"sha256": "
|
|
10135
|
+
"sha256": "ccf38aea55687532ade59d10785095bc50b266095c37901865b3e7db6777e16f"
|
|
10136
10136
|
},
|
|
10137
10137
|
{
|
|
10138
10138
|
"path": "scripts/cron/style_guide_audit.sh",
|
|
@@ -10702,7 +10702,7 @@
|
|
|
10702
10702
|
{
|
|
10703
10703
|
"path": "scripts/gds/fitness.js",
|
|
10704
10704
|
"mode": "0000644",
|
|
10705
|
-
"sha256": "
|
|
10705
|
+
"sha256": "0ea1beb8b23148696f9a88cfbca1b303d9299e1e8dc2da21bf86ea21b9d035e0"
|
|
10706
10706
|
},
|
|
10707
10707
|
{
|
|
10708
10708
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -11989,6 +11989,11 @@
|
|
|
11989
11989
|
"mode": "0000644",
|
|
11990
11990
|
"sha256": "7cb9dac9cb87b679e1ae50e4112b3a7928a38ebb9902c8947a74d17bec4d9009"
|
|
11991
11991
|
},
|
|
11992
|
+
{
|
|
11993
|
+
"path": "src/bongos/act-alone.js",
|
|
11994
|
+
"mode": "0000644",
|
|
11995
|
+
"sha256": "5d7f7b434b8aa8ab5782e7902f42b7103a697a71c95cc6f61beb4f3f0ea561f4"
|
|
11996
|
+
},
|
|
11992
11997
|
{
|
|
11993
11998
|
"path": "src/bongos/api-errors.js",
|
|
11994
11999
|
"mode": "0000644",
|
|
@@ -12262,7 +12267,7 @@
|
|
|
12262
12267
|
{
|
|
12263
12268
|
"path": "src/bongos/routes/builders.js",
|
|
12264
12269
|
"mode": "0000644",
|
|
12265
|
-
"sha256": "
|
|
12270
|
+
"sha256": "0511897ded470ced36f21645685f0757b3c77ad8b8a50e043b05a9b59fff480d"
|
|
12266
12271
|
},
|
|
12267
12272
|
{
|
|
12268
12273
|
"path": "src/bongos/routes/cli-commands.js",
|
|
@@ -12392,7 +12397,7 @@
|
|
|
12392
12397
|
{
|
|
12393
12398
|
"path": "src/module-api.js",
|
|
12394
12399
|
"mode": "0000644",
|
|
12395
|
-
"sha256": "
|
|
12400
|
+
"sha256": "93548f4008e232c69ca994b86cb82c971d7b3830271e28b5251d60e6a958e777"
|
|
12396
12401
|
},
|
|
12397
12402
|
{
|
|
12398
12403
|
"path": "src/module-loader/catalog.js",
|
|
@@ -12912,7 +12917,7 @@
|
|
|
12912
12917
|
{
|
|
12913
12918
|
"path": "tests/autonomy_fence_priority.mjs",
|
|
12914
12919
|
"mode": "0000644",
|
|
12915
|
-
"sha256": "
|
|
12920
|
+
"sha256": "050fb46d4db451ded339caa6de865410ea7a0357688f15d558e25335c83a0d22"
|
|
12916
12921
|
},
|
|
12917
12922
|
{
|
|
12918
12923
|
"path": "tests/autonomy_gate.mjs",
|
|
@@ -14284,6 +14289,11 @@
|
|
|
14284
14289
|
"mode": "0000644",
|
|
14285
14290
|
"sha256": "47d5e7db2bdab4504fca424d20588d4b0857e3246cd0376f5e111e6ca409632e"
|
|
14286
14291
|
},
|
|
14292
|
+
{
|
|
14293
|
+
"path": "tests/government_act_alone_buildings.mjs",
|
|
14294
|
+
"mode": "0000644",
|
|
14295
|
+
"sha256": "22ee46f430e4443cb4946aa27608a442c23599c338602b70f0de602635257f77"
|
|
14296
|
+
},
|
|
14287
14297
|
{
|
|
14288
14298
|
"path": "tests/government_board_amendment.mjs",
|
|
14289
14299
|
"mode": "0000644",
|
|
@@ -15672,7 +15682,7 @@
|
|
|
15672
15682
|
{
|
|
15673
15683
|
"path": "tests/module_api.mjs",
|
|
15674
15684
|
"mode": "0000644",
|
|
15675
|
-
"sha256": "
|
|
15685
|
+
"sha256": "9e282a1e2de543831d164a78a160735d37dd956a4751f2d26c74beddc41319cc"
|
|
15676
15686
|
},
|
|
15677
15687
|
{
|
|
15678
15688
|
"path": "tests/module_api_lazy.mjs",
|
|
@@ -15937,7 +15947,7 @@
|
|
|
15937
15947
|
{
|
|
15938
15948
|
"path": "tests/npm_release_release.mjs",
|
|
15939
15949
|
"mode": "0000644",
|
|
15940
|
-
"sha256": "
|
|
15950
|
+
"sha256": "1bf138c9a88e23989255548a6aec0205da7f9c1787dbf872271c1b01575f065c"
|
|
15941
15951
|
},
|
|
15942
15952
|
{
|
|
15943
15953
|
"path": "tests/npm_release_where.mjs",
|
|
@@ -17307,7 +17317,7 @@
|
|
|
17307
17317
|
{
|
|
17308
17318
|
"path": "tests/ship_mode.mjs",
|
|
17309
17319
|
"mode": "0000644",
|
|
17310
|
-
"sha256": "
|
|
17320
|
+
"sha256": "aee20a5f6e2998d18f605b91d1eb1019e8ada824135759b29633caa3153d9255"
|
|
17311
17321
|
},
|
|
17312
17322
|
{
|
|
17313
17323
|
"path": "tests/ship_post_land_verify.mjs",
|
|
@@ -47,6 +47,7 @@ The council, tester, lead, trustee, maintainer and treasurer seats are the proje
|
|
|
47
47
|
## Consequences
|
|
48
48
|
|
|
49
49
|
- Owning modules adopt `checkActAlone` on their own direct acts, one at a time: admission and goal join requests (People), goal creation (Planning), report judging (Disputes), settings (Rules), the fence (Automation), criteria (Review), the pin move (Release) and budgets (Treasury). Until then, a row for those buildings decides that building's board matters, and the direct page still decides alone. The docket says so honestly (D6). Widening, where a row lets a seat that does not hold the permission act, is deliberately not done here. It would put the table inside the permission resolver, which is a trust-boundary change of its own.
|
|
50
|
+
- **Done by [task 1004657](https://cloudbongos.com/builders#/task/1004657):** every building's direct routes now ask their row, through one doorway gate, `requireActAlone(area)` (`src/bongos/act-alone.js`), mounted after the permission gate and refusing with D5's exact shape. The routes per building are listed in `decision-rules.js` `AREAS[].acts`, and a test holds that list equal to the source. Three things are never refused: a brake (switching unattended building off, pausing one runner), because stopping must not wait for a meeting; a dry run, which only reads; and a passed matter's own act, which never goes through a route. The charter has no direct route. Goal join-request answers stay with the goal's owner.
|
|
50
51
|
- An objection on a matter still names a Full Idea section (the vote CHECK is unchanged), the same position as a genesis stage.
|
|
51
52
|
- Opening a matter needs `board.item.open` (metic floor). A project where everyone votes still has its matters put up by metics and above.
|
|
52
53
|
- Rejected: a settings route for the table (it would let a rank gate rewrite the government); a separate amendments table (a second pass path to keep in step); storing the rows in the instance config file like the board block (a row must be a database fact the trigger can guard).
|
|
@@ -3010,11 +3010,18 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3010
3010
|
landed since 1.21.87 with no explicit bump. run 37888371878. (task 1002620)
|
|
3011
3011
|
1.21.89 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3012
3012
|
landed since 1.21.88 with no explicit bump. run 37889772259. (task 1002620)
|
|
3013
|
+
(next carrier) — requireActAlone added (task 1004657, goal 1000111; ADR 0363
|
|
3014
|
+
D5). The gate a building's own page mounts after its permission gate:
|
|
3015
|
+
while the project's decision-rules row hands that building to a
|
|
3016
|
+
sitting, one builder acting alone is refused (409 decided_by_sitting).
|
|
3017
|
+
Additive; with no government module it lets every request through.
|
|
3013
3018
|
1.21.90 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3014
3019
|
landed since 1.21.89 with no explicit bump. run 37891104032. (task 1002620)
|
|
3015
3020
|
1.21.91 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3016
3021
|
landed since 1.21.90 with no explicit bump. run 37892645919. (task 1002620)
|
|
3017
3022
|
1.21.92 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3018
3023
|
landed since 1.21.91 with no explicit bump. run 37894940047. (task 1002620)
|
|
3024
|
+
1.21.93 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3025
|
+
landed since 1.21.92 with no explicit bump. run 37898035856. (task 1002620)
|
|
3019
3026
|
---------------------------------------------------------------------------
|
|
3020
3027
|
```
|
|
@@ -155,6 +155,15 @@ module.exports = function buildAutonomyRouter() {
|
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
// WHO DECIDES AT AUTOMATION (task 1004657; ADR 0363 D5). While the project's
|
|
159
|
+
// decision-rules row hands Automation to a sitting, these writes refuse one
|
|
160
|
+
// builder acting alone (409 decided_by_sitting) and point at the matter route;
|
|
161
|
+
// a passed matter acts through matter-acts.js, never through these routes.
|
|
162
|
+
// The BRAKE is exempt: switching unattended building off, or pausing one
|
|
163
|
+
// runner, never waits for a meeting — stopping can only narrow what runs.
|
|
164
|
+
const isTheBrake = (req) => !!req.body && req.body.enabled === false;
|
|
165
|
+
const isAPause = (req) => !!req.body && req.body.paused === true;
|
|
166
|
+
|
|
158
167
|
// ── the Autobongos fence (task 1003908, ADR 0307 §6) ───────────────────────
|
|
159
168
|
//
|
|
160
169
|
// These five routes ARE the fence. Everything the unattended runner is allowed
|
|
@@ -195,7 +204,7 @@ module.exports = function buildAutonomyRouter() {
|
|
|
195
204
|
|
|
196
205
|
// The kill switch. Archon, system:true — see the catalog entry for why it is the
|
|
197
206
|
// one permission that cannot be delegated to a custom rank.
|
|
198
|
-
router.post('/autonomy/fence', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), async (req, res) => {
|
|
207
|
+
router.post('/autonomy/fence', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), auth.requireActAlone('automation', { unless: isTheBrake }), async (req, res) => {
|
|
199
208
|
if (validateOrRespond(req, res, {
|
|
200
209
|
enabled: { required: true, type: 'boolean' },
|
|
201
210
|
reason: { type: 'string', maxLength: 500 },
|
|
@@ -217,7 +226,7 @@ module.exports = function buildAutonomyRouter() {
|
|
|
217
226
|
});
|
|
218
227
|
|
|
219
228
|
// Add a goal to the allowlist.
|
|
220
|
-
router.post('/autonomy/fence/goals', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), async (req, res) => {
|
|
229
|
+
router.post('/autonomy/fence/goals', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), auth.requireActAlone('automation'), async (req, res) => {
|
|
221
230
|
if (validateOrRespond(req, res, {
|
|
222
231
|
goal_id: { required: true }, // number or stringified number; coerced below
|
|
223
232
|
note: { type: 'string', maxLength: 500 },
|
|
@@ -237,7 +246,7 @@ module.exports = function buildAutonomyRouter() {
|
|
|
237
246
|
|
|
238
247
|
// Revoke one. `removed` distinguishes "just revoked" from "already gone" — an
|
|
239
248
|
// owner clicking Remove deserves to know which happened.
|
|
240
|
-
router.delete('/autonomy/fence/goals/:goalId', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), async (req, res) => {
|
|
249
|
+
router.delete('/autonomy/fence/goals/:goalId', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), auth.requireActAlone('automation'), async (req, res) => {
|
|
241
250
|
const goalId = Number(req.params.goalId);
|
|
242
251
|
if (!Number.isSafeInteger(goalId) || goalId <= 0) return res.fail('bad_goal_id', 400);
|
|
243
252
|
try {
|
|
@@ -255,7 +264,7 @@ module.exports = function buildAutonomyRouter() {
|
|
|
255
264
|
// first is part of choosing what it may do. The goal must already be on the
|
|
256
265
|
// allowlist (409 otherwise), so a priority can order the list and never widen
|
|
257
266
|
// it. `goal_id: null` clears it.
|
|
258
|
-
router.post('/autonomy/fence/priority', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), async (req, res) => {
|
|
267
|
+
router.post('/autonomy/fence/priority', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), auth.requireActAlone('automation'), async (req, res) => {
|
|
259
268
|
// Declared with an open rule: the value is a number OR null, which the
|
|
260
269
|
// validator has no type for, so the shape is checked just below.
|
|
261
270
|
if (validateOrRespond(req, res, { goal_id: {} })) return;
|
|
@@ -302,7 +311,7 @@ module.exports = function buildAutonomyRouter() {
|
|
|
302
311
|
// switch. Same atom as the kill switch: stopping a machine is the same act
|
|
303
312
|
// whether it is everyone's or one person's, and the paused builder must not be
|
|
304
313
|
// able to lift it (autonomy.run does not reach this route).
|
|
305
|
-
router.post('/autonomy/builders/:builderId/pause', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), async (req, res) => {
|
|
314
|
+
router.post('/autonomy/builders/:builderId/pause', auth.requireBuilder, auth.requirePermission('autonomy.fence.manage'), auth.requireActAlone('automation', { unless: isAPause }), async (req, res) => {
|
|
306
315
|
if (validateOrRespond(req, res, {
|
|
307
316
|
paused: { required: true, type: 'boolean' },
|
|
308
317
|
reason: { type: 'string', maxLength: 500 },
|
|
@@ -286,13 +286,23 @@ async function openMatter({ area, title, detailMd = null, action = null, propose
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
// May this builder act ALONE at this building — the check a building's own
|
|
289
|
-
// direct route runs before acting (People's rank assignments
|
|
289
|
+
// direct route runs before acting (People's rank assignments here; every other
|
|
290
|
+
// building's routes through the doorway's requireActAlone, task 1004657 — the
|
|
291
|
+
// list is decision-rules AREAS[].acts). Resolved
|
|
290
292
|
// per request against the live row and the builder's live ranks, uncached.
|
|
291
293
|
// Returns { ok: true } or { ok: false, reason: 'decided_by_sitting', … }.
|
|
294
|
+
//
|
|
295
|
+
// HOT PATH since task 1004657: ~30 write routes across the modules mount this
|
|
296
|
+
// through requireActAlone, so it does ONE query and no file I/O. The board block
|
|
297
|
+
// (loadGovernmentConfig — two synchronous file reads, boot-only by config.js's own
|
|
298
|
+
// rule) only ever shapes the CHARTER row's rule and words, and no direct route
|
|
299
|
+
// asks about the charter; every other building's row reads the same without it.
|
|
292
300
|
async function checkActAlone(area, builderId) {
|
|
293
|
-
|
|
301
|
+
if (!decisionRules.AREA_KEYS.includes(area)) return { ok: false, reason: 'unknown_area' };
|
|
294
302
|
const rows = await db.listDecisionRules();
|
|
295
|
-
const
|
|
303
|
+
const board = area === 'charter' ? loadGovernmentConfig().board : {};
|
|
304
|
+
const stored = (rows || []).find((r) => r.area === area) || decisionRules.defaultRow(area);
|
|
305
|
+
const resolved = decisionRules.resolveRow(stored, { board });
|
|
296
306
|
if (!resolved) return { ok: false, reason: 'unknown_area' };
|
|
297
307
|
if (resolved.as_today) return { ok: true, decided: resolved };
|
|
298
308
|
const seated = resolved.sitting
|
|
@@ -44,30 +44,77 @@ const { RANK_ORDER } = require('./catalog');
|
|
|
44
44
|
// The nine buildings, in street order (held equal to docket-vocab BUILDINGS by
|
|
45
45
|
// test). `gate` is the fixed permission that decides the building's matters
|
|
46
46
|
// today — the atom its own pages require, named so a reader can see what a
|
|
47
|
-
// sitting replaces. `acts`
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
47
|
+
// sitting replaces. `acts` is THE RECORD of every direct route that defers to
|
|
48
|
+
// the building's row: while the row names a sitting, each refuses one builder
|
|
49
|
+
// acting alone (409 decided_by_sitting) and points at the matter route. People's
|
|
50
|
+
// rank assignments ask in this module (task 1004529); every other route mounts
|
|
51
|
+
// the doorway's requireActAlone(area) after its permission gate (task 1004657).
|
|
52
|
+
// tests/government_act_alone_buildings.mjs reads this list against the source
|
|
53
|
+
// both ways, so a route cannot defer without being listed, or be listed without
|
|
54
|
+
// deferring. Which routes are a building's: those gated by its `gate` atom,
|
|
55
|
+
// plus the routes its passed matters act through (decision-board MATTER_ACTIONS)
|
|
56
|
+
// and its docket desks reach. A building with no direct route says so in
|
|
57
|
+
// `no_direct_route` — the charter, whose only act is an amendment.
|
|
58
|
+
// NOT a lone act, so never refused: a BRAKE (switching unattended building off,
|
|
59
|
+
// pausing one runner — stopping never waits for a meeting), a dry run, and a
|
|
60
|
+
// tenant project's own owner moving their own pin (that project's decision).
|
|
51
61
|
const AREAS = Object.freeze([
|
|
52
62
|
Object.freeze({ key: 'charter', name: 'Charter', gate: null,
|
|
53
|
-
today: 'the board, under the constitution in force', acts: Object.freeze([])
|
|
63
|
+
today: 'the board, under the constitution in force', acts: Object.freeze([]),
|
|
64
|
+
no_direct_route: 'the charter is changed only by amending the constitution — POST /government/board/items or /government/board/charter' }),
|
|
54
65
|
Object.freeze({ key: 'rules', name: 'Rules', gate: 'project.settings.manage',
|
|
55
|
-
today: 'whoever may change the project settings', acts: Object.freeze([
|
|
66
|
+
today: 'whoever may change the project settings', acts: Object.freeze([
|
|
67
|
+
'PATCH /project-settings/rot-days',
|
|
68
|
+
'PATCH /project-settings/ship-mode',
|
|
69
|
+
'PATCH /work-categories/:id',
|
|
70
|
+
]) }),
|
|
56
71
|
Object.freeze({ key: 'disputes', name: 'Disputes', gate: 'security.report.adjudicate',
|
|
57
|
-
today: 'whoever may judge reports', acts: Object.freeze([
|
|
72
|
+
today: 'whoever may judge reports', acts: Object.freeze([
|
|
73
|
+
'POST /security/reports/:id/confirm',
|
|
74
|
+
'POST /security/reports/:id/dispute',
|
|
75
|
+
'POST /security/reports/:id/fix',
|
|
76
|
+
]) }),
|
|
58
77
|
Object.freeze({ key: 'people', name: 'People', gate: 'government.manage',
|
|
59
78
|
today: 'whoever may manage ranks',
|
|
60
|
-
acts: Object.freeze([
|
|
79
|
+
acts: Object.freeze([
|
|
80
|
+
'POST /government/assignments',
|
|
81
|
+
'DELETE /government/assignments/:builderId/:rankKey',
|
|
82
|
+
'PATCH /access-requests/:id',
|
|
83
|
+
'POST /access-requests/invite',
|
|
84
|
+
]) }),
|
|
61
85
|
Object.freeze({ key: 'planning', name: 'Planning', gate: 'goal.create',
|
|
62
|
-
today: 'whoever may create goals', acts: Object.freeze([
|
|
86
|
+
today: 'whoever may create goals', acts: Object.freeze([
|
|
87
|
+
'POST /goals',
|
|
88
|
+
'POST /goals/reorder',
|
|
89
|
+
'POST /work-categories',
|
|
90
|
+
'PUT /versions/focus',
|
|
91
|
+
]) }),
|
|
63
92
|
Object.freeze({ key: 'automation', name: 'Automation', gate: 'autonomy.fence.manage',
|
|
64
|
-
today: 'whoever may switch unattended building on and off', acts: Object.freeze([
|
|
93
|
+
today: 'whoever may switch unattended building on and off', acts: Object.freeze([
|
|
94
|
+
'POST /autonomy/fence',
|
|
95
|
+
'POST /autonomy/fence/goals',
|
|
96
|
+
'DELETE /autonomy/fence/goals/:goalId',
|
|
97
|
+
'POST /autonomy/fence/priority',
|
|
98
|
+
'POST /autonomy/builders/:builderId/pause',
|
|
99
|
+
]) }),
|
|
65
100
|
Object.freeze({ key: 'review', name: 'Review', gate: 'criterion.satisfy',
|
|
66
|
-
today: 'whoever may mark work as meeting its checks', acts: Object.freeze([
|
|
101
|
+
today: 'whoever may mark work as meeting its checks', acts: Object.freeze([
|
|
102
|
+
'POST /done-when/:criterionId/satisfy',
|
|
103
|
+
'POST /done-when/:criterionId/unsatisfy',
|
|
104
|
+
]) }),
|
|
67
105
|
Object.freeze({ key: 'release', name: 'Release', gate: 'core.pin.move',
|
|
68
|
-
today: 'whoever may put a release live', acts: Object.freeze([
|
|
106
|
+
today: 'whoever may put a release live', acts: Object.freeze([
|
|
107
|
+
'POST /gate-approvals/:pr/approve',
|
|
108
|
+
'POST /override-requests/:id/decide',
|
|
109
|
+
'POST /provisioning/instances/:id/core-upgrade',
|
|
110
|
+
'POST /provisioning/instances/:id/update-channel',
|
|
111
|
+
'POST /render-deploy/act',
|
|
112
|
+
'POST /npm-release/release',
|
|
113
|
+
]) }),
|
|
69
114
|
Object.freeze({ key: 'treasury', name: 'Treasury', gate: 'builder.budget.set',
|
|
70
|
-
today: 'whoever may set spending limits', acts: Object.freeze([
|
|
115
|
+
today: 'whoever may set spending limits', acts: Object.freeze([
|
|
116
|
+
'PATCH /builders/:id/budget',
|
|
117
|
+
]) }),
|
|
71
118
|
]);
|
|
72
119
|
const AREA_KEYS = Object.freeze(AREAS.map((a) => a.key));
|
|
73
120
|
const areaByKey = (key) => AREAS.find((a) => a.key === key) || null;
|
|
@@ -245,6 +292,7 @@ function resolveRow(stored, { board = {} } = {}) {
|
|
|
245
292
|
gate: def.gate,
|
|
246
293
|
today: def.today,
|
|
247
294
|
acts: def.acts,
|
|
295
|
+
no_direct_route: def.no_direct_route || null,
|
|
248
296
|
set_by_item_id: row.set_by_item_id == null ? null : String(row.set_by_item_id),
|
|
249
297
|
updated_at: row.updated_at || null,
|
|
250
298
|
});
|
|
@@ -248,7 +248,7 @@ module.exports = function buildDoneWhenRouter() {
|
|
|
248
248
|
// everywhere — never as a UAT. The escape hatch stays (an all-abandoned
|
|
249
249
|
// criterion that was met another way still needs one); it is just no longer
|
|
250
250
|
// silent.
|
|
251
|
-
router.post('/done-when/:criterionId/satisfy', auth.requireBuilder, auth.requirePermission('criterion.satisfy'), asyncHandler('POST /done-when/:criterionId/satisfy', async (req, res) => {
|
|
251
|
+
router.post('/done-when/:criterionId/satisfy', auth.requireBuilder, auth.requirePermission('criterion.satisfy'), auth.requireActAlone('review'), asyncHandler('POST /done-when/:criterionId/satisfy', async (req, res) => {
|
|
252
252
|
if (validateOrRespond(req, res, {
|
|
253
253
|
satisfied_by_task_id: { type: 'integer', min: 1 },
|
|
254
254
|
override_reason: { type: 'string', maxLength: 2000 },
|
|
@@ -476,7 +476,7 @@ module.exports = function buildDoneWhenRouter() {
|
|
|
476
476
|
// rank: archon — un-flip a criterion; same gate as satisfy.
|
|
477
477
|
// An optional `reason` (the Review desk's one-tap chip, task 1004561) is
|
|
478
478
|
// logged beside who undid it; the flip itself is unchanged.
|
|
479
|
-
router.post('/done-when/:criterionId/unsatisfy', auth.requireBuilder, auth.requirePermission('criterion.unsatisfy'), asyncHandler('POST /done-when/:criterionId/unsatisfy', async (req, res) => {
|
|
479
|
+
router.post('/done-when/:criterionId/unsatisfy', auth.requireBuilder, auth.requirePermission('criterion.unsatisfy'), auth.requireActAlone('review'), asyncHandler('POST /done-when/:criterionId/unsatisfy', async (req, res) => {
|
|
480
480
|
if (validateOrRespond(req, res, { reason: { type: 'string', maxLength: 200 } })) return;
|
|
481
481
|
const id = parseId(req, res, { param: 'criterionId', code: 'bad_criterion_id' });
|
|
482
482
|
if (id === null) return;
|
|
@@ -255,7 +255,7 @@ module.exports = function buildGateApprovalsRouter() {
|
|
|
255
255
|
// POST /gate-approvals/:pr/approve — record owner approval for a stuck gate PR and
|
|
256
256
|
// re-run the gate-review check so it clears. rank: archon (clears the hard floor —
|
|
257
257
|
// pinned in route-rank-check.EXPECTED_RANKS for downgrade protection).
|
|
258
|
-
router.post('/gate-approvals/:pr/approve', auth.requireBuilder, auth.requirePermission('gate_approval.decide'), async (req, res) => {
|
|
258
|
+
router.post('/gate-approvals/:pr/approve', auth.requireBuilder, auth.requirePermission('gate_approval.decide'), auth.requireActAlone('release'), async (req, res) => {
|
|
259
259
|
const number = parseId(req, res, { param: 'pr', code: 'bad_pr', positiveInt: true });
|
|
260
260
|
if (number == null) return;
|
|
261
261
|
try {
|
|
@@ -83,7 +83,7 @@ module.exports = function buildGoalsRouter() {
|
|
|
83
83
|
// POST /goals — create a goal.
|
|
84
84
|
// rank: metic+archon — a Metic or Archon may create a goal; creation of a goal
|
|
85
85
|
// whose scope_modules includes a PROTECTED module is Archon-only (in-handler).
|
|
86
|
-
router.post('/goals', auth.requireBuilder, auth.requirePermission('goal.create'), async (req, res) => {
|
|
86
|
+
router.post('/goals', auth.requireBuilder, auth.requirePermission('goal.create'), auth.requireActAlone('planning'), async (req, res) => {
|
|
87
87
|
if (validateOrRespond(req, res, {
|
|
88
88
|
version_id: { required: true, type: 'string', minLength: 1, maxLength: 64 },
|
|
89
89
|
title: { required: true, type: 'string', minLength: 1, maxLength: LIMITS.TITLE },
|
|
@@ -239,7 +239,7 @@ module.exports = function buildGoalsRouter() {
|
|
|
239
239
|
// Mounted BEFORE GET /goals/:id so 'reorder' is never captured as an id.
|
|
240
240
|
// rank: archon — priority is an owner decision, never self-granted by a
|
|
241
241
|
// goal's own member/manager (unlike the goal-authority routes above).
|
|
242
|
-
router.post('/goals/reorder', auth.requireBuilder, auth.requirePermission('goal.reorder'), async (req, res) => {
|
|
242
|
+
router.post('/goals/reorder', auth.requireBuilder, auth.requirePermission('goal.reorder'), auth.requireActAlone('planning'), async (req, res) => {
|
|
243
243
|
if (validateOrRespond(req, res, {
|
|
244
244
|
version_id: { required: true, type: 'string', minLength: 1, maxLength: 64 },
|
|
245
245
|
ordered_ids: { required: true, type: 'array', maxItems: 500, itemsType: 'integer' },
|
|
@@ -425,7 +425,7 @@ module.exports = function buildGoalsRouter() {
|
|
|
425
425
|
// prompt writes into. Same permission as authoring a goal: extending the project's
|
|
426
426
|
// vocabulary is a planning act. A duplicate slug returns the existing row (200),
|
|
427
427
|
// so two people prompted at once converge instead of one being stranded.
|
|
428
|
-
router.post('/work-categories', auth.requireBuilder, auth.requirePermission('goal.create'),
|
|
428
|
+
router.post('/work-categories', auth.requireBuilder, auth.requirePermission('goal.create'), auth.requireActAlone('planning'),
|
|
429
429
|
goalCategory.categoryCreateHandler({ validateOrRespond, LIMITS, log }));
|
|
430
430
|
// PATCH /work-categories/:id — body { label?, sort_order?, active? }: rename a
|
|
431
431
|
// category, move it in the order, or stop offering it. The set has been list+add
|
|
@@ -440,7 +440,7 @@ module.exports = function buildGoalsRouter() {
|
|
|
440
440
|
// /goal-create prompt refer to. Deactivating never deletes — goals already carrying
|
|
441
441
|
// a retired category keep a resolvable label.
|
|
442
442
|
router.patch('/work-categories/:id', auth.requireBuilder,
|
|
443
|
-
auth.requirePermission('project.settings.manage'),
|
|
443
|
+
auth.requirePermission('project.settings.manage'), auth.requireActAlone('rules'),
|
|
444
444
|
goalCategory.categoryUpdateHandler({ validateOrRespond, LIMITS, parseId, log }));
|
|
445
445
|
|
|
446
446
|
// PATCH /goals/:id/category — body { category_id } (null clears it). Separate from
|
|
@@ -313,7 +313,7 @@ module.exports = function buildProjectSettingsRouter() {
|
|
|
313
313
|
// first and fitness.js caught it; that is why every gated route in the tree spells
|
|
314
314
|
// its atom inline. (Wrapping the middleware list across lines is fine — the sibling
|
|
315
315
|
// PATCH /work-categories/:id does exactly that and still classifies metic+archon.)
|
|
316
|
-
router.patch('/project-settings/rot-days', api.requireBuilder, api.requirePermission('project.settings.manage'), async (req, res) => {
|
|
316
|
+
router.patch('/project-settings/rot-days', api.requireBuilder, api.requirePermission('project.settings.manage'), api.requireActAlone('rules'), async (req, res) => {
|
|
317
317
|
// STRICT schema first (ADR 0118): it declares the only field this handler reads,
|
|
318
318
|
// so an unknown key is refused through the ADR 0116 envelope instead of being
|
|
319
319
|
// silently dropped. It settles the TYPE; parseDays below settles the RANGE, using
|
|
@@ -364,7 +364,7 @@ module.exports = function buildProjectSettingsRouter() {
|
|
|
364
364
|
// rank: project.settings.manage (metic+, government_010) — the same atom as the
|
|
365
365
|
// other writes on this page. Validated with the resolver's own parser, so the
|
|
366
366
|
// page, the CLI and the reader can never disagree about what a mode is.
|
|
367
|
-
router.patch('/project-settings/ship-mode', api.requireBuilder, api.requirePermission('project.settings.manage'), async (req, res) => {
|
|
367
|
+
router.patch('/project-settings/ship-mode', api.requireBuilder, api.requirePermission('project.settings.manage'), api.requireActAlone('rules'), async (req, res) => {
|
|
368
368
|
if (api.validateOrRespond(req, res, { mode: { required: true, type: 'string' } })) return;
|
|
369
369
|
const parsed = api.projectSettings.parseShipMode(req.body.mode);
|
|
370
370
|
if (!parsed.mode) {
|
|
@@ -1437,7 +1437,7 @@ module.exports = function buildTasksRouter() {
|
|
|
1437
1437
|
// caller; this route only renders its refusal (403 override_owner_only).
|
|
1438
1438
|
// Approving one confirms nothing and pays nothing — it stops the review holding
|
|
1439
1439
|
// the DEPLOY and leaves it open on the board.
|
|
1440
|
-
router.post('/override-requests/:id/decide', auth.requireBuilder, auth.requirePermission('override_request.decide'), async (req, res) => {
|
|
1440
|
+
router.post('/override-requests/:id/decide', auth.requireBuilder, auth.requirePermission('override_request.decide'), auth.requireActAlone('release'), async (req, res) => {
|
|
1441
1441
|
const id = parseId(req, res, { code: 'bad_request_id' });
|
|
1442
1442
|
if (id === null) return;
|
|
1443
1443
|
if (validateOrRespond(req, res, {
|
|
@@ -56,7 +56,7 @@ module.exports = function buildVersionsRouter() {
|
|
|
56
56
|
|
|
57
57
|
// rank: archon (atom version.focus.set, system) — body { version_id } sets the focus; { version_id: null } clears it.
|
|
58
58
|
// An unknown version id is refused rather than stored.
|
|
59
|
-
router.put('/versions/focus', auth.requireBuilder, auth.requirePermission('version.focus.set'), asyncHandler('PUT /versions/focus', async (req, res) => {
|
|
59
|
+
router.put('/versions/focus', auth.requireBuilder, auth.requirePermission('version.focus.set'), auth.requireActAlone('planning'), asyncHandler('PUT /versions/focus', async (req, res) => {
|
|
60
60
|
if (validateOrRespond(req, res, { version_id: { type: 'string', maxLength: 64 } })) return;
|
|
61
61
|
const raw = (req.body || {}).version_id;
|
|
62
62
|
if (raw === undefined || (raw !== null && !raw.trim())) return res.fail('bad_version_id', { status: 400, message: 'Pick a version to star, or send null to clear the focus.' });
|
|
@@ -36,6 +36,7 @@ module.exports = function buildNpmReleaseReleaseRouter({
|
|
|
36
36
|
'/npm-release/release',
|
|
37
37
|
api.requireBuilder,
|
|
38
38
|
api.requirePermission('core.pin.move'),
|
|
39
|
+
api.requireActAlone('release'),
|
|
39
40
|
async (req, res) => {
|
|
40
41
|
if (api.validateOrRespond(req, res, releaseSchema, { strict: true })) return undefined;
|
|
41
42
|
if (!VERSION_RE.test(req.body.version)) {
|