@bongos/core 1.19.1063 → 1.19.1065

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.bongos-core.json +136 -56
  2. package/.claude/skills/grade-recover/SKILL.md +1 -0
  3. package/.claude/skills/tweak/SKILL.md +70 -0
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +12 -0
  6. package/clients/bongos-client/index.cjs +12 -0
  7. package/clients/bongos-client/index.d.ts +21 -1
  8. package/clients/bongos-client/index.mjs +12 -0
  9. package/docs/api/openapi.json +492 -4
  10. package/docs/api-reference.md +9 -3
  11. package/docs/copy-inventory.md +60 -41
  12. package/docs/copy-registry.json +228 -47
  13. package/docs/file-map.md +1 -0
  14. package/docs/module-api-changelog.md +4 -0
  15. package/docs/page-inventory.json +11 -2
  16. package/docs/page-readings.json +45 -22
  17. package/modules/copy-desk/page-decisions.js +71 -0
  18. package/modules/copy-desk/page-status.js +96 -0
  19. package/modules/copy-desk/pages.js +30 -0
  20. package/modules/copy-desk/routes/copy-desk.js +153 -0
  21. package/modules/copy-desk/tests/copy_no_cms.mjs +40 -4
  22. package/modules/hall-ui/public/approval-queue-lib.js +156 -0
  23. package/modules/hall-ui/public/approval-queue.css +286 -0
  24. package/modules/hall-ui/public/approval-queue.js +338 -0
  25. package/modules/hall-ui/public/studio.css +33 -0
  26. package/modules/hall-ui/public/studio.html +18 -1
  27. package/modules/hall-ui/public/studio.js +30 -0
  28. package/modules/hall-ui/public/studio.states.json +198 -0
  29. package/modules/hall-ui/records/approval-queue.md +32 -0
  30. package/modules/lifecycle/db-grade.js +96 -58
  31. package/modules/lifecycle/github-pr-close.js +41 -0
  32. package/modules/lifecycle/github-push.js +5 -2
  33. package/modules/lifecycle/lifecycle.js +12 -0
  34. package/modules/lifecycle/migrations/lifecycle_013_task_visual_slots.sql +52 -0
  35. package/modules/lifecycle/page-tweak-approve.js +242 -0
  36. package/modules/lifecycle/page-tweak-hold.js +74 -0
  37. package/modules/lifecycle/page-tweak-reads.js +33 -1
  38. package/modules/lifecycle/routes/gate-approvals.js +17 -2
  39. package/modules/lifecycle/routes/tasks.js +4 -2
  40. package/modules/lifecycle/routes/visuals.js +82 -0
  41. package/modules/lifecycle/task-visual-db.js +40 -1
  42. package/modules/lifecycle/task-visuals.js +24 -0
  43. package/package-lock.json +2 -2
  44. package/package.json +1 -1
  45. package/release-notes.json +12 -0
  46. package/scripts/gds/copy-apply.js +277 -1
  47. package/scripts/gds/page-reader.js +2 -0
  48. package/scripts/gds/ship-finish.js +27 -2
  49. package/scripts/gds/ship-flow.js +17 -1
  50. package/scripts/gds/ship-land.js +30 -7
  51. package/scripts/gds/ship-merge.js +43 -15
  52. package/scripts/gds/strand-watch.js +41 -1
  53. package/scripts/gds/task.js +23 -5
  54. package/scripts/gds/tweak-renders.js +200 -0
  55. package/scripts/hall-preview/server.js +18 -0
  56. package/src/module-api.js +1 -1
  57. package/tests/copy_desk_page_approve.mjs +307 -0
  58. package/tests/fixtures/page-ledger.mjs +41 -1
  59. package/tests/gate_approvals.mjs +93 -0
  60. package/tests/hall_approval_queue.mjs +327 -0
  61. package/tests/hall_audit.mjs +4 -0
  62. package/tests/page_tweak_hold.mjs +265 -0
  63. package/tests/publish_status_branch.mjs +23 -0
  64. package/tests/task_visual_slots.mjs +161 -0
  65. package/tests/tweak_batch_apply.mjs +265 -0
@@ -4979,6 +4979,37 @@
4979
4979
  ]
4980
4980
  }
4981
4981
  },
