@bongos/core 1.19.491 → 1.19.493
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 +68 -43
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +4 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +151 -0
- package/docs/adr/README.md +2 -0
- package/docs/api/openapi.json +106 -3
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +56 -34
- package/docs/copy-registry.json +237 -39
- package/docs/design/mocks/hall/task/v1.html +7 -11
- package/docs/design/mocks/hall/task/v3.html +7 -11
- package/docs/file-map.md +2 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/copy-desk/module.json +2 -2
- package/modules/copy-desk/proposals.js +278 -0
- package/modules/copy-desk/routes/copy-desk.js +173 -9
- package/modules/copy-desk/tests/copy_no_cms.mjs +90 -12
- package/modules/copy-desk/tests/copy_proposals.mjs +279 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/hall-ui/public/copy-desk.css +9 -1
- package/modules/hall-ui/public/copy-desk.html +33 -0
- package/modules/hall-ui/public/copy-desk.js +134 -3
- package/modules/hall-ui/public/copy-desk.states.json +18 -1
- package/modules/hall-ui/public/task.css +71 -1
- package/modules/hall-ui/public/task.js +132 -13
- package/modules/hall-ui/public/task.states.json +1 -1
- package/modules/lifecycle/db-claim-reads.js +40 -0
- package/modules/lifecycle/db.js +4 -0
- package/modules/lifecycle/lifecycle.js +6 -0
- package/modules/lifecycle/routes/tasks.js +12 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/copy-apply.js +338 -0
- package/scripts/gds/copy-inventory.js +128 -30
- package/src/module-api.js +1 -1
- package/tests/hall_collab_world.mjs +24 -4
- package/tests/hall_record_world.mjs +183 -2
- package/tests/task_detail_includes.mjs +108 -0
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.493",
|
|
6
|
+
"core_contract": "1.19.493",
|
|
7
|
+
"source_commit": "555a6e26ed0d472d1349e2897e3c4d652822aad4",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-02T23:20:09.143Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 423,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1859,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2306,
|
|
20
|
+
"tree_sha256": "73851bac69c6ce46da2367b50f0b5d00c2b76bf12efdce650349ef737305b318",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -362,12 +362,12 @@
|
|
|
362
362
|
{
|
|
363
363
|
"path": "clients/bongos-client/README.md",
|
|
364
364
|
"mode": "0000644",
|
|
365
|
-
"sha256": "
|
|
365
|
+
"sha256": "7904ca2bfec8710c90bd846edb4c0b50436eebc29fcb87897eea69fea09d1001"
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/bongos-client.global.js",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "4f6b441e36573c71d391a24b76247df79db02a46bf719965bee22051a90c916f"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/examples/hello-world.mjs",
|
|
@@ -377,17 +377,17 @@
|
|
|
377
377
|
{
|
|
378
378
|
"path": "clients/bongos-client/index.cjs",
|
|
379
379
|
"mode": "0000644",
|
|
380
|
-
"sha256": "
|
|
380
|
+
"sha256": "193252b5fb3be984786b23507a400564dfd6ad1899b0778b436882b8b28de39e"
|
|
381
381
|
},
|
|
382
382
|
{
|
|
383
383
|
"path": "clients/bongos-client/index.d.ts",
|
|
384
384
|
"mode": "0000644",
|
|
385
|
-
"sha256": "
|
|
385
|
+
"sha256": "8def1c72bdee7bed83886ed5d0eb5f0bd185f1661ee5613c431cd6106e3f49dc"
|
|
386
386
|
},
|
|
387
387
|
{
|
|
388
388
|
"path": "clients/bongos-client/index.mjs",
|
|
389
389
|
"mode": "0000644",
|
|
390
|
-
"sha256": "
|
|
390
|
+
"sha256": "1bf4942a3074fa90887b3efdcff39bde587abc6d8b70b4b83a2dea149beaa85a"
|
|
391
391
|
},
|
|
392
392
|
{
|
|
393
393
|
"path": "clients/bongos-client/package.json",
|
|
@@ -1669,20 +1669,25 @@
|
|
|
1669
1669
|
"mode": "0000644",
|
|
1670
1670
|
"sha256": "240b71442e189fd1167ad0b339dc389d55b77d83aa45c676a7e29f573645b681"
|
|
1671
1671
|
},
|
|
1672
|
+
{
|
|
1673
|
+
"path": "docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md",
|
|
1674
|
+
"mode": "0000644",
|
|
1675
|
+
"sha256": "69db7f69b30eb87a8b8e921ea664344faf7b953c7a85ce9d788282a0e181dbd1"
|
|
1676
|
+
},
|
|
1672
1677
|
{
|
|
1673
1678
|
"path": "docs/adr/README.md",
|
|
1674
1679
|
"mode": "0000644",
|
|
1675
|
-
"sha256": "
|
|
1680
|
+
"sha256": "8c8285dcd86e937c0f09b18839fe9628ae845cd4f4f9d332d723cf216e88deac"
|
|
1676
1681
|
},
|
|
1677
1682
|
{
|
|
1678
1683
|
"path": "docs/api-reference.md",
|
|
1679
1684
|
"mode": "0000644",
|
|
1680
|
-
"sha256": "
|
|
1685
|
+
"sha256": "1388440a16b1563120683f32ad285c9234c636f54c49a293bc7c1e9aabe55b53"
|
|
1681
1686
|
},
|
|
1682
1687
|
{
|
|
1683
1688
|
"path": "docs/api/openapi.json",
|
|
1684
1689
|
"mode": "0000644",
|
|
1685
|
-
"sha256": "
|
|
1690
|
+
"sha256": "32f6db18d82e0bd79d2346f9fb5c9176473eae3beaf6c4f34ffca958a5790bc6"
|
|
1686
1691
|
},
|
|
1687
1692
|
{
|
|
1688
1693
|
"path": "docs/architecture.md",
|
|
@@ -1707,12 +1712,12 @@
|
|
|
1707
1712
|
{
|
|
1708
1713
|
"path": "docs/copy-inventory.md",
|
|
1709
1714
|
"mode": "0000644",
|
|
1710
|
-
"sha256": "
|
|
1715
|
+
"sha256": "ff30c55a613e2a81c1b03114810d73e6fb5e8c4ea9dd2071171e749ebecfc4f6"
|
|
1711
1716
|
},
|
|
1712
1717
|
{
|
|
1713
1718
|
"path": "docs/copy-registry.json",
|
|
1714
1719
|
"mode": "0000644",
|
|
1715
|
-
"sha256": "
|
|
1720
|
+
"sha256": "0fe0ea407c373ba9ef054a29bbfe16e3d76a53e3bc51391073615a73d9d42aab"
|
|
1716
1721
|
},
|
|
1717
1722
|
{
|
|
1718
1723
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -1832,7 +1837,7 @@
|
|
|
1832
1837
|
{
|
|
1833
1838
|
"path": "docs/design/mocks/hall/task/v1.html",
|
|
1834
1839
|
"mode": "0000644",
|
|
1835
|
-
"sha256": "
|
|
1840
|
+
"sha256": "17424d5f6a26f9bc0281d733104701668efd4019ab66df1f9623b7e850adf7a3"
|
|
1836
1841
|
},
|
|
1837
1842
|
{
|
|
1838
1843
|
"path": "docs/design/mocks/hall/task/v2.html",
|
|
@@ -1842,7 +1847,7 @@
|
|
|
1842
1847
|
{
|
|
1843
1848
|
"path": "docs/design/mocks/hall/task/v3.html",
|
|
1844
1849
|
"mode": "0000644",
|
|
1845
|
-
"sha256": "
|
|
1850
|
+
"sha256": "e7e5614c0b6250a1d3e7746659654c5b26827119709171032e166cc5ed701f06"
|
|
1846
1851
|
},
|
|
1847
1852
|
{
|
|
1848
1853
|
"path": "docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg",
|
|
@@ -2152,7 +2157,7 @@
|
|
|
2152
2157
|
{
|
|
2153
2158
|
"path": "docs/file-map.md",
|
|
2154
2159
|
"mode": "0000644",
|
|
2155
|
-
"sha256": "
|
|
2160
|
+
"sha256": "60398ae5544a8af7d591d59e7efd5b95614350d53933b98ce133daf6788c9c5b"
|
|
2156
2161
|
},
|
|
2157
2162
|
{
|
|
2158
2163
|
"path": "docs/handoff-template.md",
|
|
@@ -2162,7 +2167,7 @@
|
|
|
2162
2167
|
{
|
|
2163
2168
|
"path": "docs/module-api-changelog.md",
|
|
2164
2169
|
"mode": "0000644",
|
|
2165
|
-
"sha256": "
|
|
2170
|
+
"sha256": "f08b4a0d71d24c26c48f74dd177c4e165d689931d8c140ccfe3f7386a778aa17"
|
|
2166
2171
|
},
|
|
2167
2172
|
{
|
|
2168
2173
|
"path": "docs/modules-contract.md",
|
|
@@ -3272,7 +3277,12 @@
|
|
|
3272
3277
|
{
|
|
3273
3278
|
"path": "modules/copy-desk/module.json",
|
|
3274
3279
|
"mode": "0000644",
|
|
3275
|
-
"sha256": "
|
|
3280
|
+
"sha256": "6bbfd4ff3aec4fbd5692747b734ae9bc683ad47b0743aea60d937c7e646bc43e"
|
|
3281
|
+
},
|
|
3282
|
+
{
|
|
3283
|
+
"path": "modules/copy-desk/proposals.js",
|
|
3284
|
+
"mode": "0000644",
|
|
3285
|
+
"sha256": "b05aefb7fb7f965b92389986b6e53991823abb8c1e8a03e88637ef3d065100c0"
|
|
3276
3286
|
},
|
|
3277
3287
|
{
|
|
3278
3288
|
"path": "modules/copy-desk/registry.js",
|
|
@@ -3282,7 +3292,7 @@
|
|
|
3282
3292
|
{
|
|
3283
3293
|
"path": "modules/copy-desk/routes/copy-desk.js",
|
|
3284
3294
|
"mode": "0000644",
|
|
3285
|
-
"sha256": "
|
|
3295
|
+
"sha256": "1d371bc5e850f12a1e261eb1d4409cb4462943ad0020ae629a3858d2f7063235"
|
|
3286
3296
|
},
|
|
3287
3297
|
{
|
|
3288
3298
|
"path": "modules/copy-desk/tests/copy_flags.mjs",
|
|
@@ -3292,7 +3302,12 @@
|
|
|
3292
3302
|
{
|
|
3293
3303
|
"path": "modules/copy-desk/tests/copy_no_cms.mjs",
|
|
3294
3304
|
"mode": "0000644",
|
|
3295
|
-
"sha256": "
|
|
3305
|
+
"sha256": "2da96945940ab8e27164913e520a0cbe7ba5291967f9cc7e524feed8d26eb082"
|
|
3306
|
+
},
|
|
3307
|
+
{
|
|
3308
|
+
"path": "modules/copy-desk/tests/copy_proposals.mjs",
|
|
3309
|
+
"mode": "0000644",
|
|
3310
|
+
"sha256": "86b7f54494e94407745e46290263ed4463fdb10e5d320276bcb199667ca52097"
|
|
3296
3311
|
},
|
|
3297
3312
|
{
|
|
3298
3313
|
"path": "modules/copy-desk/tests/copy_queue.mjs",
|
|
@@ -3522,7 +3537,7 @@
|
|
|
3522
3537
|
{
|
|
3523
3538
|
"path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
|
|
3524
3539
|
"mode": "0000644",
|
|
3525
|
-
"sha256": "
|
|
3540
|
+
"sha256": "193252b5fb3be984786b23507a400564dfd6ad1899b0778b436882b8b28de39e"
|
|
3526
3541
|
},
|
|
3527
3542
|
{
|
|
3528
3543
|
"path": "modules/dev-box/app/test/unit.mjs",
|
|
@@ -4097,22 +4112,22 @@
|
|
|
4097
4112
|
{
|
|
4098
4113
|
"path": "modules/hall-ui/public/copy-desk.css",
|
|
4099
4114
|
"mode": "0000644",
|
|
4100
|
-
"sha256": "
|
|
4115
|
+
"sha256": "8c44e1147a48d906d354cbd87a196c805c6aa813ce8b8d658402d6d1681622ef"
|
|
4101
4116
|
},
|
|
4102
4117
|
{
|
|
4103
4118
|
"path": "modules/hall-ui/public/copy-desk.html",
|
|
4104
4119
|
"mode": "0000644",
|
|
4105
|
-
"sha256": "
|
|
4120
|
+
"sha256": "96b2dac4c916f1912bd5b5b954bbdea598581e4213cc905ea2ee4db7583d115d"
|
|
4106
4121
|
},
|
|
4107
4122
|
{
|
|
4108
4123
|
"path": "modules/hall-ui/public/copy-desk.js",
|
|
4109
4124
|
"mode": "0000644",
|
|
4110
|
-
"sha256": "
|
|
4125
|
+
"sha256": "a9943681bde027ba6fba7623d6ae0f07ea710f52c2bf111453b9ed9c4ecfef03"
|
|
4111
4126
|
},
|
|
4112
4127
|
{
|
|
4113
4128
|
"path": "modules/hall-ui/public/copy-desk.states.json",
|
|
4114
4129
|
"mode": "0000644",
|
|
4115
|
-
"sha256": "
|
|
4130
|
+
"sha256": "d820ae78793fb58dccd497a1ad3e5668f184b134a2addd053f49cb5e43716d82"
|
|
4116
4131
|
},
|
|
4117
4132
|
{
|
|
4118
4133
|
"path": "modules/hall-ui/public/copy-on-click.js",
|
|
@@ -4637,7 +4652,7 @@
|
|
|
4637
4652
|
{
|
|
4638
4653
|
"path": "modules/hall-ui/public/task.css",
|
|
4639
4654
|
"mode": "0000644",
|
|
4640
|
-
"sha256": "
|
|
4655
|
+
"sha256": "c811a3aafeb8e792315b5ac77c637f31d00169e9ba9e4007f897c276f418f935"
|
|
4641
4656
|
},
|
|
4642
4657
|
{
|
|
4643
4658
|
"path": "modules/hall-ui/public/task.html",
|
|
@@ -4647,12 +4662,12 @@
|
|
|
4647
4662
|
{
|
|
4648
4663
|
"path": "modules/hall-ui/public/task.js",
|
|
4649
4664
|
"mode": "0000644",
|
|
4650
|
-
"sha256": "
|
|
4665
|
+
"sha256": "58776fd75f82144019b4e94fb31e32a7ba75dad991be6a95c56c9682317abe47"
|
|
4651
4666
|
},
|
|
4652
4667
|
{
|
|
4653
4668
|
"path": "modules/hall-ui/public/task.states.json",
|
|
4654
4669
|
"mode": "0000644",
|
|
4655
|
-
"sha256": "
|
|
4670
|
+
"sha256": "fb7112aff3834f88305e9705335dd6b0527cc1cea8f4a1b7d5842b377437da9e"
|
|
4656
4671
|
},
|
|
4657
4672
|
{
|
|
4658
4673
|
"path": "modules/hall-ui/public/triumph.wav",
|
|
@@ -4817,7 +4832,7 @@
|
|
|
4817
4832
|
{
|
|
4818
4833
|
"path": "modules/lifecycle/db-claim-reads.js",
|
|
4819
4834
|
"mode": "0000644",
|
|
4820
|
-
"sha256": "
|
|
4835
|
+
"sha256": "9c8f0af3a00c0b9844196cc94b89bf3dd6fd04bf2f5a8d135fe4e43267ac0d7a"
|
|
4821
4836
|
},
|
|
4822
4837
|
{
|
|
4823
4838
|
"path": "modules/lifecycle/db-claims.js",
|
|
@@ -4872,7 +4887,7 @@
|
|
|
4872
4887
|
{
|
|
4873
4888
|
"path": "modules/lifecycle/db.js",
|
|
4874
4889
|
"mode": "0000644",
|
|
4875
|
-
"sha256": "
|
|
4890
|
+
"sha256": "fb179db8a33c20f06e2805fe4d813efd91ac0eb80fdedc701671fde50e884ad2"
|
|
4876
4891
|
},
|
|
4877
4892
|
{
|
|
4878
4893
|
"path": "modules/lifecycle/dead-deps.js",
|
|
@@ -4952,7 +4967,7 @@
|
|
|
4952
4967
|
{
|
|
4953
4968
|
"path": "modules/lifecycle/lifecycle.js",
|
|
4954
4969
|
"mode": "0000644",
|
|
4955
|
-
"sha256": "
|
|
4970
|
+
"sha256": "d65493ef08cb6373a7f8d22d27e12b5990bc0a60bc4d734fd9df41591d6374a1"
|
|
4956
4971
|
},
|
|
4957
4972
|
{
|
|
4958
4973
|
"path": "modules/lifecycle/merge-lock.js",
|
|
@@ -5082,7 +5097,7 @@
|
|
|
5082
5097
|
{
|
|
5083
5098
|
"path": "modules/lifecycle/routes/tasks.js",
|
|
5084
5099
|
"mode": "0000644",
|
|
5085
|
-
"sha256": "
|
|
5100
|
+
"sha256": "0bbf26870c5959ef6101c535e9a3987d2177d2a8d1b6c91bded9c31350836165"
|
|
5086
5101
|
},
|
|
5087
5102
|
{
|
|
5088
5103
|
"path": "modules/lifecycle/routes/versions.js",
|
|
@@ -6797,12 +6812,12 @@
|
|
|
6797
6812
|
{
|
|
6798
6813
|
"path": "package-lock.json",
|
|
6799
6814
|
"mode": "0000644",
|
|
6800
|
-
"sha256": "
|
|
6815
|
+
"sha256": "cf38af2ed94e3c8e0e37ad7ccb13c1d0be2229a46541b72b149807554ba09f47"
|
|
6801
6816
|
},
|
|
6802
6817
|
{
|
|
6803
6818
|
"path": "package.json",
|
|
6804
6819
|
"mode": "0000644",
|
|
6805
|
-
"sha256": "
|
|
6820
|
+
"sha256": "2214a9bca120dfe87c3b7b611c1733c86ed95f5d715ab25aa6b46a7d4305dd89"
|
|
6806
6821
|
},
|
|
6807
6822
|
{
|
|
6808
6823
|
"path": "public-docs/index.html",
|
|
@@ -7084,10 +7099,15 @@
|
|
|
7084
7099
|
"mode": "0000644",
|
|
7085
7100
|
"sha256": "270101b46772cc8e280bc5a36f66cfae578a901f07b4b5e1f959c9a566ebb5fd"
|
|
7086
7101
|
},
|
|
7102
|
+
{
|
|
7103
|
+
"path": "scripts/gds/copy-apply.js",
|
|
7104
|
+
"mode": "0000644",
|
|
7105
|
+
"sha256": "4b99eabcfcb8c6c472a2161427fc22a43b3208b5fbfc1ffe097e8f1926a44216"
|
|
7106
|
+
},
|
|
7087
7107
|
{
|
|
7088
7108
|
"path": "scripts/gds/copy-inventory.js",
|
|
7089
7109
|
"mode": "0000644",
|
|
7090
|
-
"sha256": "
|
|
7110
|
+
"sha256": "8c798359b51abdd749233e65aa5906732bccc5b9088dae32513cde7949404b6c"
|
|
7091
7111
|
},
|
|
7092
7112
|
{
|
|
7093
7113
|
"path": "scripts/gds/coreB-carve-proof.sh",
|
|
@@ -8462,7 +8482,7 @@
|
|
|
8462
8482
|
{
|
|
8463
8483
|
"path": "src/module-api.js",
|
|
8464
8484
|
"mode": "0000644",
|
|
8465
|
-
"sha256": "
|
|
8485
|
+
"sha256": "175a69332addde58537530b703f16d57631da7a935d3c9ff362ded4ac574da0d"
|
|
8466
8486
|
},
|
|
8467
8487
|
{
|
|
8468
8488
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9852,7 +9872,7 @@
|
|
|
9852
9872
|
{
|
|
9853
9873
|
"path": "tests/hall_collab_world.mjs",
|
|
9854
9874
|
"mode": "0000644",
|
|
9855
|
-
"sha256": "
|
|
9875
|
+
"sha256": "d5608c37de6e17a7ec4c0bb48e0815b7df2726609933baebad8975739a343d2d"
|
|
9856
9876
|
},
|
|
9857
9877
|
{
|
|
9858
9878
|
"path": "tests/hall_error_envelope.mjs",
|
|
@@ -9897,7 +9917,7 @@
|
|
|
9897
9917
|
{
|
|
9898
9918
|
"path": "tests/hall_record_world.mjs",
|
|
9899
9919
|
"mode": "0000644",
|
|
9900
|
-
"sha256": "
|
|
9920
|
+
"sha256": "a810f02a8a82968a4d4742caa2b2c43608f69cee1d3442dd83a8faaec307ebd4"
|
|
9901
9921
|
},
|
|
9902
9922
|
{
|
|
9903
9923
|
"path": "tests/hall_reference_world.mjs",
|
|
@@ -11329,6 +11349,11 @@
|
|
|
11329
11349
|
"mode": "0000644",
|
|
11330
11350
|
"sha256": "fd2fe04c41ee6c739c338b04eb33b8e37ef634b3e2e25f0c041358fcab209bf0"
|
|
11331
11351
|
},
|
|
11352
|
+
{
|
|
11353
|
+
"path": "tests/task_detail_includes.mjs",
|
|
11354
|
+
"mode": "0000644",
|
|
11355
|
+
"sha256": "1e4d480e64d915be750d5bb9581f2da83767800daa7769a551103bc00ca79497"
|
|
11356
|
+
},
|
|
11332
11357
|
{
|
|
11333
11358
|
"path": "tests/task_goal_link.mjs",
|
|
11334
11359
|
"mode": "0000644",
|
|
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
|
|
|
5
5
|
by hand; it regenerates when the spec changes, so it can never drift from the routes.
|
|
6
6
|
|
|
7
7
|
- API version: **v1** (served at `/api/bongos/v1`)
|
|
8
|
-
-
|
|
8
|
+
- 345 operations across 53 resource groups
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -287,6 +287,8 @@ function createClient(opts = {}) {
|
|
|
287
287
|
postCopyDeskFlags: (args) => request("POST", "/copy-desk/flags", { hasBody: true }, args),
|
|
288
288
|
// POST /copy-desk/flags/{id}/close — rank: any-builder — POST /copy-desk/flags/:id/close
|
|
289
289
|
postCopyDeskFlagsIdClose: (args) => request("POST", "/copy-desk/flags/{id}/close", { hasBody: true }, args),
|
|
290
|
+
// POST /copy-desk/proposals — rank: any-builder — POST /copy-desk/proposals
|
|
291
|
+
postCopyDeskProposals: (args) => request("POST", "/copy-desk/proposals", { hasBody: true }, args),
|
|
290
292
|
// GET /copy-desk/queue — rank: any-builder — GET /copy-desk/queue
|
|
291
293
|
getCopyDeskQueue: (args) => request("GET", "/copy-desk/queue", { hasBody: false }, args),
|
|
292
294
|
// GET /copy-desk/strings — rank: any-builder — GET /copy-desk/strings
|
|
@@ -286,6 +286,8 @@ function createClient(opts = {}) {
|
|
|
286
286
|
postCopyDeskFlags: (args) => request("POST", "/copy-desk/flags", { hasBody: true }, args),
|
|
287
287
|
// POST /copy-desk/flags/{id}/close — rank: any-builder — POST /copy-desk/flags/:id/close
|
|
288
288
|
postCopyDeskFlagsIdClose: (args) => request("POST", "/copy-desk/flags/{id}/close", { hasBody: true }, args),
|
|
289
|
+
// POST /copy-desk/proposals — rank: any-builder — POST /copy-desk/proposals
|
|
290
|
+
postCopyDeskProposals: (args) => request("POST", "/copy-desk/proposals", { hasBody: true }, args),
|
|
289
291
|
// GET /copy-desk/queue — rank: any-builder — GET /copy-desk/queue
|
|
290
292
|
getCopyDeskQueue: (args) => request("GET", "/copy-desk/queue", { hasBody: false }, args),
|
|
291
293
|
// GET /copy-desk/strings — rank: any-builder — GET /copy-desk/strings
|
|
@@ -230,6 +230,8 @@ export interface PostCopyDeskFlagsIdCloseRequest { status: string; resolution_no
|
|
|
230
230
|
export interface PostCopyDeskFlagsIdCloseResponse { ok: boolean; flag: unknown }
|
|
231
231
|
export interface PostCopyDeskFlagsRequest { scope?: string; surface: string; string_id?: string; reason: string; kind?: string }
|
|
232
232
|
export interface PostCopyDeskFlagsResponse { ok: boolean; flag: unknown }
|
|
233
|
+
export interface PostCopyDeskProposalsRequest { string_id: string; proposed_text: string; why: string; file?: string; line?: number; flag_id?: number }
|
|
234
|
+
export interface PostCopyDeskProposalsResponse { ok: boolean; task: unknown; created: boolean; patch?: unknown }
|
|
233
235
|
export interface PostCostRequest { task_id?: number; amount_usd: number; category: string; description?: string; source?: string; source_ref?: string; recorded_at?: string; fresh_input_usd?: number; cache_read_usd?: number; cache_write_usd?: number; output_usd?: number; builder_id?: number; skill_name?: string }
|
|
234
236
|
export interface PostCostResponse { skipped?: boolean; reason?: unknown; id?: unknown; recorded_at?: unknown }
|
|
235
237
|
export interface PostDependenciesResponse { ok: boolean; dependency: unknown }
|
|
@@ -595,6 +597,8 @@ export interface BongosClient {
|
|
|
595
597
|
postCopyDeskFlags(args: RequestArgs & { body: PostCopyDeskFlagsRequest }): Promise<PostCopyDeskFlagsResponse>;
|
|
596
598
|
/** POST /copy-desk/flags/{id}/close — rank: any-builder */
|
|
597
599
|
postCopyDeskFlagsIdClose(args: RequestArgs & { body: PostCopyDeskFlagsIdCloseRequest }): Promise<PostCopyDeskFlagsIdCloseResponse>;
|
|
600
|
+
/** POST /copy-desk/proposals — rank: any-builder */
|
|
601
|
+
postCopyDeskProposals(args: RequestArgs & { body: PostCopyDeskProposalsRequest }): Promise<PostCopyDeskProposalsResponse>;
|
|
598
602
|
/** GET /copy-desk/queue — rank: any-builder */
|
|
599
603
|
getCopyDeskQueue(args?: RequestArgs): Promise<ApiResponse>;
|
|
600
604
|
/** GET /copy-desk/strings — rank: any-builder */
|
|
@@ -283,6 +283,8 @@ export function createClient(opts = {}) {
|
|
|
283
283
|
postCopyDeskFlags: (args) => request("POST", "/copy-desk/flags", { hasBody: true }, args),
|
|
284
284
|
// POST /copy-desk/flags/{id}/close — rank: any-builder — POST /copy-desk/flags/:id/close
|
|
285
285
|
postCopyDeskFlagsIdClose: (args) => request("POST", "/copy-desk/flags/{id}/close", { hasBody: true }, args),
|
|
286
|
+
// POST /copy-desk/proposals — rank: any-builder — POST /copy-desk/proposals
|
|
287
|
+
postCopyDeskProposals: (args) => request("POST", "/copy-desk/proposals", { hasBody: true }, args),
|
|
286
288
|
// GET /copy-desk/queue — rank: any-builder — GET /copy-desk/queue
|
|
287
289
|
getCopyDeskQueue: (args) => request("GET", "/copy-desk/queue", { hasBody: false }, args),
|
|
288
290
|
// GET /copy-desk/strings — rank: any-builder — GET /copy-desk/strings
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# 0233 — A copy proposal is a task carrying a patch, and the applier is the only writer
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-02
|
|
5
|
+
- **Tasks:** [#1003114](https://cloudbongos.com/builders#/task/1003114) (R03 of goal 1000074, the artist's loop) — builds on [#1003112](https://cloudbongos.com/builders#/task/1003112) (R01, the registry) and [#1003113](https://cloudbongos.com/builders#/task/1003113) (R02, the flag and the queue)
|
|
6
|
+
- **Extends:** [ADR 0081](<redacted>.md) — the repo is the source of truth, with swappable tool adapters that land their output as a committed diff. This applies that contract to TEXT.
|
|
7
|
+
- **Extends:** [ADR 0178](<redacted>.md) — which pinned that `copy_desk_flags` may never hold replacement text, and named this task as the one that would have to keep that true while adding an editing affordance.
|
|
8
|
+
- **Related:** 0083 (module system, ports), 0086 (goals), 0016 (authority resolved server-side), 0162 (a verdict is advisory, never self-executing), 0197 (the ui-design module)
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
Goal 1000074 asks for two things that pull against each other, and says so in its own text.
|
|
13
|
+
|
|
14
|
+
The artist must be able to **change copy without an engineer** — that is the criterion
|
|
15
|
+
`artist-edits-land-as-tasks`. And there must be **no live CMS**: "every copy change is a claimed,
|
|
16
|
+
graded, shipped GDS task ... never a write path that bypasses the ledger."
|
|
17
|
+
|
|
18
|
+
R02 built the half that needs no reconciliation: the **flag**, a judgement with a pointer. It
|
|
19
|
+
deliberately shipped with no way to say what the copy *should* say, because the obvious place to put
|
|
20
|
+
that — a column beside the flag — is one plausible read away from making the copy desk a copy source.
|
|
21
|
+
ADR 0178 §3 pinned the absence and named the erosion path: not a dramatic "add a CMS" commit, but one
|
|
22
|
+
plausible column, then one plausible read of it, and the ledger rule is dead with nobody having
|
|
23
|
+
decided to kill it.
|
|
24
|
+
|
|
25
|
+
So R03 is the load-bearing one. An affordance that edits, and a ledger that must record every edit,
|
|
26
|
+
are one bad column apart from being incompatible.
|
|
27
|
+
|
|
28
|
+
## Decision
|
|
29
|
+
|
|
30
|
+
### 1. The shape is borrowed from the design half, not invented
|
|
31
|
+
|
|
32
|
+
ADR 0081 already solved this exact problem for *form*. A tool authors; `adapter.export()` lands the
|
|
33
|
+
tool's output into the repo as a committed diff under a claimed task, inside a path guard; the repo
|
|
34
|
+
stays the source of truth and the tool never writes to production. The `ui-design` module has run
|
|
35
|
+
that contract for design tokens and component surfaces since ADR 0197.
|
|
36
|
+
|
|
37
|
+
**A copy proposal is that same contract for text.** The artist is the tool, the copy desk is where
|
|
38
|
+
they author, and the proposal is the export. Nothing about this needed a new idea — it needed the
|
|
39
|
+
existing one applied to the other half of the design contract, which is what makes the two halves
|
|
40
|
+
read as one system rather than two features that happen to share a goal.
|
|
41
|
+
|
|
42
|
+
### 2. The proposal lives on the TASK, as a fenced patch — not in a table
|
|
43
|
+
|
|
44
|
+
`POST /copy-desk/proposals` creates a **GDS task** whose description carries a ```` ```copy-proposal ````
|
|
45
|
+
block: schema, string id, surface, file, line, render path, the current wording and the proposed one.
|
|
46
|
+
|
|
47
|
+
This is the whole answer to the tension, and every part of it is doing work:
|
|
48
|
+
|
|
49
|
+
- **The ledger is where a proposal belongs anyway.** The methodology already requires every change to
|
|
50
|
+
be a claimed task. A proposal is a change that has not happened yet, so the task IS the record —
|
|
51
|
+
not a shadow of one kept somewhere else and reconciled later.
|
|
52
|
+
- **Nothing renders a task description as product copy**, so the wording has no path to a reader that
|
|
53
|
+
does not pass through a claim, a diff and a grade.
|
|
54
|
+
- **A fenced block is readable by the reviewer and parseable by the applier.** The point of a proposal
|
|
55
|
+
is that it is arguable before it lands; a reviewer has to be able to see the change without running
|
|
56
|
+
anything.
|
|
57
|
+
- **`copy-desk` gains no migration.** `tests/copy_no_cms.mjs` asserts the module still has exactly one,
|
|
58
|
+
because the obvious-but-wrong implementation of R03 is a `copy_desk_proposals` table — which is
|
|
59
|
+
ADR 0178's forbidden column with extra steps.
|
|
60
|
+
|
|
61
|
+
The module's manifest gains `consumes: ["lifecycle"]` and calls `lifecycle.createTask` through the
|
|
62
|
+
kernel port. That direction is the safe one and is the point: this module can put a work request
|
|
63
|
+
**into** the ledger, and nothing can pull copy **out** of this module. `provides` stays empty.
|
|
64
|
+
|
|
65
|
+
### 3. The applier re-derives the position; the registry never stores one
|
|
66
|
+
|
|
67
|
+
`scripts/gds/copy-apply.js` is what whoever claims the task runs. It does **not** trust the patch's
|
|
68
|
+
line number. It re-runs R01's own extractor over the working tree, finds the occurrence there, and
|
|
69
|
+
replaces exactly the characters that string occupies.
|
|
70
|
+
|
|
71
|
+
That required teaching the extractor to record a **source span** alongside every string it finds —
|
|
72
|
+
and deliberately **dropping it from the committed registry**. An offset goes stale the moment anything
|
|
73
|
+
above it in the file changes, while the registry's id is designed to survive precisely that movement;
|
|
74
|
+
a committed offset would be wrong far more often than the id ever is. So the span is a live fact,
|
|
75
|
+
recomputed at the moment of the edit, by the same code that decided the string was copy in the first
|
|
76
|
+
place. One set of matching rules, in one file, used by both the inventory and the applier — which is
|
|
77
|
+
what stops the applier from editing a byte the inventory would never have called copy.
|
|
78
|
+
|
|
79
|
+
The registry's committed bytes are unchanged by this; `--check` and `tests/copy_inventory.mjs` are
|
|
80
|
+
what prove it.
|
|
81
|
+
|
|
82
|
+
### 4. The placeholder arithmetic is the rule that makes a patch mechanical
|
|
83
|
+
|
|
84
|
+
The registry writes `{…}` where the source has `${expr}`, so an artist edits a readable sentence with
|
|
85
|
+
holes in it. The applier puts the source's **own** expressions back, the k-th hole taking the k-th
|
|
86
|
+
expression — so an artist may move a value within a sentence (`Hello ${name}, welcome back` →
|
|
87
|
+
`Good to see you, ${name} — welcome back`) but may not add or drop one. The count is checked when the
|
|
88
|
+
proposal is made **and again against the real source** at apply time, because the source is what is
|
|
89
|
+
about to be written.
|
|
90
|
+
|
|
91
|
+
That single rule is what turns "a note for an engineer" into "a change that lands with one command",
|
|
92
|
+
which is the difference between meeting the criterion and describing it.
|
|
93
|
+
|
|
94
|
+
### 5. Every ambiguity is a named refusal, because a wrong edit is worse than no edit
|
|
95
|
+
|
|
96
|
+
The applier refuses, by name and with the reason on one line, when: the wording is already gone
|
|
97
|
+
(`target_gone`), the sentence appears at several sites and the line does not disambiguate
|
|
98
|
+
(`ambiguous_target`), the literal is escaped so no exact span exists (`no_exact_span`), the
|
|
99
|
+
placeholder count does not hold (`placeholder_mismatch`), or the file is outside the surface the
|
|
100
|
+
registry defines (`path_outside_surface` — the direct analogue of the design adapter's path-escape
|
|
101
|
+
guard, derived from R01's own `SURFACES` rather than a second list).
|
|
102
|
+
|
|
103
|
+
None of these means the wording was wrong. They mean a person should look. And after writing, the
|
|
104
|
+
applier regenerates the registry and **checks that what it wrote is what the inventory now reads** —
|
|
105
|
+
an independent read of the file it just edited.
|
|
106
|
+
|
|
107
|
+
### 6. The command takes no id, because the task cannot know its own
|
|
108
|
+
|
|
109
|
+
A proposal task has to print the exact line that lands it, and its description is composed *before*
|
|
110
|
+
the row exists. Every way of closing that gap is worse than the rule it would paper over — a
|
|
111
|
+
placeholder is a step for the claimer to get wrong, and a second write to patch the description in
|
|
112
|
+
afterwards buys a port widening and a failure mode for a string.
|
|
113
|
+
|
|
114
|
+
So the bare call, `node scripts/gds/copy-apply.js`, acts on **the task the caller is holding**.
|
|
115
|
+
Holding no claim is a named refusal that says to claim it first. The command may only run under a
|
|
116
|
+
claim — that is the whole feature — and a claim already names a task, so the constraint and the
|
|
117
|
+
ergonomics are the same fact. `--task <id>` remains for someone holding several.
|
|
118
|
+
|
|
119
|
+
### 7. Proposing is open to every rank, and lands in `backlog`
|
|
120
|
+
|
|
121
|
+
Same floor as filing, for ADR 0178 §4's reason: an artist is a craft, not a rank, and a rank floor
|
|
122
|
+
would hide the loop from the people most likely to use it. The quality gates are the required `why`
|
|
123
|
+
(the same judgement floor a flag's reason carries), the placeholder arithmetic, and the fact that the
|
|
124
|
+
target is resolved **server-side from the registry** — a caller cannot describe a change to a
|
|
125
|
+
sentence that does not exist.
|
|
126
|
+
|
|
127
|
+
The task is created at `backlog`, so a proposal is a *request*: it needs the board's ordinary triage
|
|
128
|
+
before anyone can claim it. Landing it `ready` would let anyone with an opinion mint claimable work.
|
|
129
|
+
|
|
130
|
+
## Consequences
|
|
131
|
+
|
|
132
|
+
- **The write surface of `copy-desk` is now three verbs, not two**, and `tests/copy_no_cms.mjs` says so
|
|
133
|
+
in a diff a reviewer can see — which is exactly what R02 built that list for.
|
|
134
|
+
- **`scripts/gds/copy-apply.js` is the only thing in this feature that writes a surface file**, it is a
|
|
135
|
+
command rather than a route, and the test asserts no file in the module's request path imports it.
|
|
136
|
+
Naming it is fine and required: the task tells its claimer the exact line to run.
|
|
137
|
+
- **The `lifecycle` port gained `findTaskBySourceRef`** so a double-submitted proposal resolves to the
|
|
138
|
+
task it already made. A genuinely different second attempt at the same string is a new task, which
|
|
139
|
+
is right — two people can disagree about the fix, and the board should hold both.
|
|
140
|
+
- **A proposal targets ONE occurrence.** When a string is shown in several places the caller must say
|
|
141
|
+
which, and the refusal points at the duplicates the copy desk already surfaces. Rewording a sentence
|
|
142
|
+
said in seven places is usually the wrong answer to it being said seven times.
|
|
143
|
+
- **A quoted literal containing an escape has no span**, so ~9 of ~1,600 inventoried strings cannot be
|
|
144
|
+
patched by command and are refused by name. Buying them would mean an escape-aware index map — a
|
|
145
|
+
second place for the escaping rules to be wrong, for nine strings.
|
|
146
|
+
- **The copy desk route file moved off `console.error` to the doorway's `api.logger`**, the seam
|
|
147
|
+
builder-settings / dev-box / ideas / government already use. Structured, attributable, and it keeps
|
|
148
|
+
an error's stack instead of stringifying it.
|
|
149
|
+
- **R05 still owns the hard question.** Nothing here gates a ship. Whether an artist's judgement can
|
|
150
|
+
ever block, and how that coexists with ADR 0162, is R05's to decide — a proposal is a work request
|
|
151
|
+
by construction until it does.
|
package/docs/adr/README.md
CHANGED
|
@@ -320,4 +320,6 @@ This keeps the decision history honest and traceable.
|
|
|
320
320
|
| 0226 | [**One stale-claim timer; reporting degrades where enforcement refuses** ([task 1003476](https://cloudbongos.com/builders#/task/1003476), goal 1000072, criterion `legibility-sort-and-staleness`). "How long may a claim go silent" had FOUR answers and none of them was the project's: the sweeper that actually releases a claim read `GDS_STALE_CLAIM_HOURS` (else 6h); `GET /builders/roster` and `GET /builders/:id/active-claims` each hardcoded 24h in byte-identical blocks; and the two `session_pulses` helpers defaulted to 30 minutes. The 6-vs-24 pair is the harm: the roster is the screen an Archon opens to decide whether to intervene, and for **eighteen hours** after the sweeper had released a claim, stamped its session log `[auto-released:stale]` and returned the task to `ready`, that screen still showed it healthy — while the roster's own comment claimed to "mirror" the sweeper, true of the definition and false of the number. Decision: ONE resolver, `src/stale-timer.js` (a KERNEL file, the `instance-config.js`/`branding.js` category — host config a module reads, never hardcodes), reached by modules through the doorway and by core routes and the CLI directly. Precedence: explicit override (`--threshold-hours`, `?stale_hours=` — a LENS, labelled `query`, never a reconfiguration) → `<PREFIX>_STALE_CLAIM_HOURS` via `instance-config.resolveEnv`, whose legacy prefix chain still honours the live `GDS_STALE_CLAIM_HOURS` so **nothing in a running deployment has to change** (and which is the channel ADR 0190's runner already patches into `web.env`) → **6**, the SWEEP's historical number and deliberately not the roster's 24, because the sweep is the surface that acts and unifying on 24 would have made every project three times more patient with a dead claim under cover of a refactor. Bound 1..720h — the range `/builders/roster` has published since V3.R14; one timer, one bound. Every surface that prints the timer prints its PROVENANCE (`stale_hours_source`, the sweep header, the auto-release note): a number without it is exactly what let 6 and 24 coexist unnoticed. **The fail posture is asymmetric and that is the decision**: the resolver reports an unusable value rather than throwing, then a surface that only READS degrades to the default and names the bad var (500-ing the roster an Archon opened to find the problem would hide the problem), while the SWEEP refuses to run at all (it releases other people's claims and stamps their logs; no reload undoes that) — a surface that only reads may guess and say so, a surface that acts may not guess. Both routes now share one `staleHoursFor(req, res)`; two copies of a literal is how the drift happened twice inside one file. Rejected: a new `config/stale.json` tier (the env route already IS the per-project channel); making 24 the shared default; throwing from the resolver (forces a second reader — the original bug); folding in the boards' 14/30/60-DAY task buckets (a different question — days, about a task, a browsing lens with no enforcement behind it); a DB row + settings UI (a reasonable next step, but the defect was four numbers, not a missing knob — a row added before the readers agreed would have been a fifth).](<redacted>.md) | GDS / claims + ops |
|
|
321
321
|
| 0232 | [**Rot is derived, not swept; and `water` is the only new verb** ([task 1003279](https://cloudbongos.com/builders#/task/1003279) · goal 1000072, criterion `legibility-sort-and-staleness`). The inbox half [ADR 0226](<redacted>.md) split out and left open. Owner decision (2026-08-25): a goal or task past the timer raises a **needs-attention line** — not an automatic status change (it mutates real work with no human in the loop, and a wrong timer then silently freezes it) and not a nightly digest (a notification is not an action, and one you scroll past is indistinguishable from the silent decay it was meant to fix). The build note asked for an idempotent poller; the decision is that **there is no poller and no items table** — a line is a `SELECT` comparing the thing's rot clock to the timer, so "exactly one item per stale thing, not one per sweep" is true by arithmetic rather than by deduplication code that must be written correctly and then stay correct, and clearing falls out for free (an edit moves `updated_at`, a decision moves `status`, watering moves the mark). A stored item would have been a cache of a query whose inputs it could not observe. **The exclusions are the load-bearing part**: rot is work nobody picked up (`backlog`/`ready`, and `open` goals), and `active` is deliberately excluded because the claim-silence timer already reports it in hours — listing it here in days would put one rot in two inboxes under two numbers, the 6h-vs-24h split one tier up; `blocked`, `completed`/`confirmed` and the terminal states are excluded for the same reason, each naming the surface that owns it. The clock is **not a new definition of touched** — it is `task-lib.js`'s published `max(updated_at, shipped_at)` (task 1003278) plus a water mark, pinned by a test asserting the domain expression appears verbatim in migration `core_231`'s index expressions, because drift there is silent. The timer is a **second** timer and defends itself as a different question (days vs hours, DB vs env, asks a human vs releases a claim), stored in a new unseeded generic `project_settings` table — no row means "never configured", which is what lets a settings page tell that apart from "set to the default" — with ADR 0226's precedence and its reporting-degrades fail posture, and an unreachable DB explicitly not treated as a bad value. Three of the four verbs (**prioritise/prune/kill**) reuse `promote`/`demote`/`abandon`/`archive`, the task-1003066 precedent, so they add no permission; only `water` is new, gated on ownership OR `builder.roster.read` inside the handler (a creator holding no atom must still get through). Verbs are **served with each row** rather than hardcoded, since a goal legitimately gets two where a task gets four. Rejected: an items table, a sweep, a digest, auto-demotion, and a fourth verb on goals that would render a dead button.](<redacted>.md) | GDS / legibility + hall |
|
|
322
322
|
|
|
323
|
+
| 0233 | [**A copy proposal is a task carrying a patch, and the applier is the only writer** ([task 1003114](https://cloudbongos.com/builders#/task/1003114) · goal 1000074, criterion `artist-edits-land-as-tasks`). Goal 1000074 asks for two things that pull against each other in its own text: the artist must change copy WITHOUT an engineer, and there must be NO LIVE CMS because every copy change is a claimed, graded, shipped task. R02 shipped the half that needs no reconciliation (the flag) and [ADR 0178](<redacted>.md) §3 pinned the absence of any column that could hold replacement text, naming this task as the one that would have to keep it true. Decisions: (1) the shape is **borrowed from the design half, not invented** — ADR 0081 already has a tool authoring and an `adapter.export()` landing the output as a committed diff under a claim inside a path guard, so the artist is the tool, the copy desk is where they author, and the proposal is the export. (2) A proposal is a **TASK carrying a fenced `copy-proposal` patch**, never a row: the ledger is where a proposal belongs anyway, nothing renders a task description as product copy, a reviewer can read the change without running anything, and `copy-desk` gains NO migration (the obvious-but-wrong R03 is a `copy_desk_proposals` table, which is the forbidden column with extra steps). The module gains `consumes: ["lifecycle"]` and calls `createTask` through the port — it can put a request INTO the ledger and nothing can pull copy OUT of it. (3) `scripts/gds/copy-apply.js` **re-derives the position** rather than trusting the patch, which required teaching R01s extractor to record a source SPAN and deliberately dropping it from the committed registry: an offset goes stale the moment anything above it changes, while the id is designed to survive exactly that movement, so the span is recomputed at the moment of the edit by the same code that decided the string was copy. (4) The **placeholder arithmetic** is what makes a patch mechanical rather than a note for an engineer — the applier puts the sources own `${expr}` back, k-th hole to k-th expression, so an artist may MOVE a value within a sentence but never add or drop one, checked at proposal time and again against the real source. (5) Every ambiguity is a **named refusal** (`target_gone`, `ambiguous_target`, `no_exact_span`, `placeholder_mismatch`, `path_outside_surface` — the design adapters path guard, derived from R01s own SURFACES), because a wrong edit is worse than no edit; after writing, the applier regenerates the registry and checks that what it wrote is what the inventory now reads. (6) Proposing is **open to every rank** (an artist is a craft, not a rank) and lands at `backlog`, so a proposal is a request that passes ordinary triage. Rejected: a proposals table, a column beside the flag, trusting the patchs line number, an escape-aware index map to buy spans for nine strings, auto-closing the flag a proposal answers.](<redacted>.md) | copy-desk / the artist loop |
|
|
324
|
+
|
|
323
325
|
> ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
|