@bongos/core 1.19.813 → 1.19.814
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 +60 -30
- package/clients/bongos-client/bongos-client.global.js +1 -1
- package/clients/bongos-client/index.cjs +1 -1
- package/clients/bongos-client/index.d.ts +2 -3
- package/clients/bongos-client/index.mjs +1 -1
- package/docs/adr/0297-a-tasks-creator-may-retire-it.md +128 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +5 -28
- package/docs/api-reference.md +2 -2
- package/docs/canonical-permissions.md +30 -1
- package/docs/copy-inventory.md +49 -45
- package/docs/copy-registry.json +86 -50
- package/docs/module-api-changelog.md +2 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +1 -1
- package/modules/government/catalog.js +28 -1
- package/modules/government/migrations/government_015_task_abandon_own.sql +64 -0
- package/modules/hall-ui/public/task.js +50 -1
- package/modules/lifecycle/abandon-authz.js +132 -0
- package/modules/lifecycle/db-ship.js +59 -5
- package/modules/lifecycle/routes/abandon-handler.js +151 -0
- package/modules/lifecycle/routes/tasks.js +26 -38
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/route-rank-check.js +12 -1
- package/src/module-api.js +1 -1
- package/tests/abandon_creator_authz.mjs +176 -0
- package/tests/abandon_route.mjs +276 -0
- package/tests/abandon_task.mjs +145 -3
- package/tests/government_ownership_scope.mjs +13 -7
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.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.814",
|
|
6
|
+
"core_contract": "1.19.814",
|
|
7
|
+
"source_commit": "1f01e14964d5ea0ba6bc679b6545e33b7858829c",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-17T07:
|
|
9
|
+
"built_at": "2026-09-17T07:24:36.013Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 489,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2214,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2729,
|
|
20
|
+
"tree_sha256": "7a2511fd82d8a030199059a73c74669bc0c447fb5860e4eca010231226cd32a1",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -352,7 +352,7 @@
|
|
|
352
352
|
{
|
|
353
353
|
"path": "clients/bongos-client/bongos-client.global.js",
|
|
354
354
|
"mode": "0000644",
|
|
355
|
-
"sha256": "
|
|
355
|
+
"sha256": "5753830b87cce7612cb6c6799ea3807fa5c2e9eaf1b1e5f137af21fb3692a3c4"
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"path": "clients/bongos-client/examples/hello-world.mjs",
|
|
@@ -362,17 +362,17 @@
|
|
|
362
362
|
{
|
|
363
363
|
"path": "clients/bongos-client/index.cjs",
|
|
364
364
|
"mode": "0000644",
|
|
365
|
-
"sha256": "
|
|
365
|
+
"sha256": "233ade028d8fb2334e0cd2e17d10d695a5c873a8ebe5589407cccac6dd023bd5"
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/index.d.ts",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "e713625480afabdcda10f665beabb91cccaa3bce0268371a6ee780daea626b6d"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.mjs",
|
|
374
374
|
"mode": "0000644",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "e8592cf9783404ce849e1c234a06c716993ef9c9f4d5b066a456eeeec002b0c0"
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"path": "clients/bongos-client/package.json",
|
|
@@ -1969,20 +1969,25 @@
|
|
|
1969
1969
|
"mode": "0000644",
|
|
1970
1970
|
"sha256": "440116d384964c06055b1c7c07c30a962b164f519c9bbf3ce910570d186f1308"
|
|
1971
1971
|
},
|
|
1972
|
+
{
|
|
1973
|
+
"path": "docs/adr/0297-a-tasks-creator-may-retire-it.md",
|
|
1974
|
+
"mode": "0000644",
|
|
1975
|
+
"sha256": "4e6d5268c58e905c86acc1bbd4c66778e3695ecd37cbc1c9e0f1448cd67f26a9"
|
|
1976
|
+
},
|
|
1972
1977
|
{
|
|
1973
1978
|
"path": "docs/adr/README.md",
|
|
1974
1979
|
"mode": "0000644",
|
|
1975
|
-
"sha256": "
|
|
1980
|
+
"sha256": "9e305c448d390efbfd7f4b5521284a8dc452d43a106d4686a0176433e2d1a3a7"
|
|
1976
1981
|
},
|
|
1977
1982
|
{
|
|
1978
1983
|
"path": "docs/api-reference.md",
|
|
1979
1984
|
"mode": "0000644",
|
|
1980
|
-
"sha256": "
|
|
1985
|
+
"sha256": "16b812b3d5df71ecd9c8908fcd724345cee9ebe36bdf445fedd623e8d3572498"
|
|
1981
1986
|
},
|
|
1982
1987
|
{
|
|
1983
1988
|
"path": "docs/api/openapi.json",
|
|
1984
1989
|
"mode": "0000644",
|
|
1985
|
-
"sha256": "
|
|
1990
|
+
"sha256": "d062ab3925ae7919f1f0e32a2da4974cae41ee409d914ea6fb49bfd3591b91d2"
|
|
1986
1991
|
},
|
|
1987
1992
|
{
|
|
1988
1993
|
"path": "docs/architecture.md",
|
|
@@ -2002,17 +2007,17 @@
|
|
|
2002
2007
|
{
|
|
2003
2008
|
"path": "docs/canonical-permissions.md",
|
|
2004
2009
|
"mode": "0000644",
|
|
2005
|
-
"sha256": "
|
|
2010
|
+
"sha256": "bbc4878ab6e3a0cedf5fad1b197d427b44d89ea2d32fd8dce9babfe73da09ee1"
|
|
2006
2011
|
},
|
|
2007
2012
|
{
|
|
2008
2013
|
"path": "docs/copy-inventory.md",
|
|
2009
2014
|
"mode": "0000644",
|
|
2010
|
-
"sha256": "
|
|
2015
|
+
"sha256": "b0fb481aa86ab8ad8b2bf37bfdf09e6aba00f611156d92002632384cddc40cb2"
|
|
2011
2016
|
},
|
|
2012
2017
|
{
|
|
2013
2018
|
"path": "docs/copy-registry.json",
|
|
2014
2019
|
"mode": "0000644",
|
|
2015
|
-
"sha256": "
|
|
2020
|
+
"sha256": "a21e7da139c5593f452509692e3f17b945f99eb5013f9a0e7ec4ceedc8c8b50e"
|
|
2016
2021
|
},
|
|
2017
2022
|
{
|
|
2018
2023
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2537,7 +2542,7 @@
|
|
|
2537
2542
|
{
|
|
2538
2543
|
"path": "docs/module-api-changelog.md",
|
|
2539
2544
|
"mode": "0000644",
|
|
2540
|
-
"sha256": "
|
|
2545
|
+
"sha256": "2e01c98f75c74643cc843b80f507635d1d674749fed9185414cfce51eeac3cf8"
|
|
2541
2546
|
},
|
|
2542
2547
|
{
|
|
2543
2548
|
"path": "docs/modules-contract.md",
|
|
@@ -4072,7 +4077,7 @@
|
|
|
4072
4077
|
{
|
|
4073
4078
|
"path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
|
|
4074
4079
|
"mode": "0000644",
|
|
4075
|
-
"sha256": "
|
|
4080
|
+
"sha256": "233ade028d8fb2334e0cd2e17d10d695a5c873a8ebe5589407cccac6dd023bd5"
|
|
4076
4081
|
},
|
|
4077
4082
|
{
|
|
4078
4083
|
"path": "modules/dev-box/app/test/unit.mjs",
|
|
@@ -4322,7 +4327,7 @@
|
|
|
4322
4327
|
{
|
|
4323
4328
|
"path": "modules/government/catalog.js",
|
|
4324
4329
|
"mode": "0000644",
|
|
4325
|
-
"sha256": "
|
|
4330
|
+
"sha256": "409e2309bdfcf4b3bb60cfde69e8854af9cd280a205cca2f7640103e08a56c84"
|
|
4326
4331
|
},
|
|
4327
4332
|
{
|
|
4328
4333
|
"path": "modules/government/config.js",
|
|
@@ -4444,6 +4449,11 @@
|
|
|
4444
4449
|
"mode": "0000644",
|
|
4445
4450
|
"sha256": "0ddff91ec6d4c1f4344c6fdc2bd2f158e34a9b14662a100cf73796924b7c98de"
|
|
4446
4451
|
},
|
|
4452
|
+
{
|
|
4453
|
+
"path": "modules/government/migrations/government_015_task_abandon_own.sql",
|
|
4454
|
+
"mode": "0000644",
|
|
4455
|
+
"sha256": "d376d2532b942c77a8ad71f6bdbeea389db3276ee86a6e57a58eed41439b6bdd"
|
|
4456
|
+
},
|
|
4447
4457
|
{
|
|
4448
4458
|
"path": "modules/government/module.json",
|
|
4449
4459
|
"mode": "0000644",
|
|
@@ -5327,7 +5337,7 @@
|
|
|
5327
5337
|
{
|
|
5328
5338
|
"path": "modules/hall-ui/public/task.js",
|
|
5329
5339
|
"mode": "0000644",
|
|
5330
|
-
"sha256": "
|
|
5340
|
+
"sha256": "49cd43aa6da51f6b30efac92e27912b44589c22cf879ef62b4201ed8e8552996"
|
|
5331
5341
|
},
|
|
5332
5342
|
{
|
|
5333
5343
|
"path": "modules/hall-ui/public/task.states.json",
|
|
@@ -5469,6 +5479,11 @@
|
|
|
5469
5479
|
"mode": "0000644",
|
|
5470
5480
|
"sha256": "7cb9dac9cb87b679e1ae50e4112b3a7928a38ebb9902c8947a74d17bec4d9009"
|
|
5471
5481
|
},
|
|
5482
|
+
{
|
|
5483
|
+
"path": "modules/lifecycle/abandon-authz.js",
|
|
5484
|
+
"mode": "0000644",
|
|
5485
|
+
"sha256": "4aad5d4d75fb329ae721b06840248d03c47159c632f14e22c2566ded36c158f8"
|
|
5486
|
+
},
|
|
5472
5487
|
{
|
|
5473
5488
|
"path": "modules/lifecycle/abandon-leftovers.js",
|
|
5474
5489
|
"mode": "0000644",
|
|
@@ -5567,7 +5582,7 @@
|
|
|
5567
5582
|
{
|
|
5568
5583
|
"path": "modules/lifecycle/db-ship.js",
|
|
5569
5584
|
"mode": "0000644",
|
|
5570
|
-
"sha256": "
|
|
5585
|
+
"sha256": "19e70254936af3f51fa12d3b9aa9d25dbc0a84761aee84b5143ebfe6a8654b30"
|
|
5571
5586
|
},
|
|
5572
5587
|
{
|
|
5573
5588
|
"path": "modules/lifecycle/db-tasks.js",
|
|
@@ -5739,6 +5754,11 @@
|
|
|
5739
5754
|
"mode": "0000644",
|
|
5740
5755
|
"sha256": "74ca9df69cd8d119c59cccf5976a3ab99c8cc1e85414488ad432c445ddd821e2"
|
|
5741
5756
|
},
|
|
5757
|
+
{
|
|
5758
|
+
"path": "modules/lifecycle/routes/abandon-handler.js",
|
|
5759
|
+
"mode": "0000644",
|
|
5760
|
+
"sha256": "64e1baa060af63f7242d7164c7e9711aa1230ef003d1aa3f51c91f392e840725"
|
|
5761
|
+
},
|
|
5742
5762
|
{
|
|
5743
5763
|
"path": "modules/lifecycle/routes/analytics.js",
|
|
5744
5764
|
"mode": "0000644",
|
|
@@ -5832,7 +5852,7 @@
|
|
|
5832
5852
|
{
|
|
5833
5853
|
"path": "modules/lifecycle/routes/tasks.js",
|
|
5834
5854
|
"mode": "0000644",
|
|
5835
|
-
"sha256": "
|
|
5855
|
+
"sha256": "06948b8df847145848dec75dcb3c525cecd24948064eae19ae524e0498576b2c"
|
|
5836
5856
|
},
|
|
5837
5857
|
{
|
|
5838
5858
|
"path": "modules/lifecycle/routes/version-route-authz.js",
|
|
@@ -7772,12 +7792,12 @@
|
|
|
7772
7792
|
{
|
|
7773
7793
|
"path": "package-lock.json",
|
|
7774
7794
|
"mode": "0000644",
|
|
7775
|
-
"sha256": "
|
|
7795
|
+
"sha256": "bc7f846af00793ffc9d682071391f110593648a25297cd15050385b9c3a8828f"
|
|
7776
7796
|
},
|
|
7777
7797
|
{
|
|
7778
7798
|
"path": "package.json",
|
|
7779
7799
|
"mode": "0000644",
|
|
7780
|
-
"sha256": "
|
|
7800
|
+
"sha256": "db09d4c1609d9b4b0547b052326785367cfe19a5838cd3f9d458dac40ef70685"
|
|
7781
7801
|
},
|
|
7782
7802
|
{
|
|
7783
7803
|
"path": "public-docs/index.html",
|
|
@@ -9532,7 +9552,7 @@
|
|
|
9532
9552
|
{
|
|
9533
9553
|
"path": "src/bongos/route-rank-check.js",
|
|
9534
9554
|
"mode": "0000644",
|
|
9535
|
-
"sha256": "
|
|
9555
|
+
"sha256": "909c8ea1b1f73ece215ada3cc41488590a0558879af7b3850e55cbf2729b719c"
|
|
9536
9556
|
},
|
|
9537
9557
|
{
|
|
9538
9558
|
"path": "src/bongos/routes.js",
|
|
@@ -9667,7 +9687,7 @@
|
|
|
9667
9687
|
{
|
|
9668
9688
|
"path": "src/module-api.js",
|
|
9669
9689
|
"mode": "0000644",
|
|
9670
|
-
"sha256": "
|
|
9690
|
+
"sha256": "2b104850c999f4a9c71f7f205c640b9525a9a812031c1bad6d13cdcd06c05706"
|
|
9671
9691
|
},
|
|
9672
9692
|
{
|
|
9673
9693
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9719,15 +9739,25 @@
|
|
|
9719
9739
|
"mode": "0000644",
|
|
9720
9740
|
"sha256": "2e46d071e5c61d302f34507215dd0a5f20a0887841ae0f7b033be865c2cb8af5"
|
|
9721
9741
|
},
|
|
9742
|
+
{
|
|
9743
|
+
"path": "tests/abandon_creator_authz.mjs",
|
|
9744
|
+
"mode": "0000644",
|
|
9745
|
+
"sha256": "fb0611fe900cb213344b52c50c5f28543b5f5986b4a227cf4fcb380512a3a167"
|
|
9746
|
+
},
|
|
9722
9747
|
{
|
|
9723
9748
|
"path": "tests/abandon_leftovers.mjs",
|
|
9724
9749
|
"mode": "0000644",
|
|
9725
9750
|
"sha256": "fe9562bd27fc127f5113de4316d1abe784874aa198d87be79caa302d526ecf17"
|
|
9726
9751
|
},
|
|
9752
|
+
{
|
|
9753
|
+
"path": "tests/abandon_route.mjs",
|
|
9754
|
+
"mode": "0000644",
|
|
9755
|
+
"sha256": "523fe88b9582153d56c0867dc2973fb89a6b9dbc010066e57ca8dba2265a0b6d"
|
|
9756
|
+
},
|
|
9727
9757
|
{
|
|
9728
9758
|
"path": "tests/abandon_task.mjs",
|
|
9729
9759
|
"mode": "0000644",
|
|
9730
|
-
"sha256": "
|
|
9760
|
+
"sha256": "bcf19d27a76fd9fbafc3791f6a46d9636453427dc1c50940069673781eb817bc"
|
|
9731
9761
|
},
|
|
9732
9762
|
{
|
|
9733
9763
|
"path": "tests/access_requests_invite.mjs",
|
|
@@ -11227,7 +11257,7 @@
|
|
|
11227
11257
|
{
|
|
11228
11258
|
"path": "tests/government_ownership_scope.mjs",
|
|
11229
11259
|
"mode": "0000644",
|
|
11230
|
-
"sha256": "
|
|
11260
|
+
"sha256": "a05a1aaec7b6924bc1653307f921b16ec9517191bf51956c968c4d53eec0e988"
|
|
11231
11261
|
},
|
|
11232
11262
|
{
|
|
11233
11263
|
"path": "tests/government_page_gates.mjs",
|
|
@@ -899,7 +899,7 @@ function createClient(opts = {}) {
|
|
|
899
899
|
getTasksId: (args) => request("GET", "/tasks/{id}", { hasBody: false }, args),
|
|
900
900
|
// PATCH /tasks/{id} — rank: metic+archon — PATCH /tasks/:id
|
|
901
901
|
patchTasksId: (args) => request("PATCH", "/tasks/{id}", { hasBody: true }, args),
|
|
902
|
-
// POST /tasks/{id}/abandon — rank:
|
|
902
|
+
// POST /tasks/{id}/abandon — rank: any-builder — POST /tasks/:id/abandon
|
|
903
903
|
postTasksIdAbandon: (args) => request("POST", "/tasks/{id}/abandon", { hasBody: true }, args),
|
|
904
904
|
// POST /tasks/{id}/attest-gate — rank: any-builder — POST /tasks/:id/attest-gate
|
|
905
905
|
postTasksIdAttestGate: (args) => request("POST", "/tasks/{id}/attest-gate", { hasBody: true }, args),
|
|
@@ -898,7 +898,7 @@ function createClient(opts = {}) {
|
|
|
898
898
|
getTasksId: (args) => request("GET", "/tasks/{id}", { hasBody: false }, args),
|
|
899
899
|
// PATCH /tasks/{id} — rank: metic+archon — PATCH /tasks/:id
|
|
900
900
|
patchTasksId: (args) => request("PATCH", "/tasks/{id}", { hasBody: true }, args),
|
|
901
|
-
// POST /tasks/{id}/abandon — rank:
|
|
901
|
+
// POST /tasks/{id}/abandon — rank: any-builder — POST /tasks/:id/abandon
|
|
902
902
|
postTasksIdAbandon: (args) => request("POST", "/tasks/{id}/abandon", { hasBody: true }, args),
|
|
903
903
|
// POST /tasks/{id}/attest-gate — rank: any-builder — POST /tasks/:id/attest-gate
|
|
904
904
|
postTasksIdAttestGate: (args) => request("POST", "/tasks/{id}/attest-gate", { hasBody: true }, args),
|
|
@@ -399,7 +399,6 @@ export interface PostSsoTokenRequest { client_id: string; client_secret: string;
|
|
|
399
399
|
export interface PostSsoTokenResponse { github_id: unknown; github_login: unknown; display_name: unknown; avatar_url: unknown }
|
|
400
400
|
export interface PostTaskRecommendationsRequest { task_id: number; recommended_to: number; reason: string }
|
|
401
401
|
export interface PostTaskRecommendationsResponse { ok: boolean; recommendation: unknown }
|
|
402
|
-
export interface PostTasksIdAbandonRequest { reason?: string }
|
|
403
402
|
export interface PostTasksIdAttestGateRequest { head_sha: string }
|
|
404
403
|
export interface PostTasksIdAttestGateResponse { ok: boolean; attested: unknown; reason: unknown }
|
|
405
404
|
export interface PostTasksIdConfirmRequest { verified?: unknown }
|
|
@@ -1262,8 +1261,8 @@ export interface BongosClient {
|
|
|
1262
1261
|
getTasksId(args?: RequestArgs): Promise<ApiResponse>;
|
|
1263
1262
|
/** PATCH /tasks/{id} — rank: metic+archon */
|
|
1264
1263
|
patchTasksId(args?: RequestArgs & { body?: PatchTasksIdRequest }): Promise<ApiResponse>;
|
|
1265
|
-
/** POST /tasks/{id}/abandon — rank:
|
|
1266
|
-
postTasksIdAbandon(args?: RequestArgs
|
|
1264
|
+
/** POST /tasks/{id}/abandon — rank: any-builder */
|
|
1265
|
+
postTasksIdAbandon(args?: RequestArgs): Promise<ApiResponse>;
|
|
1267
1266
|
/** POST /tasks/{id}/attest-gate — rank: any-builder */
|
|
1268
1267
|
postTasksIdAttestGate(args: RequestArgs & { body: PostTasksIdAttestGateRequest }): Promise<PostTasksIdAttestGateResponse>;
|
|
1269
1268
|
/** POST /tasks/{id}/confirm — rank: any-builder */
|
|
@@ -895,7 +895,7 @@ export function createClient(opts = {}) {
|
|
|
895
895
|
getTasksId: (args) => request("GET", "/tasks/{id}", { hasBody: false }, args),
|
|
896
896
|
// PATCH /tasks/{id} — rank: metic+archon — PATCH /tasks/:id
|
|
897
897
|
patchTasksId: (args) => request("PATCH", "/tasks/{id}", { hasBody: true }, args),
|
|
898
|
-
// POST /tasks/{id}/abandon — rank:
|
|
898
|
+
// POST /tasks/{id}/abandon — rank: any-builder — POST /tasks/:id/abandon
|
|
899
899
|
postTasksIdAbandon: (args) => request("POST", "/tasks/{id}/abandon", { hasBody: true }, args),
|
|
900
900
|
// POST /tasks/{id}/attest-gate — rank: any-builder — POST /tasks/:id/attest-gate
|
|
901
901
|
postTasksIdAttestGate: (args) => request("POST", "/tasks/{id}/attest-gate", { hasBody: true }, args),
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# ADR 0297 — A task's creator may retire it, and that is the ownership axis's first route
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · **Date:** 2026-09-16 · **Task:** [task 1002443](https://cloudbongos.com/builders#/task/1002443) (goal 1000095)
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
The owner, 2026-07-25: *"Each task should have a creator, the creator of that task
|
|
8
|
+
should be able to abandon that task directly inside of the UI."*
|
|
9
|
+
|
|
10
|
+
The first half shipped elsewhere and earlier. `migrations/core_227_task_created_by.sql`
|
|
11
|
+
([task 1003278](https://cloudbongos.com/builders#/task/1003278), goal 1000072) added
|
|
12
|
+
`tasks.created_by` — nullable, no backfill, indexed, with the two `SELECT t.*` views
|
|
13
|
+
rebuilt so the column actually reaches the claimable feed — and `createTask` has
|
|
14
|
+
threaded the caller through ever since. This ADR is only about the second half, which
|
|
15
|
+
had not moved: **retiring a task was a rank power and nothing else.**
|
|
16
|
+
|
|
17
|
+
`POST /tasks/:id/abandon` gated on `task.abandon`, floor `metic`
|
|
18
|
+
([ADR 0152](0152-metic-task-abandonment.md), archon before that under
|
|
19
|
+
[ADR 0090](0090-metic-task-authoring.md)). So a builder below Metic could file a
|
|
20
|
+
task and then could not withdraw it. They had to ask someone with triage authority to
|
|
21
|
+
retire a proposal that was theirs in the first place — the same shape as the goal
|
|
22
|
+
membership problem [ADR 0106](0106-goal-owner-manager-authority.md) fixed, and
|
|
23
|
+
for the same reason: **the rule is about whose row it is, and a rank cannot say that.**
|
|
24
|
+
|
|
25
|
+
The permission model had already grown the vocabulary for this and had never used it.
|
|
26
|
+
[ADR 0151](0151-governance-permissions-as-atom-ranks-as-roles.md) §3 defines an **ownership axis** —
|
|
27
|
+
a key declared `scope: 'own'` is *parameterized by a concrete row*, so holding it
|
|
28
|
+
authorizes nothing until a handler that has loaded that row supplies its owner. R100
|
|
29
|
+
made `requirePermission` refuse an own-scoped key outright, precisely so `.own` could
|
|
30
|
+
never degrade into `.any`, and recorded that **zero call sites passed one**. The
|
|
31
|
+
resolver shipped `authorizeOwned` / `authorizeOwnedOrAny`; six keys were declared
|
|
32
|
+
own-scoped; not one route decided anything with them. This is that axis's first real
|
|
33
|
+
consumer.
|
|
34
|
+
|
|
35
|
+
## Decision
|
|
36
|
+
|
|
37
|
+
**Two authorities reach the abandon transition, and only one of them is rank-shaped.**
|
|
38
|
+
|
|
39
|
+
| key | floor | reaches |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `task.abandon` | `metic` | any task, any creator — triage authority, unchanged from ADR 0152 |
|
|
42
|
+
| `task.abandon.own` | `xenos` | a task you created, while it is still `backlog` / `ready` / `blocked` |
|
|
43
|
+
|
|
44
|
+
The route is therefore `requireBuilder` plus an in-handler decision — the shape
|
|
45
|
+
`POST /goals/:id/members`, `PATCH /goals/:id/members/:builderId` and
|
|
46
|
+
`PATCH /inbox/:id/body` already use, and the shape `requirePermission` structurally
|
|
47
|
+
cannot express. Its rank pin moves `metic+archon` → `any-builder`, whose job becomes
|
|
48
|
+
catching an accidental **tightening** back to a rank floor as loudly as it catches a
|
|
49
|
+
gate removal.
|
|
50
|
+
|
|
51
|
+
**The two facts are resolved in two different places, on purpose.** Authority here is a
|
|
52
|
+
conjunction drawn from two sources, and folding them together loses one:
|
|
53
|
+
|
|
54
|
+
- the **rank fact** — does this builder hold the key — is request-scoped, so it resolves
|
|
55
|
+
in the route before the transaction opens: once, uncached, per
|
|
56
|
+
[ADR 0016](0016-trust-boundary-server-enforced-permissions.md);
|
|
57
|
+
- the **owner fact** — is this row theirs — is a column that can change under us, so it
|
|
58
|
+
is evaluated *inside* `abandonTask`'s existing `FOR UPDATE` transaction, through a new
|
|
59
|
+
optional `authorize` callback. `created_by` joins the locked `SELECT`, so the predicate
|
|
60
|
+
and the `UPDATE` read the same row version.
|
|
61
|
+
|
|
62
|
+
Reading `created_by` in the route would be a TOCTOU window — the task can be
|
|
63
|
+
re-attributed, claimed or shipped between the check and the write. Resolving permissions
|
|
64
|
+
inside the transaction would hold a row lock across a second pool acquire, which under a
|
|
65
|
+
saturated pool is a deadlock. Splitting them is what makes the decision both **fresh**
|
|
66
|
+
and **race-free**, and it is why the callback is documented as pure and DB-free rather
|
|
67
|
+
than merely happening to be.
|
|
68
|
+
|
|
69
|
+
**The decision itself is a pure module**, `modules/lifecycle/abandon-authz.js`, following
|
|
70
|
+
`goal-route-authz.js` and `shouldRefuseSessionClaim`. Every branch is pinned by a
|
|
71
|
+
DB-free unit test instead of only through a live route, and the hall's display hint and
|
|
72
|
+
the server's verdict are demonstrably reading one rule.
|
|
73
|
+
|
|
74
|
+
**A creator's window is narrower than a Metic's, and stops at the grader.**
|
|
75
|
+
`backlog` / `ready` / `blocked` only. Past that a retire is a correction to the ledger
|
|
76
|
+
rather than an author changing their mind, so `completed` and `confirmed` stay with the
|
|
77
|
+
cross-owner authority. `active` is excluded here and again by the active-claim guard.
|
|
78
|
+
Nothing is subtracted from `task.abandon`: a Metic keeps every status they could reach
|
|
79
|
+
before, and a test pins that the new window never becomes a ceiling on the old power.
|
|
80
|
+
|
|
81
|
+
**`counterpart: ['task.abandon']` is declared, not derived.** Same `act` verb, same
|
|
82
|
+
`task` resource, so the cross-owner key genuinely subsumes retiring your own and
|
|
83
|
+
`authorizeOwnedOrAny` may admit on it without consulting the owner. The verb is `act`
|
|
84
|
+
rather than `manage` deliberately: `manage` is the full-control verb and would silently
|
|
85
|
+
dominate every future narrower `task.*.own` key — the verb-blind escalation
|
|
86
|
+
[task 1003195](https://cloudbongos.com/builders#/task/1003195) closed, where a
|
|
87
|
+
`memory.read.any` grant satisfied a `memory.manage.own` write.
|
|
88
|
+
|
|
89
|
+
## Consequences
|
|
90
|
+
|
|
91
|
+
**A null creator belongs to nobody, and that is load-bearing.** Every task filed before
|
|
92
|
+
`core_227` (~1,357 of them) plus the seed, restock and Discord-bug vectors carry
|
|
93
|
+
`created_by IS NULL`. The owner comparison is the government resolver's own — injected,
|
|
94
|
+
never re-implemented, because a duplicated ownership comparison is exactly how one
|
|
95
|
+
quietly widens — and it matches `null`, `''`, objects and `NaN` against nothing. Were
|
|
96
|
+
that to slip, one xenos-floor grant would open every historical task at once. It is the
|
|
97
|
+
single widest failure mode here and has its own test.
|
|
98
|
+
|
|
99
|
+
**The hall shows the control to a creator, and pre-answers the refusals in words.** A
|
|
100
|
+
task's creator sees "Retire this task…" on their own record; outside the window they are
|
|
101
|
+
told why rather than shown a button that would 403. The list of statuses is restated in
|
|
102
|
+
the browser file because it cannot `require` the server's, and is marked as a display
|
|
103
|
+
mirror the server re-decides under a lock.
|
|
104
|
+
|
|
105
|
+
**Fail-closed at every new branch.** A missing `government` port denies (its absence
|
|
106
|
+
means authority cannot be *established*, and admitting there would make disabling a
|
|
107
|
+
module a privilege escalation — the inversion ADR 0016 exists to prevent); a throwing
|
|
108
|
+
resolver denies; anything that is not an explicit `{ ok: true }` denies; `authorize`
|
|
109
|
+
runs *before* the already-abandoned idempotent return and before the shipped and
|
|
110
|
+
active-claim guards, so an unauthorized caller cannot probe a row's state through a
|
|
111
|
+
200 or distinguish the 409s.
|
|
112
|
+
|
|
113
|
+
**No guard was removed.** Shipped stays immutable, an active claim still has to be
|
|
114
|
+
released first, the re-abandon is still idempotent, `/promote` still restores, and the
|
|
115
|
+
audit_log entry is unchanged on both paths.
|
|
116
|
+
|
|
117
|
+
**What this does not do.** It does not give creators any other power over their own
|
|
118
|
+
tasks — editing, promoting and dependency management keep their Metic floor, and
|
|
119
|
+
widening those is a separate decision. It does not touch `task.merge`, which reuses
|
|
120
|
+
`abandonTask` under the hood but is a triage judgement about duplicates rather than
|
|
121
|
+
authorship. And it does not backfill attribution: the control simply does not appear on
|
|
122
|
+
tasks that predate `core_227`, which is the honest outcome of that migration's own
|
|
123
|
+
refusal to guess.
|
|
124
|
+
|
|
125
|
+
**Revisit if** a second `task.*.own` key appears (the `act` verb choice is what keeps
|
|
126
|
+
that safe, and a wider relation would need a `VERB_DOMINANCE` entry stating why), or if
|
|
127
|
+
goal leadership should confer shaping authority — deliberately left conferring nothing
|
|
128
|
+
here, as [task 1002858](https://cloudbongos.com/builders#/task/1002858) also left it.
|
package/docs/adr/README.md
CHANGED
|
@@ -388,3 +388,4 @@ This keeps the decision history honest and traceable.
|
|
|
388
388
|
| 0294 | [**CLI sessions do not expire; revocation is the whole remedy** ([task 1003956](https://cloudbongos.com/builders#/task/1003956), goal 1000110 — owner request 2026-09-16, after a three-week gap signed him out). A `bongos` sign-in died after 24h idle: `createSession` defaulted a null `expiresAt` to `now()+SESSION_TTL_MS` and `lookupSession` rewrote `expires_at = now()+24h` on every request. **This is the third argument about the number and the first about the mechanism** — V3.R38 ([task #259](https://cloudbongos.com/builders#/task/259), migration 035) set 1h, V3.R90 ([task 1000400](https://cloudbongos.com/builders#/task/1000400)) widened it to 24h because 1h "kicked builders out over lunch", and each round shortened the nuisance while leaving the mechanism alone. The hygiene case for keeping it (criterion C9 — bound a leaked token in the mode-0600 plaintext session file) **does not survive contact with refresh-on-use**: a leaked token is by definition one somebody is using, and every use rolled the expiry forward another day, so the timer could only ever fire on a session gone quiet — the legitimate holder stepping away. The control was aimed at attackers and landed exclusively on users. [ADR 0206](0206-own-scoped-session-revocation-no-absolute-ceiling.md) had already written half of this down ("a CLI bearer in daily use is, in practice, immortal") without drawing the conclusion. Decision: **CLI sessions carry no expiry and last until revoked.** `createSession` records `expires_at = NULL` when the caller names none — every such call site is `source: 'cli'` (device flow, federated sign-in, hall re-issue, dev-box pair) — and `lookupSession` still bumps `last_used_at` but rolls `expires_at` forward only via `CASE WHEN expires_at IS NULL THEN NULL ELSE …`; **without that CASE the first request after sign-in re-mints the very timeout this removes.** Migration `core_243` clears `expires_at` on live unrevoked `source='cli'` rows, so the change reaches existing sessions instead of charging every builder one last forced re-auth on deploy day — migration 035's grace inverted, for the reason 035 gave. **Browser sessions are untouched** (they pass an explicit 30 days, so the new default never reaches them, and the migration's `source='cli'` filter is its whole safety property — `lookupSession` only rolls an expiry it can already see, so a web row cleared by mistake stays cleared forever). `SESSION_TTL_MS` survives as the roll-forward window for rows that have an expiry, no longer as a default. The accepted cost, stated plainly: **a leaked CLI token is valid until revoked** — smaller in practice than it sounds, since under refresh-on-use an actively-used leak was already immortal, so what changes is the *abandoned* copy on a dead laptop. The remedy is ADR 0206's own-scoped revoke, immediate where a TTL is next-day, and that ADR's visibility half (`created_at` beside `last_used_at`) is now the primary control rather than a nice-to-have. Nothing about rank moves: `setBuilderRank` still sweeps on demotion and `requireBuilder` still refuses a deactivated builder on the hot path, neither consulting `expires_at`. Rejected: widening the TTL a fourth time; keeping it and making re-auth cheaper (already built — [task 1000401](https://cloudbongos.com/builders#/task/1000401), [task 1000504](https://cloudbongos.com/builders#/task/1000504) — the friction shrank, the interruption stayed); an absolute `created_at` ceiling (ADR 0206's reasoning holds unchanged); dropping the column (load-bearing for web); a blanket `UPDATE … SET expires_at = NULL`. Revisit for machine principals nobody will notice a leak on, a compliance maximum, or long-dead sessions accumulating faster than people prune them.](0294-cli-sessions-do-not-expire.md) | auth / account security / CLI |
|
|
389
389
|
| 0295 | [**The registry is the witness the publish lane cannot be** ([task 1003916](https://cloudbongos.com/builders#/task/1003916), goal 1000090). On 2026-09-13 eight publish runs failed over three hours and NOTHING reported it; it was found by a person idly reading the npm version list. Task 1003914 fixed the lossiness and left the silence, which is structural: a failed run is one red row nobody watches, `main` self-heals on the next merge and **erases the evidence**, and the lane's own verdict step writes to a job summary no one reads. Same shape as the nightly upgrade sweep that died reporting `failed=0`. **Decision: a separate reader compares what the lane ANNOUNCED (release commits) against what the registry HOLDS**; the difference is a phantom. The publisher infers from git because OIDC trusted publishing carries no read credential — **that never bound a reader**, and since task 1003872 the package is public, so an anonymous read needs none. Keeping witness and publisher separate IS the point. Daily not hourly (billable minutes, against a failure measured in hours), cadence pinned to the cron by `routine-schedule.js`; deliberately NOT on the session-start path (a third-party call behind a hook timeout). Healthy days are SILENT in Discord — findings to stdout, the all-clear to stderr — because a channel of reassurances stops being read. Two false-alarm sources, each guarded: a closed incident is permanent history (a 3-day window, by TIME not commit count, which a release burst would outrun), and **npm acknowledges a publish before its listing shows it** — found by RUNNING the check, which flagged 1.19.794 fifteen minutes after a publish whose log ended `published @bongos/core@1.19.794` and whose per-version endpoint answered 200; hence a 30-minute grace period plus per-version confirmation, with the listing only ever nominating suspects. A failed read throws rather than reading as "nothing missing". Proven by replaying the REAL outage from git + the live registry, not a synthetic failure. Does NOT detect a lane that stopped being triggered.](0295-the-registry-is-the-witness-the-lane-cannot-be.md) | release / observability |
|
|
390
390
|
| 0296 | [**A role speciality is data over three existing surfaces, not a fourth mechanism** ([task 1003855](https://cloudbongos.com/builders#/task/1003855), goal 1000095). A craft says HOW you work; nothing said what you work ON. A speciality is a named bundle of knowledge, skills and an interaction contract sitting under a craft — an ideator with an Economics speciality. **Decision: it is DATA, not an ADR 0083 code module** — a subject-matter expert authors one, and requiring them to ship a package puts the mechanism out of reach of exactly the person it exists for. It layers over three surfaces that already exist (discipline-modes.json, the `behavior_prefs.interaction` contract path, the manifest contribution seam); only the knowledge bundle is new. The contract **rides the existing GET /me -> Conductor injection path rather than adding a second one**, and is CRAFT-SCOPED: the hook injects only the contract matching the claim in hand (which needed `t.discipline` on the active-claims read), and with no claim there is no craft and **nothing is injected** — silence beats guessing which expertise applies. Training is two-fold per the owner's 2026-09-17 ruling: documents they supplied, plus learnings captured from sessions worked in it (pointing at existing `learnings` rows, never a second corpus). The corpus is **inventoried, never inlined** — a document may run to 100k chars — so `speciality_documents` joins the recall ingester, because otherwise the contract's "search it with /recall" is a lie whose failure is silent. **`sellable` is recorded with no storefront behind it**: ADR 0107 defers the marketplace and explicitly rejected building one, so the flag captures the owner's decision when they make it and grants nothing — a deliberate partial implementation, recorded rather than silently made. Index scoping errs toward UNDER-sharing: only a public speciality's documents are corpus-wide, because `project` names a set the index cannot express.](0296-a-role-speciality-is-data-over-three-existing-surfaces.md) | roles / modules |
|
|
391
|
+
| 0297 | [**A task’s creator may retire it, and that is the ownership axis’s first route** ([task 1002443](https://cloudbongos.com/builders#/task/1002443), goal 1000095 — owner request 2026-07-25: *"the creator of that task should be able to abandon that task directly inside of the UI"*). Attribution itself shipped earlier and elsewhere (`core_227`, [task 1003278](https://cloudbongos.com/builders#/task/1003278)); what had not moved is that **retiring a task was a rank power and nothing else** — `task.abandon` floors at `metic` (ADR 0152), so a sub-Metic builder could file a task and then could not withdraw their own proposal. The model already had the vocabulary and had never used it: ADR 0151 §3 defines an **ownership axis** where a `scope:'own'` key authorizes nothing until a handler supplies the row's owner, R100 made `requirePermission` refuse such a key outright so `.own` can never degrade into `.any`, and it recorded that **zero call sites passed one**. This is that axis’s first real consumer. **Decision: two authorities, only one rank-shaped** — `task.abandon` (metic, any task) OR new `task.abandon.own` (xenos, a task you created, while it is still backlog/ready/blocked). Route becomes `requireBuilder` + an in-handler decision (the `POST /goals/:id/members` shape), pin `metic+archon`→`any-builder` so it now catches an accidental **tightening** as loudly as a gate removal. **The two facts resolve in two places on purpose:** the RANK fact is request-scoped and resolves before the transaction (uncached, ADR 0016); the OWNER fact is a column that can change under us, so `created_by` joins `abandonTask`’s existing `FOR UPDATE` SELECT and a new optional `authorize` callback decides under that lock. Reading `created_by` in the route would be a TOCTOU window; resolving permissions inside the transaction would hold a row lock across a second pool acquire. The decision is a PURE module (`abandon-authz.js`, the `goal-route-authz.js` precedent) so every branch is pinned DB-free and the hall hint and server verdict provably read one rule. **A creator’s window stops at the grader** — completed/confirmed stay cross-owner, because past the grader a retire is a ledger correction, not an author changing their mind; nothing is subtracted from `task.abandon`, pinned by a test. `counterpart: ['task.abandon']` is declared not derived, verb `act` not `manage` — `manage` would silently dominate every future narrower `task.*.own` key (the verb-blind escalation task 1003195 closed). **A null creator belongs to nobody and that is load-bearing:** ~1,357 pre-`core_227` tasks plus seed/restock/discord vectors are unattributed, so the owner comparison is the government resolver’s own — injected, never re-implemented, since a duplicated ownership comparison is how one quietly widens — and one slip would open every historical task to a xenos grant at once. Fail-closed throughout: a missing `government` port, a throwing resolver, and anything but an explicit `{ok:true}` all deny, and `authorize` runs BEFORE the idempotent already-abandoned return and the shipped/active-claim guards so an unauthorized caller cannot probe a row through a 200. No guard removed. Does NOT widen edit/promote/dependency (still Metic), does not touch `task.merge`, and does not backfill attribution — the control simply does not appear on tasks predating `core_227`.](0297-a-tasks-creator-may-retire-it.md) | authority / lifecycle / ownership |
|
package/docs/api/openapi.json
CHANGED
|
@@ -16170,12 +16170,9 @@
|
|
|
16170
16170
|
"tasks"
|
|
16171
16171
|
],
|
|
16172
16172
|
"summary": "POST /tasks/:id/abandon",
|
|
16173
|
-
"description": "POST /tasks/:id/abandon — terminal \"retire this task\" transition (task 987). The lifecycle had promote/ship/confirm/resolve but no first-class way to KILL a task that is no longer relevant: the only paths to status='abandoned' were offboarding a builder or a raw SQL UPDATE, so stale/moot tasks lingered in 'ready' as claimable noise. Refuses on a shipped task (immutable history) and on a task with an active claim (release it first); idempotent on an already- abandoned task. The abandon + its `reason` land in audit_log via the writing middleware, so the ledger keeps a record of who retired what. rank:
|
|
16174
|
-
"x-rank": "
|
|
16173
|
+
"description": "POST /tasks/:id/abandon — terminal \"retire this task\" transition (task 987). The lifecycle had promote/ship/confirm/resolve but no first-class way to KILL a task that is no longer relevant: the only paths to status='abandoned' were offboarding a builder or a raw SQL UPDATE, so stale/moot tasks lingered in 'ready' as claimable noise. Refuses on a shipped task (immutable history) and on a task with an active claim (release it first); idempotent on an already- abandoned task. The abandon + its `reason` land in audit_log via the writing middleware, so the ledger keeps a record of who retired what. rank: any-builder — DELIBERATELY, and the middleware list is not the whole gate (ADR 0297). Two authorities reach this transition: `task.abandon` (floor metic) retires ANY task, and `task.abandon.own` (floor xenos) retires one you CREATED while it is still backlog/ready/blocked. The second is a predicate over tasks.created_by, which no rank floor can express — the ADR 0151 ownership axis, and this is its first route. requirePermission structurally cannot carry it: an own-scoped key has no unconditional answer and that gate refuses one outright (auth.js, R100), so `.own` can never collapse into `.any`. The two facts are resolved in two places ON PURPOSE. The RANK fact is request-scoped and resolves here, once, uncached (ADR 0016); the OWNER fact is a column that can change under us, so it is evaluated inside abandonTask's FOR UPDATE transaction. Reading created_by here would be a TOCTOU window; resolving permissions in there would hold a row lock across a second pool acquire. Existing guards (shipped refused, active claim refused, idempotent re-abandon) and the audit_log entry are unchanged on both paths.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
16174
|
+
"x-rank": "any-builder",
|
|
16175
16175
|
"x-source": "modules/lifecycle/routes/tasks.js",
|
|
16176
|
-
"x-permissions": [
|
|
16177
|
-
"task.abandon"
|
|
16178
|
-
],
|
|
16179
16176
|
"parameters": [
|
|
16180
16177
|
{
|
|
16181
16178
|
"name": "id",
|
|
@@ -16187,23 +16184,12 @@
|
|
|
16187
16184
|
"description": "Path parameter `id`."
|
|
16188
16185
|
}
|
|
16189
16186
|
],
|
|
16190
|
-
"requestBody": {
|
|
16191
|
-
"required": false,
|
|
16192
|
-
"content": {
|
|
16193
|
-
"application/json": {
|
|
16194
|
-
"schema": {
|
|
16195
|
-
"$ref": "#/components/schemas/PostTasksIdAbandonRequest"
|
|
16196
|
-
}
|
|
16197
|
-
}
|
|
16198
|
-
},
|
|
16199
|
-
"x-validated": true
|
|
16200
|
-
},
|
|
16201
16187
|
"responses": {
|
|
16202
16188
|
"200": {
|
|
16203
16189
|
"description": "Success."
|
|
16204
16190
|
},
|
|
16205
16191
|
"400": {
|
|
16206
|
-
"$ref": "#/components/responses/
|
|
16192
|
+
"$ref": "#/components/responses/BadRequest"
|
|
16207
16193
|
},
|
|
16208
16194
|
"401": {
|
|
16209
16195
|
"$ref": "#/components/responses/Unauthorized"
|
|
@@ -16883,7 +16869,7 @@
|
|
|
16883
16869
|
"tasks"
|
|
16884
16870
|
],
|
|
16885
16871
|
"summary": "POST /tasks/:id/merge",
|
|
16886
|
-
"description": "
|
|
16872
|
+
"description": "**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `task.merge` (all required).",
|
|
16887
16873
|
"x-rank": "metic+archon",
|
|
16888
16874
|
"x-source": "modules/lifecycle/routes/tasks.js",
|
|
16889
16875
|
"x-permissions": [
|
|
@@ -24021,15 +24007,6 @@
|
|
|
24021
24007
|
"recommendation"
|
|
24022
24008
|
]
|
|
24023
24009
|
},
|
|
24024
|
-
"PostTasksIdAbandonRequest": {
|
|
24025
|
-
"type": "object",
|
|
24026
|
-
"properties": {
|
|
24027
|
-
"reason": {
|
|
24028
|
-
"type": "string"
|
|
24029
|
-
}
|
|
24030
|
-
},
|
|
24031
|
-
"additionalProperties": false
|
|
24032
|
-
},
|
|
24033
24010
|
"PostTasksIdAttestGateRequest": {
|
|
24034
24011
|
"type": "object",
|
|
24035
24012
|
"properties": {
|
|
@@ -24783,7 +24760,7 @@
|
|
|
24783
24760
|
}
|
|
24784
24761
|
},
|
|
24785
24762
|
"x-endpoint-count": 390,
|
|
24786
|
-
"x-schema-count":
|
|
24763
|
+
"x-schema-count": 429,
|
|
24787
24764
|
"x-undocumented-bodies": 9,
|
|
24788
24765
|
"x-response-schemas": 293,
|
|
24789
24766
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
package/docs/api-reference.md
CHANGED
|
@@ -642,7 +642,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
642
642
|
| POST | `/api/bongos/tasks` | `metic+archon` | `version_id`, `title`, `description`, `status`, `touches`, `est_minutes`, `est_cost_usd`, `manual_degree`, `priority`, `automation_tag`, `credits_reward`, `source`, `source_ref`, `value_summary`, `kind`, `discipline`, `parallel_safe`, `newcomer_friendly`, `security_sensitive`, `requires_rank`, `auto_detect_touches`, `criterion_ids`, `module_key`, `goal_id` | Task create/promote/edit (plan/scope mutation) is Metic+ as of ADR 0090 (owner decision 2026-06-26): a Metic who can run idea-triage may … |
|
|
643
643
|
| GET | `/api/bongos/tasks/:id` | `any-builder` | — | single-task read; preserved per CRITICAL §4 (supervisor hot path). |
|
|
644
644
|
| PATCH | `/api/bongos/tasks/:id` | `metic+archon` | `kind`, `discipline`, `requires_rank`, `module_key`, `goal_id`, `parent_task_id`, `parallel_safe`, `newcomer_friendly`, `xenos_claimable`, `security_sensitive`, `automation_tag`, `needs_migration`, `description`, `title`, `priority`, `value_summary`, `status` | PATCH /tasks/:id — supports body.parent_task_id (V2 C2), body.kind (V2 B1), body.discipline (mig 014), body.parallel_safe (mig 028), and … |
|
|
645
|
-
| POST | `/api/bongos/tasks/:id/abandon` | `
|
|
645
|
+
| POST | `/api/bongos/tasks/:id/abandon` | `any-builder` | — | POST /tasks/:id/abandon — terminal "retire this task" transition (task 987). |
|
|
646
646
|
| POST | `/api/bongos/tasks/:id/attest-gate` | `any-builder` | `head_sha` | POST /tasks/:id/attest-gate — body: { head_sha }. |
|
|
647
647
|
| POST | `/api/bongos/tasks/:id/confirm` | `any-builder` | `verified` | |
|
|
648
648
|
| GET | `/api/bongos/tasks/:id/criteria` | `any-builder` | — | GET /tasks/:id/criteria — the criteria this task gates (with live "Cn" + source). |
|
|
@@ -654,7 +654,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
654
654
|
| PUT | `/api/bongos/tasks/:id/dependencies` | `metic+archon` | `depends_on_task_ids` | PUT /tasks/:id/dependencies — replace the full dep set. |
|
|
655
655
|
| DELETE | `/api/bongos/tasks/:id/dependencies/:depId` | `metic+archon` | — | DELETE /tasks/:id/dependencies/:depId — removes the edge. |
|
|
656
656
|
| POST | `/api/bongos/tasks/:id/grade` | `any-builder` | `subagent_output`, `signals`, `committed_files`, `diff_hash` | GDS-V3 task V3.R18 (task 209). |
|
|
657
|
-
| POST | `/api/bongos/tasks/:id/merge` | `metic+archon` | `into_task_id`, `reason` |
|
|
657
|
+
| POST | `/api/bongos/tasks/:id/merge` | `metic+archon` | `into_task_id`, `reason` | |
|
|
658
658
|
| POST | `/api/bongos/tasks/:id/override-request` | `any-builder` | `rationale` | POST /tasks/:id/override-request — V3.R74 (task 293). |
|
|
659
659
|
| POST | `/api/bongos/tasks/:id/promote` | `metic+archon` | — | backlog→ready promotion; scope mutation; ADR 0090 (was archon). |
|
|
660
660
|
| POST | `/api/bongos/tasks/:id/publish-branch` | `any-builder` | `branch`, `head_sha`, `bundle_b64`, `value_summary` | |
|
|
@@ -76,7 +76,9 @@ forming the ordered claim-gate tier **`xenos` (1) < `thetes` (2) < `metic` (3) <
|
|
|
76
76
|
links (`/tasks/:id/dependencies`, `/tasks/:id/criteria`) — the reasoning being that a rank trusted to
|
|
77
77
|
*triage* ideas is trusted to *seed* the work they become. **As of [ADR 0152](adr/0152-metic-task-abandonment.md)
|
|
78
78
|
(owner decision 2026-07-27) a Metic may also retire work** — `POST /tasks/:id/abandon` — and undo a
|
|
79
|
-
mis-retire, since `POST /tasks/:id/promote` now also restores `abandoned → backlog`.
|
|
79
|
+
mis-retire, since `POST /tasks/:id/promote` now also restores `abandoned → backlog`. That rank power
|
|
80
|
+
is unchanged by [ADR 0297](adr/0297-a-tasks-creator-may-retire-it.md), which adds a SECOND way to
|
|
81
|
+
reach the same route rather than narrowing this one (see the ownership-axis note below). **Still cannot**
|
|
80
82
|
set rank, close versions, grader-bypass confirm, or author cross-tier `POST /dependencies` / goal-scope edges
|
|
81
83
|
([ADR 0086](adr/0086-goal-scoped-work-hierarchy.md)) — those stay Archon. A builder reaches Metic
|
|
82
84
|
only by deliberate Archon promotion (`PATCH /builders/:id/rank`).
|
|
@@ -169,6 +171,33 @@ or missing ranks fail closed.
|
|
|
169
171
|
`tasks/:id/{dependencies,criteria}` CRUD — moved to Metic+ in [ADR 0090](adr/0090-metic-task-authoring.md),
|
|
170
172
|
and **task retirement** — `POST /api/gds/tasks/:id/abandon`, plus the `abandoned → backlog` restore on
|
|
171
173
|
`/promote` — in [ADR 0152](adr/0152-metic-task-abandonment.md) (see the Metic+Archon bullet).
|
|
174
|
+
|
|
175
|
+
> **The ownership axis: a rule about WHOSE row, which no rank can express** ([ADR 0297](adr/0297-a-tasks-creator-may-retire-it.md),
|
|
176
|
+
> owner request 2026-07-25). `POST /api/gds/tasks/:id/abandon` is the first route decided on the
|
|
177
|
+
> [ADR 0151](adr/0151-governance-permissions-as-atom-ranks-as-roles.md) §3 ownership axis, and so the first whose
|
|
178
|
+
> middleware list is deliberately **not** the whole gate. Two authorities reach it:
|
|
179
|
+
>
|
|
180
|
+
> | permission | floor | reaches |
|
|
181
|
+
> |---|---|---|
|
|
182
|
+
> | `task.abandon` | `metic` | any task, any creator — the ADR 0152 triage power, unchanged |
|
|
183
|
+
> | `task.abandon.own` | `xenos` | a task **you created** (`tasks.created_by`), while it is still `backlog`/`ready`/`blocked` |
|
|
184
|
+
>
|
|
185
|
+
> The route is `requireBuilder` + an in-handler decision, the same shape as
|
|
186
|
+
> `POST /goals/:id/members` and `PATCH /inbox/:id/body`. `requirePermission` **cannot** carry an
|
|
187
|
+
> own-scoped key: it has no unconditional answer, so that gate refuses one outright (R100) precisely so
|
|
188
|
+
> `.own` can never collapse into `.any`. Its `route-rank-check.js` pin is therefore `any-builder`, whose
|
|
189
|
+
> job is to catch an accidental **tightening** back to a rank floor as loudly as a gate removal.
|
|
190
|
+
>
|
|
191
|
+
> **The DB still enforces, and now under a row lock.** The rank half resolves per request, uncached
|
|
192
|
+
> (ADR 0016, unchanged); the owner half is read inside `abandonTask`'s `FOR UPDATE` transaction, so the
|
|
193
|
+
> predicate and the write see one row version. The decision itself is pure and unit-tested in
|
|
194
|
+
> `modules/lifecycle/abandon-authz.js` — the hall only *displays* the control, and is never the wall.
|
|
195
|
+
>
|
|
196
|
+
> **A creator's window stops at the grader.** `completed`/`confirmed` stay cross-owner-only: past the
|
|
197
|
+
> grader a retire is a correction to the ledger, not an author changing their mind. The existing guards
|
|
198
|
+
> are untouched for both paths — never a shipped task, never one with an active claim. And a task with
|
|
199
|
+
> `created_by IS NULL` (everything filed before `core_227`, plus the seed/restock/Discord vectors)
|
|
200
|
+
> belongs to **nobody**: the control never appears on it, and no ownership grant can reach it.
|
|
172
201
|
`POST /api/gds/done-when/:id/unsatisfy` **was** Archon-only and moved to Metic in ADR 0157, superseding
|
|
173
202
|
[ADR 0154](adr/0154-goal-owner-criterion-authoring.md)'s "the backward ratchet stays Archon" carve-out;
|
|
174
203
|
satisfy and unsatisfy remain separate permissions, so confirming a criterion still never confers
|