4982
+ "/copy-desk/approvals": {
4983
+ "get": {
4984
+ "operationId": "get_copy_desk_approvals",
4985
+ "tags": [
4986
+ "copy-desk"
4987
+ ],
4988
+ "summary": "GET /copy-desk/approvals",
4989
+ "description": "GET /copy-desk/approvals — every round waiting for its artist, oldest applied first, with its position (\"1 of 2\"), the lines it rewrote (and the ones the applier refused), and where its renders are. Any signed-in builder; `can_decide` says whether the caller may approve or send back.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
4990
+ "x-rank": "any-builder",
4991
+ "x-source": "modules/copy-desk/routes/copy-desk.js",
4992
+ "responses": {
4993
+ "200": {
4994
+ "description": "Success."
4995
+ },
4996
+ "400": {
4997
+ "$ref": "#/components/responses/BadRequest"
4998
+ },
4999
+ "401": {
5000
+ "$ref": "#/components/responses/Unauthorized"
5001
+ },
5002
+ "403": {
5003
+ "$ref": "#/components/responses/Forbidden"
5004
+ }
5005
+ },
5006
+ "security": [
5007
+ {
5008
+ "builderSession": []
5009
+ }
5010
+ ]
5011
+ }
5012
+ },
4982
5013
  "/copy-desk/flags": {
4983
5014
  "post": {
4984
5015
  "operationId": "post_copy_desk_flags",
@@ -5198,6 +5229,69 @@
5198
5229
  ]
5199
5230
  }
5200
5231
  },
5232
+ "/copy-desk/pages/{pageId}/approve": {
5233
+ "post": {
5234
+ "operationId": "post_copy_desk_pages_pageId_approve",
5235
+ "tags": [
5236
+ "copy-desk"
5237
+ ],
5238
+ "summary": "POST /copy-desk/pages/:pageId/approve",
5239
+ "description": "------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/approve — the held pass goes on to land. Body: { task_id? } (the round the artist looked at). The port promotes completed -> confirmed with the rewards the hold skipped and nudges the merge; the reconciler lands it otherwise. The count goes up when it ships. 200 approved · 409 not_waiting · 409 round_moved · 403 not_an_artist -------------------------------------------------------------------------\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
5240
+ "x-rank": "any-builder",
5241
+ "x-source": "modules/copy-desk/routes/copy-desk.js",
5242
+ "parameters": [
5243
+ {
5244
+ "name": "pageId",
5245
+ "in": "path",
5246
+ "required": true,
5247
+ "schema": {
5248
+ "type": "string"
5249
+ },
5250
+ "description": "Path parameter `pageId`."
5251
+ }
5252
+ ],
5253
+ "requestBody": {
5254
+ "required": false,
5255
+ "content": {
5256
+ "application/json": {
5257
+ "schema": {
5258
+ "$ref": "#/components/schemas/PostCopyDeskPagesPageIdApproveRequest"
5259
+ }
5260
+ }
5261
+ },
5262
+ "x-validated": true
5263
+ },
5264
+ "responses": {
5265
+ "200": {
5266
+ "description": "Success.",
5267
+ "content": {
5268
+ "application/json": {
5269
+ "schema": {
5270
+ "$ref": "#/components/schemas/PostCopyDeskPagesPageIdApproveResponse"
5271
+ }
5272
+ }
5273
+ }
5274
+ },
5275
+ "400": {
5276
+ "$ref": "#/components/responses/ValidationFailed"
5277
+ },
5278
+ "401": {
5279
+ "$ref": "#/components/responses/Unauthorized"
5280
+ },
5281
+ "403": {
5282
+ "$ref": "#/components/responses/Forbidden"
5283
+ },
5284
+ "404": {
5285
+ "$ref": "#/components/responses/NotFound"
5286
+ }
5287
+ },
5288
+ "security": [
5289
+ {
5290
+ "builderSession": []
5291
+ }
5292
+ ]
5293
+ }
5294
+ },
5201
5295
  "/copy-desk/pages/{pageId}/asks": {
5202
5296
  "post": {
5203
5297
  "operationId": "post_copy_desk_pages_pageId_asks",
@@ -5525,6 +5619,69 @@
5525
5619
  ]
5526
5620
  }
5527
5621
  },
5622
+ "/copy-desk/pages/{pageId}/send-back": {
5623
+ "post": {
5624
+ "operationId": "post_copy_desk_pages_pageId_send_back",
5625
+ "tags": [
5626
+ "copy-desk"
5627
+ ],
5628
+ "summary": "POST /copy-desk/pages/:pageId/send-back",
5629
+ "description": "------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/send-back — back to the /tweak queue with a note (ADR 0341 D7). Body: { note, task_id? }. The note is one line of at least 10 characters (pages.normalizeSentBackNote). The port appends a page-tweak-sent-back block { at, by, note }, releases any open claim, drops the round's grade row, returns the task to ready and closes the unmerged PR, so the round reads `submitted` again. 200 sent_back · 400 note_too_short / note_too_long · 409 not_waiting · 409 round_moved · 403 not_an_artist -------------------------------------------------------------------------\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
5630
+ "x-rank": "any-builder",
5631
+ "x-source": "modules/copy-desk/routes/copy-desk.js",
5632
+ "parameters": [
5633
+ {
5634
+ "name": "pageId",
5635
+ "in": "path",
5636
+ "required": true,
5637
+ "schema": {
5638
+ "type": "string"
5639
+ },
5640
+ "description": "Path parameter `pageId`."
5641
+ }
5642
+ ],
5643
+ "requestBody": {
5644
+ "required": true,
5645
+ "content": {
5646
+ "application/json": {
5647
+ "schema": {
5648
+ "$ref": "#/components/schemas/PostCopyDeskPagesPageIdSendBackRequest"
5649
+ }
5650
+ }
5651
+ },
5652
+ "x-validated": true
5653
+ },
5654
+ "responses": {
5655
+ "200": {
5656
+ "description": "Success.",
5657
+ "content": {
5658
+ "application/json": {
5659
+ "schema": {
5660
+ "$ref": "#/components/schemas/PostCopyDeskPagesPageIdSendBackResponse"
5661
+ }
5662
+ }
5663
+ }
5664
+ },
5665
+ "400": {
5666
+ "$ref": "#/components/responses/ValidationFailed"
5667
+ },
5668
+ "401": {
5669
+ "$ref": "#/components/responses/Unauthorized"
5670
+ },
5671
+ "403": {
5672
+ "$ref": "#/components/responses/Forbidden"
5673
+ },
5674
+ "404": {
5675
+ "$ref": "#/components/responses/NotFound"
5676
+ }
5677
+ },
5678
+ "security": [
5679
+ {
5680
+ "builderSession": []
5681
+ }
5682
+ ]
5683
+ }
5684
+ },
5528
5685
  "/copy-desk/pages/{pageId}/submit": {
5529
5686
  "post": {
5530
5687
  "operationId": "post_copy_desk_pages_pageId_submit",
@@ -20455,6 +20612,189 @@
20455
20612
  ]
20456
20613
  }
20457
20614
  },
20615
+ "/tasks/{id}/visuals": {
20616
+ "get": {
20617
+ "operationId": "get_tasks_id_visuals",
20618
+ "tags": [
20619
+ "tasks"
20620
+ ],
20621
+ "summary": "GET /tasks/:id/visuals",
20622
+ "description": "GET /tasks/:id/visuals — the task's named visuals. Any signed-in builder: it lists URLs only, and each image is still served under its own task's audience.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
20623
+ "x-rank": "any-builder",
20624
+ "x-source": "modules/lifecycle/routes/visuals.js",
20625
+ "parameters": [
20626
+ {
20627
+ "name": "id",
20628
+ "in": "path",
20629
+ "required": true,
20630
+ "schema": {
20631
+ "type": "string"
20632
+ },
20633
+ "description": "Path parameter `id`."
20634
+ }
20635
+ ],
20636
+ "responses": {
20637
+ "200": {
20638
+ "description": "Success.",
20639
+ "content": {
20640
+ "application/json": {
20641
+ "schema": {
20642
+ "$ref": "#/components/schemas/GetTasksIdVisualsResponse"
20643
+ }
20644
+ }
20645
+ }
20646
+ },
20647
+ "400": {
20648
+ "$ref": "#/components/responses/BadRequest"
20649
+ },
20650
+ "401": {
20651
+ "$ref": "#/components/responses/Unauthorized"
20652
+ },
20653
+ "403": {
20654
+ "$ref": "#/components/responses/Forbidden"
20655
+ },
20656
+ "404": {
20657
+ "$ref": "#/components/responses/NotFound"
20658
+ }
20659
+ },
20660
+ "security": [
20661
+ {
20662
+ "builderSession": []
20663
+ }
20664
+ ]
20665
+ }
20666
+ },
20667
+ "/tasks/{id}/visuals/{slot}": {
20668
+ "post": {
20669
+ "operationId": "post_tasks_id_visuals_slot",
20670
+ "tags": [
20671
+ "tasks"
20672
+ ],
20673
+ "summary": "POST /tasks/:id/visuals/:slot",
20674
+ "description": "POST /tasks/:id/visuals/:slot — attach (or replace) one named visual.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
20675
+ "x-rank": "any-builder",
20676
+ "x-source": "modules/lifecycle/routes/visuals.js",
20677
+ "parameters": [
20678
+ {
20679
+ "name": "id",
20680
+ "in": "path",
20681
+ "required": true,
20682
+ "schema": {
20683
+ "type": "string"
20684
+ },
20685
+ "description": "Path parameter `id`."
20686
+ },
20687
+ {
20688
+ "name": "slot",
20689
+ "in": "path",
20690
+ "required": true,
20691
+ "schema": {
20692
+ "type": "string"
20693
+ },
20694
+ "description": "Path parameter `slot`."
20695
+ }
20696
+ ],
20697
+ "requestBody": {
20698
+ "required": true,
20699
+ "content": {
20700
+ "application/json": {
20701
+ "schema": {
20702
+ "$ref": "#/components/schemas/PostTasksIdVisualsSlotRequest"
20703
+ }
20704
+ }
20705
+ },
20706
+ "x-validated": true
20707
+ },
20708
+ "responses": {
20709
+ "200": {
20710
+ "description": "Success.",
20711
+ "content": {
20712
+ "application/json": {
20713
+ "schema": {
20714
+ "$ref": "#/components/schemas/PostTasksIdVisualsSlotResponse"
20715
+ }
20716
+ }
20717
+ }
20718
+ },
20719
+ "400": {
20720
+ "$ref": "#/components/responses/ValidationFailed"
20721
+ },
20722
+ "401": {
20723
+ "$ref": "#/components/responses/Unauthorized"
20724
+ },
20725
+ "403": {
20726
+ "$ref": "#/components/responses/Forbidden"
20727
+ },
20728
+ "404": {
20729
+ "$ref": "#/components/responses/NotFound"
20730
+ }
20731
+ },
20732
+ "security": [
20733
+ {
20734
+ "builderSession": []
20735
+ }
20736
+ ]
20737
+ },
20738
+ "delete": {
20739
+ "operationId": "delete_tasks_id_visuals_slot",
20740
+ "tags": [
20741
+ "tasks"
20742
+ ],
20743
+ "summary": "DELETE /tasks/:id/visuals/:slot",
20744
+ "description": "DELETE /tasks/:id/visuals/:slot — remove one named visual. Idempotent.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
20745
+ "x-rank": "any-builder",
20746
+ "x-source": "modules/lifecycle/routes/visuals.js",
20747
+ "parameters": [
20748
+ {
20749
+ "name": "id",
20750
+ "in": "path",
20751
+ "required": true,
20752
+ "schema": {
20753
+ "type": "string"
20754
+ },
20755
+ "description": "Path parameter `id`."
20756
+ },
20757
+ {
20758
+ "name": "slot",
20759
+ "in": "path",
20760
+ "required": true,
20761
+ "schema": {
20762
+ "type": "string"
20763
+ },
20764
+ "description": "Path parameter `slot`."
20765
+ }
20766
+ ],
20767
+ "responses": {
20768
+ "200": {
20769
+ "description": "Success.",
20770
+ "content": {
20771
+ "application/json": {
20772
+ "schema": {
20773
+ "$ref": "#/components/schemas/DeleteTasksIdVisualsSlotResponse"
20774
+ }
20775
+ }
20776
+ }
20777
+ },
20778
+ "400": {
20779
+ "$ref": "#/components/responses/BadRequest"
20780
+ },
20781
+ "401": {
20782
+ "$ref": "#/components/responses/Unauthorized"
20783
+ },
20784
+ "403": {
20785
+ "$ref": "#/components/responses/Forbidden"
20786
+ },
20787
+ "404": {
20788
+ "$ref": "#/components/responses/NotFound"
20789
+ }
20790
+ },
20791
+ "security": [
20792
+ {
20793
+ "builderSession": []
20794
+ }
20795
+ ]
20796
+ }
20797
+ },
20458
20798
  "/tasks/{id}/vote": {
20459
20799
  "post": {
20460
20800
  "operationId": "post_tasks_id_vote",
@@ -21282,6 +21622,21 @@
21282
21622
  "visual_url"
21283
21623
  ]
21284
21624
  },
21625
+ "DeleteTasksIdVisualsSlotResponse": {
21626
+ "type": "object",
21627
+ "properties": {
21628
+ "ok": {
21629
+ "type": "boolean"
21630
+ },
21631
+ "slot": {},
21632
+ "visual_url": {}
21633
+ },
21634
+ "required": [
21635
+ "ok",
21636
+ "slot",
21637
+ "visual_url"
21638
+ ]
21639
+ },
21285
21640
  "ErrorEnvelope": {
21286
21641
  "type": "object",
21287
21642
  "required": [
@@ -23203,6 +23558,21 @@
23203
23558
  "count"
23204
23559
  ]
23205
23560
  },
23561
+ "GetTasksIdVisualsResponse": {
23562
+ "type": "object",
23563
+ "properties": {
23564
+ "ok": {
23565
+ "type": "boolean"
23566
+ },
23567
+ "task_id": {},
23568
+ "slots": {}
23569
+ },
23570
+ "required": [
23571
+ "ok",
23572
+ "task_id",
23573
+ "slots"
23574
+ ]
23575
+ },
23206
23576
  "GetTasksNewcomerFloorResponse": {
23207
23577
  "type": "object",
23208
23578
  "properties": {
@@ -25141,6 +25511,40 @@
25141
25511
  "flag"
25142
25512
  ]
25143
25513
  },
25514
+ "PostCopyDeskPagesPageIdApproveRequest": {
25515
+ "type": "object",
25516
+ "properties": {
25517
+ "task_id": {
25518
+ "type": "string",
25519
+ "minLength": 1,
25520
+ "maxLength": 20
25521
+ }
25522
+ },
25523
+ "additionalProperties": false
25524
+ },
25525
+ "PostCopyDeskPagesPageIdApproveResponse": {
25526
+ "type": "object",
25527
+ "properties": {
25528
+ "ok": {
25529
+ "type": "boolean"
25530
+ },
25531
+ "outcome": {},
25532
+ "page_id": {},
25533
+ "round": {},
25534
+ "status_after": {},
25535
+ "land": {},
25536
+ "next": {}
25537
+ },
25538
+ "required": [
25539
+ "ok",
25540
+ "outcome",
25541
+ "page_id",
25542
+ "round",
25543
+ "status_after",
25544
+ "land",
25545
+ "next"
25546
+ ]
25547
+ },
25144
25548
  "PostCopyDeskPagesPageIdAsksRequest": {
25145
25549
  "type": "object",
25146
25550
  "properties": {
@@ -25177,6 +25581,47 @@
25177
25581
  "merge"
25178
25582
  ]
25179
25583
  },
25584
+ "PostCopyDeskPagesPageIdSendBackRequest": {
25585
+ "type": "object",
25586
+ "properties": {
25587
+ "note": {
25588
+ "type": "string",
25589
+ "minLength": 1
25590
+ },
25591
+ "task_id": {
25592
+ "type": "string",
25593
+ "minLength": 1,
25594
+ "maxLength": 20
25595
+ }
25596
+ },
25597
+ "required": [
25598
+ "note"
25599
+ ],
25600
+ "additionalProperties": false
25601
+ },
25602
+ "PostCopyDeskPagesPageIdSendBackResponse": {
25603
+ "type": "object",
25604
+ "properties": {
25605
+ "ok": {
25606
+ "type": "boolean"
25607
+ },
25608
+ "outcome": {},
25609
+ "page_id": {},
25610
+ "round": {},
25611
+ "sent_back": {},
25612
+ "pr": {},
25613
+ "next": {}
25614
+ },
25615
+ "required": [
25616
+ "ok",
25617
+ "outcome",
25618
+ "page_id",
25619
+ "round",
25620
+ "sent_back",
25621
+ "pr",
25622
+ "next"
25623
+ ]
25624
+ },
25180
25625
  "PostCopyDeskProposalsRequest": {
25181
25626
  "type": "object",
25182
25627
  "properties": {
@@ -25376,7 +25821,8 @@
25376
25821
  "status_set": {
25377
25822
  "type": "boolean"
25378
25823
  },
25379
- "recheck": {}
25824
+ "recheck": {},
25825
+ "held_for_artist": {}
25380
25826
  },
25381
25827
  "required": [
25382
25828
  "ok",
@@ -28179,6 +28625,48 @@
28179
28625
  "bytes"
28180
28626
  ]
28181
28627
  },
28628
+ "PostTasksIdVisualsSlotRequest": {
28629
+ "type": "object",
28630
+ "properties": {
28631
+ "image_b64": {
28632
+ "type": "string",
28633
+ "minLength": 1,
28634
+ "maxLength": 7340032
28635
+ },
28636
+ "content_type": {
28637
+ "type": "string",
28638
+ "minLength": 1,
28639
+ "maxLength": 100
28640
+ },
28641
+ "alt": {
28642
+ "type": "string"
28643
+ }
28644
+ },
28645
+ "required": [
28646
+ "image_b64",
28647
+ "content_type"
28648
+ ],
28649
+ "additionalProperties": false
28650
+ },
28651
+ "PostTasksIdVisualsSlotResponse": {
28652
+ "type": "object",
28653
+ "properties": {
28654
+ "ok": {
28655
+ "type": "boolean"
28656
+ },
28657
+ "slot": {},
28658
+ "visual_url": {},
28659
+ "visual_alt": {},
28660
+ "bytes": {}
28661
+ },
28662
+ "required": [
28663
+ "ok",
28664
+ "slot",
28665
+ "visual_url",
28666
+ "visual_alt",
28667
+ "bytes"
28668
+ ]
28669
+ },
28182
28670
  "PostTasksIdVoteRequest": {
28183
28671
  "type": "object",
28184
28672
  "properties": {
@@ -28718,9 +29206,9 @@
28718
29206
  "description": "A required dependency/feature is not configured or is temporarily down."
28719
29207
  }
28720
29208
  },
28721
- "x-endpoint-count": 454,
28722
- "x-schema-count": 487,
29209
+ "x-endpoint-count": 460,
29210
+ "x-schema-count": 495,
28723
29211
  "x-undocumented-bodies": 10,
28724
- "x-response-schemas": 333,
29212
+ "x-response-schemas": 338,
28725
29213
  "x-generated-by": "scripts/gds/gen-api-docs.js"
28726
29214
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Bongos API reference
4
4
 
5
- > **Generated from the live route files** — the route file is authoritative. 454 endpoints across 76 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 460 endpoints across 76 route files.
6
6
  > Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
7
7
 
8
8
  Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust-boundary-server-enforced-permissions.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -210,21 +210,24 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
210
210
  | GET | `/api/bongos/connections/requests` | `any-builder` | — | MY pending requests, both directions (own data). |
211
211
  | GET | `/api/bongos/connections/suggestions` | `any-builder` | — | "builders you may know" (R21, task 1002297; ADR 0202 D5). |
212
212
 
213
- ## `copy-desk` (16)
213
+ ## `copy-desk` (19)
214
214
 
215
215
  | Method | Path | Rank | Body | Description |
216
216
  |---|---|---|---|---|
217
+ | GET | `/api/bongos/copy-desk/approvals` | `any-builder` | — | GET /copy-desk/approvals — every round waiting for its artist, oldest applied first, with its position ("1 of 2"), the lines it rewrote (… |
217
218
  | POST | `/api/bongos/copy-desk/flags` | `any-builder` | `scope`, `surface`, `string_id`, `reason`, `kind` | ------------------------------------------------------------------------- POST /copy-desk/flags — file a flag. |
218
219
  | POST | `/api/bongos/copy-desk/flags/:id/close` | `any-builder` | `status`, `resolution_note`, `resolved_task_id` | ------------------------------------------------------------------------- POST /copy-desk/flags/:id/close — resolve or dismiss. |
219
220
  | GET | `/api/bongos/copy-desk/next` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/next — the Tweak/CopyWrite Flow's recommendation… |
220
221
  | GET | `/api/bongos/copy-desk/pages` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/pages — every page's status, count and drift, pl… |
221
222
  | GET | `/api/bongos/copy-desk/pages/:pageId` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/pages/:pageId — one page: status, changelog, the… |
223
+ | POST | `/api/bongos/copy-desk/pages/:pageId/approve` | `any-builder` | `task_id` | ------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/approve — the held pass goes on t… |
222
224
  | POST | `/api/bongos/copy-desk/pages/:pageId/asks` | `any-builder` | `reason` | ------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/asks — ask for a page (task 10043… |
223
225
  | POST | `/api/bongos/copy-desk/pages/:pageId/claim` | `any-builder` | validated | ------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/claim — claim a page's tweak (tas… |
224
226
  | GET | `/api/bongos/copy-desk/pages/:pageId/draft` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/pages/:pageId/draft — what the tweak editor open… |
225
227
  | PUT | `/api/bongos/copy-desk/pages/:pageId/draft` | `any-builder` | `reading_hash`, `lines` | ------------------------------------------------------------------------- PUT /copy-desk/pages/:pageId/draft — autosave the artist's draf… |
226
228
  | GET | `/api/bongos/copy-desk/pages/:pageId/draft.docx` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/pages/:pageId/draft.docx — download the page's w… |
227
229
  | POST | `/api/bongos/copy-desk/pages/:pageId/draft.docx` | `any-builder` | _undocumented_ | ------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/draft.docx — upload the Word file… |
230
+ | POST | `/api/bongos/copy-desk/pages/:pageId/send-back` | `any-builder` | `note`, `task_id` | ------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/send-back — back to the /tweak qu… |
228
231
  | POST | `/api/bongos/copy-desk/pages/:pageId/submit` | `any-builder` | validated | ------------------------------------------------------------------------- POST /copy-desk/pages/:pageId/submit — freeze the draft and que… |
229
232
  | POST | `/api/bongos/copy-desk/proposals` | `any-builder` | `string_id`, `proposed_text`, `why`, `file`, `line`, `flag_id` | ------------------------------------------------------------------------- POST /copy-desk/proposals — an artist proposes new wording, and… |
230
233
  | GET | `/api/bongos/copy-desk/queue` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/queue — the artist's working list. |
@@ -743,7 +746,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
743
746
  |---|---|---|---|---|
744
747
  | GET | `/api/bongos/task-visuals/:name` | `public` | — | the WIDEST path this route can take, declared honestly. |
745
748
 
746
- ## `tasks` (32)
749
+ ## `tasks` (35)
747
750
 
748
751
  | Method | Path | Rank | Body | Description |
749
752
  |---|---|---|---|---|
@@ -772,6 +775,9 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
772
775
  | POST | `/api/bongos/tasks/:id/ship` | `any-builder` | validated | confirmed → shipped. |
773
776
  | POST | `/api/bongos/tasks/:id/visual` | `any-builder` | `image_b64`, `content_type`, `alt` | POST /tasks/:id/visual — attach (or replace) this task's ship-time visual. |
774
777
  | DELETE | `/api/bongos/tasks/:id/visual` | `any-builder` | — | DELETE /tasks/:id/visual — remove it again. |
778
+ | GET | `/api/bongos/tasks/:id/visuals` | `any-builder` | — | GET /tasks/:id/visuals — the task's named visuals. |
779
+ | POST | `/api/bongos/tasks/:id/visuals/:slot` | `any-builder` | `image_b64`, `content_type`, `alt` | POST /tasks/:id/visuals/:slot — attach (or replace) one named visual. |
780
+ | DELETE | `/api/bongos/tasks/:id/visuals/:slot` | `any-builder` | — | DELETE /tasks/:id/visuals/:slot — remove one named visual. |
775
781
  | POST | `/api/bongos/tasks/:id/vote` | `metic+archon` | `vote` | POST /tasks/:id/vote { vote: 1 \| -1 \| 0 } — cast/change/clear a peer vote on a confirmed/shipped task. |
776
782
  | POST | `/api/bongos/tasks/:id/water` | `any-builder` | — | POST /tasks/:id/water — reset a rotting task's clock. |
777
783
  | GET | `/api/bongos/tasks/claimable` | `any-builder` | — | NOTE: /tasks/claimable must come BEFORE /tasks/:id or Express will route 'claimable' to the :id param. |