@bongos/core 1.19.1063 → 1.19.1064
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 +72 -37
- package/.claude/skills/grade-recover/SKILL.md +1 -0
- package/.claude/skills/tweak/SKILL.md +70 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +10 -0
- package/clients/bongos-client/index.mjs +6 -0
- package/docs/api/openapi.json +258 -3
- package/docs/api-reference.md +5 -2
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +2 -0
- package/modules/lifecycle/db-grade.js +16 -0
- package/modules/lifecycle/migrations/lifecycle_013_task_visual_slots.sql +52 -0
- package/modules/lifecycle/page-tweak-hold.js +74 -0
- package/modules/lifecycle/page-tweak-reads.js +33 -1
- package/modules/lifecycle/routes/tasks.js +4 -2
- package/modules/lifecycle/routes/visuals.js +82 -0
- package/modules/lifecycle/task-visual-db.js +40 -1
- package/modules/lifecycle/task-visuals.js +24 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/gds/copy-apply.js +277 -1
- package/scripts/gds/page-reader.js +2 -0
- package/scripts/gds/ship-finish.js +27 -2
- package/scripts/gds/ship-flow.js +17 -1
- package/scripts/gds/ship-land.js +30 -7
- package/scripts/gds/ship-merge.js +43 -15
- package/scripts/gds/strand-watch.js +41 -1
- package/scripts/gds/task.js +23 -5
- package/scripts/gds/tweak-renders.js +200 -0
- package/src/module-api.js +1 -1
- package/tests/page_tweak_hold.mjs +265 -0
- package/tests/publish_status_branch.mjs +23 -0
- package/tests/task_visual_slots.mjs +161 -0
- package/tests/tweak_batch_apply.mjs +265 -0
package/docs/api/openapi.json
CHANGED
|
@@ -20455,6 +20455,189 @@
|
|
|
20455
20455
|
]
|
|
20456
20456
|
}
|
|
20457
20457
|
},
|
|
20458
|
+
"/tasks/{id}/visuals": {
|
|
20459
|
+
"get": {
|
|
20460
|
+
"operationId": "get_tasks_id_visuals",
|
|
20461
|
+
"tags": [
|
|
20462
|
+
"tasks"
|
|
20463
|
+
],
|
|
20464
|
+
"summary": "GET /tasks/:id/visuals",
|
|
20465
|
+
"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).",
|
|
20466
|
+
"x-rank": "any-builder",
|
|
20467
|
+
"x-source": "modules/lifecycle/routes/visuals.js",
|
|
20468
|
+
"parameters": [
|
|
20469
|
+
{
|
|
20470
|
+
"name": "id",
|
|
20471
|
+
"in": "path",
|
|
20472
|
+
"required": true,
|
|
20473
|
+
"schema": {
|
|
20474
|
+
"type": "string"
|
|
20475
|
+
},
|
|
20476
|
+
"description": "Path parameter `id`."
|
|
20477
|
+
}
|
|
20478
|
+
],
|
|
20479
|
+
"responses": {
|
|
20480
|
+
"200": {
|
|
20481
|
+
"description": "Success.",
|
|
20482
|
+
"content": {
|
|
20483
|
+
"application/json": {
|
|
20484
|
+
"schema": {
|
|
20485
|
+
"$ref": "#/components/schemas/GetTasksIdVisualsResponse"
|
|
20486
|
+
}
|
|
20487
|
+
}
|
|
20488
|
+
}
|
|
20489
|
+
},
|
|
20490
|
+
"400": {
|
|
20491
|
+
"$ref": "#/components/responses/BadRequest"
|
|
20492
|
+
},
|
|
20493
|
+
"401": {
|
|
20494
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
20495
|
+
},
|
|
20496
|
+
"403": {
|
|
20497
|
+
"$ref": "#/components/responses/Forbidden"
|
|
20498
|
+
},
|
|
20499
|
+
"404": {
|
|
20500
|
+
"$ref": "#/components/responses/NotFound"
|
|
20501
|
+
}
|
|
20502
|
+
},
|
|
20503
|
+
"security": [
|
|
20504
|
+
{
|
|
20505
|
+
"builderSession": []
|
|
20506
|
+
}
|
|
20507
|
+
]
|
|
20508
|
+
}
|
|
20509
|
+
},
|
|
20510
|
+
"/tasks/{id}/visuals/{slot}": {
|
|
20511
|
+
"post": {
|
|
20512
|
+
"operationId": "post_tasks_id_visuals_slot",
|
|
20513
|
+
"tags": [
|
|
20514
|
+
"tasks"
|
|
20515
|
+
],
|
|
20516
|
+
"summary": "POST /tasks/:id/visuals/:slot",
|
|
20517
|
+
"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).",
|
|
20518
|
+
"x-rank": "any-builder",
|
|
20519
|
+
"x-source": "modules/lifecycle/routes/visuals.js",
|
|
20520
|
+
"parameters": [
|
|
20521
|
+
{
|
|
20522
|
+
"name": "id",
|
|
20523
|
+
"in": "path",
|
|
20524
|
+
"required": true,
|
|
20525
|
+
"schema": {
|
|
20526
|
+
"type": "string"
|
|
20527
|
+
},
|
|
20528
|
+
"description": "Path parameter `id`."
|
|
20529
|
+
},
|
|
20530
|
+
{
|
|
20531
|
+
"name": "slot",
|
|
20532
|
+
"in": "path",
|
|
20533
|
+
"required": true,
|
|
20534
|
+
"schema": {
|
|
20535
|
+
"type": "string"
|
|
20536
|
+
},
|
|
20537
|
+
"description": "Path parameter `slot`."
|
|
20538
|
+
}
|
|
20539
|
+
],
|
|
20540
|
+
"requestBody": {
|
|
20541
|
+
"required": true,
|
|
20542
|
+
"content": {
|
|
20543
|
+
"application/json": {
|
|
20544
|
+
"schema": {
|
|
20545
|
+
"$ref": "#/components/schemas/PostTasksIdVisualsSlotRequest"
|
|
20546
|
+
}
|
|
20547
|
+
}
|
|
20548
|
+
},
|
|
20549
|
+
"x-validated": true
|
|
20550
|
+
},
|
|
20551
|
+
"responses": {
|
|
20552
|
+
"200": {
|
|
20553
|
+
"description": "Success.",
|
|
20554
|
+
"content": {
|
|
20555
|
+
"application/json": {
|
|
20556
|
+
"schema": {
|
|
20557
|
+
"$ref": "#/components/schemas/PostTasksIdVisualsSlotResponse"
|
|
20558
|
+
}
|
|
20559
|
+
}
|
|
20560
|
+
}
|
|
20561
|
+
},
|
|
20562
|
+
"400": {
|
|
20563
|
+
"$ref": "#/components/responses/ValidationFailed"
|
|
20564
|
+
},
|
|
20565
|
+
"401": {
|
|
20566
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
20567
|
+
},
|
|
20568
|
+
"403": {
|
|
20569
|
+
"$ref": "#/components/responses/Forbidden"
|
|
20570
|
+
},
|
|
20571
|
+
"404": {
|
|
20572
|
+
"$ref": "#/components/responses/NotFound"
|
|
20573
|
+
}
|
|
20574
|
+
},
|
|
20575
|
+
"security": [
|
|
20576
|
+
{
|
|
20577
|
+
"builderSession": []
|
|
20578
|
+
}
|
|
20579
|
+
]
|
|
20580
|
+
},
|
|
20581
|
+
"delete": {
|
|
20582
|
+
"operationId": "delete_tasks_id_visuals_slot",
|
|
20583
|
+
"tags": [
|
|
20584
|
+
"tasks"
|
|
20585
|
+
],
|
|
20586
|
+
"summary": "DELETE /tasks/:id/visuals/:slot",
|
|
20587
|
+
"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).",
|
|
20588
|
+
"x-rank": "any-builder",
|
|
20589
|
+
"x-source": "modules/lifecycle/routes/visuals.js",
|
|
20590
|
+
"parameters": [
|
|
20591
|
+
{
|
|
20592
|
+
"name": "id",
|
|
20593
|
+
"in": "path",
|
|
20594
|
+
"required": true,
|
|
20595
|
+
"schema": {
|
|
20596
|
+
"type": "string"
|
|
20597
|
+
},
|
|
20598
|
+
"description": "Path parameter `id`."
|
|
20599
|
+
},
|
|
20600
|
+
{
|
|
20601
|
+
"name": "slot",
|
|
20602
|
+
"in": "path",
|
|
20603
|
+
"required": true,
|
|
20604
|
+
"schema": {
|
|
20605
|
+
"type": "string"
|
|
20606
|
+
},
|
|
20607
|
+
"description": "Path parameter `slot`."
|
|
20608
|
+
}
|
|
20609
|
+
],
|
|
20610
|
+
"responses": {
|
|
20611
|
+
"200": {
|
|
20612
|
+
"description": "Success.",
|
|
20613
|
+
"content": {
|
|
20614
|
+
"application/json": {
|
|
20615
|
+
"schema": {
|
|
20616
|
+
"$ref": "#/components/schemas/DeleteTasksIdVisualsSlotResponse"
|
|
20617
|
+
}
|
|
20618
|
+
}
|
|
20619
|
+
}
|
|
20620
|
+
},
|
|
20621
|
+
"400": {
|
|
20622
|
+
"$ref": "#/components/responses/BadRequest"
|
|
20623
|
+
},
|
|
20624
|
+
"401": {
|
|
20625
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
20626
|
+
},
|
|
20627
|
+
"403": {
|
|
20628
|
+
"$ref": "#/components/responses/Forbidden"
|
|
20629
|
+
},
|
|
20630
|
+
"404": {
|
|
20631
|
+
"$ref": "#/components/responses/NotFound"
|
|
20632
|
+
}
|
|
20633
|
+
},
|
|
20634
|
+
"security": [
|
|
20635
|
+
{
|
|
20636
|
+
"builderSession": []
|
|
20637
|
+
}
|
|
20638
|
+
]
|
|
20639
|
+
}
|
|
20640
|
+
},
|
|
20458
20641
|
"/tasks/{id}/vote": {
|
|
20459
20642
|
"post": {
|
|
20460
20643
|
"operationId": "post_tasks_id_vote",
|
|
@@ -21282,6 +21465,21 @@
|
|
|
21282
21465
|
"visual_url"
|
|
21283
21466
|
]
|
|
21284
21467
|
},
|
|
21468
|
+
"DeleteTasksIdVisualsSlotResponse": {
|
|
21469
|
+
"type": "object",
|
|
21470
|
+
"properties": {
|
|
21471
|
+
"ok": {
|
|
21472
|
+
"type": "boolean"
|
|
21473
|
+
},
|
|
21474
|
+
"slot": {},
|
|
21475
|
+
"visual_url": {}
|
|
21476
|
+
},
|
|
21477
|
+
"required": [
|
|
21478
|
+
"ok",
|
|
21479
|
+
"slot",
|
|
21480
|
+
"visual_url"
|
|
21481
|
+
]
|
|
21482
|
+
},
|
|
21285
21483
|
"ErrorEnvelope": {
|
|
21286
21484
|
"type": "object",
|
|
21287
21485
|
"required": [
|
|
@@ -23203,6 +23401,21 @@
|
|
|
23203
23401
|
"count"
|
|
23204
23402
|
]
|
|
23205
23403
|
},
|
|
23404
|
+
"GetTasksIdVisualsResponse": {
|
|
23405
|
+
"type": "object",
|
|
23406
|
+
"properties": {
|
|
23407
|
+
"ok": {
|
|
23408
|
+
"type": "boolean"
|
|
23409
|
+
},
|
|
23410
|
+
"task_id": {},
|
|
23411
|
+
"slots": {}
|
|
23412
|
+
},
|
|
23413
|
+
"required": [
|
|
23414
|
+
"ok",
|
|
23415
|
+
"task_id",
|
|
23416
|
+
"slots"
|
|
23417
|
+
]
|
|
23418
|
+
},
|
|
23206
23419
|
"GetTasksNewcomerFloorResponse": {
|
|
23207
23420
|
"type": "object",
|
|
23208
23421
|
"properties": {
|
|
@@ -28179,6 +28392,48 @@
|
|
|
28179
28392
|
"bytes"
|
|
28180
28393
|
]
|
|
28181
28394
|
},
|
|
28395
|
+
"PostTasksIdVisualsSlotRequest": {
|
|
28396
|
+
"type": "object",
|
|
28397
|
+
"properties": {
|
|
28398
|
+
"image_b64": {
|
|
28399
|
+
"type": "string",
|
|
28400
|
+
"minLength": 1,
|
|
28401
|
+
"maxLength": 7340032
|
|
28402
|
+
},
|
|
28403
|
+
"content_type": {
|
|
28404
|
+
"type": "string",
|
|
28405
|
+
"minLength": 1,
|
|
28406
|
+
"maxLength": 100
|
|
28407
|
+
},
|
|
28408
|
+
"alt": {
|
|
28409
|
+
"type": "string"
|
|
28410
|
+
}
|
|
28411
|
+
},
|
|
28412
|
+
"required": [
|
|
28413
|
+
"image_b64",
|
|
28414
|
+
"content_type"
|
|
28415
|
+
],
|
|
28416
|
+
"additionalProperties": false
|
|
28417
|
+
},
|
|
28418
|
+
"PostTasksIdVisualsSlotResponse": {
|
|
28419
|
+
"type": "object",
|
|
28420
|
+
"properties": {
|
|
28421
|
+
"ok": {
|
|
28422
|
+
"type": "boolean"
|
|
28423
|
+
},
|
|
28424
|
+
"slot": {},
|
|
28425
|
+
"visual_url": {},
|
|
28426
|
+
"visual_alt": {},
|
|
28427
|
+
"bytes": {}
|
|
28428
|
+
},
|
|
28429
|
+
"required": [
|
|
28430
|
+
"ok",
|
|
28431
|
+
"slot",
|
|
28432
|
+
"visual_url",
|
|
28433
|
+
"visual_alt",
|
|
28434
|
+
"bytes"
|
|
28435
|
+
]
|
|
28436
|
+
},
|
|
28182
28437
|
"PostTasksIdVoteRequest": {
|
|
28183
28438
|
"type": "object",
|
|
28184
28439
|
"properties": {
|
|
@@ -28718,9 +28973,9 @@
|
|
|
28718
28973
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
28719
28974
|
}
|
|
28720
28975
|
},
|
|
28721
|
-
"x-endpoint-count":
|
|
28722
|
-
"x-schema-count":
|
|
28976
|
+
"x-endpoint-count": 457,
|
|
28977
|
+
"x-schema-count": 491,
|
|
28723
28978
|
"x-undocumented-bodies": 10,
|
|
28724
|
-
"x-response-schemas":
|
|
28979
|
+
"x-response-schemas": 336,
|
|
28725
28980
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
28726
28981
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -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.
|
|
5
|
+
> **Generated from the live route files** — the route file is authoritative. 457 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`.
|
|
@@ -743,7 +743,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
743
743
|
|---|---|---|---|---|
|
|
744
744
|
| GET | `/api/bongos/task-visuals/:name` | `public` | — | the WIDEST path this route can take, declared honestly. |
|
|
745
745
|
|
|
746
|
-
## `tasks` (
|
|
746
|
+
## `tasks` (35)
|
|
747
747
|
|
|
748
748
|
| Method | Path | Rank | Body | Description |
|
|
749
749
|
|---|---|---|---|---|
|
|
@@ -772,6 +772,9 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
772
772
|
| POST | `/api/bongos/tasks/:id/ship` | `any-builder` | validated | confirmed → shipped. |
|
|
773
773
|
| 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
774
|
| DELETE | `/api/bongos/tasks/:id/visual` | `any-builder` | — | DELETE /tasks/:id/visual — remove it again. |
|
|
775
|
+
| GET | `/api/bongos/tasks/:id/visuals` | `any-builder` | — | GET /tasks/:id/visuals — the task's named visuals. |
|
|
776
|
+
| 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. |
|
|
777
|
+
| DELETE | `/api/bongos/tasks/:id/visuals/:slot` | `any-builder` | — | DELETE /tasks/:id/visuals/:slot — remove one named visual. |
|
|
775
778
|
| 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
779
|
| POST | `/api/bongos/tasks/:id/water` | `any-builder` | — | POST /tasks/:id/water — reset a rotting task's clock. |
|
|
777
780
|
| GET | `/api/bongos/tasks/claimable` | `any-builder` | — | NOTE: /tasks/claimable must come BEFORE /tasks/:id or Express will route 'claimable' to the :id param. |
|
package/docs/file-map.md
CHANGED
|
@@ -433,6 +433,7 @@ tests/
|
|
|
433
433
|
├── ship-check/SKILL.md ← /ship-check — run every freshness + fitness check ship.js will run (repo-map/session-index/file-map/api-docs/copy-registry --check, linkify, skill-lint, fitness; --tests adds the DB-free lane; an ADVISORY render-fit row renders the changed pages via scripts/gds/render-check.js, ADR 0329) in ONE command before shipping, with the healing command per stale artifact (task 1003548; script scripts/gds/ship-check.js)
|
|
434
434
|
├── status/SKILL.md ← /status — what's the status of X / what's left for criterion Cn (read-only rollup)
|
|
435
435
|
├── strand-fix/SKILL.md ← /strand-fix N — walk ONE stranded confirmed task (strand:no_branch_no_tip / land_not_proven — the shape the reconciler gave up on) to shipped: find the work (main commit / unpushed branch / conflicting PR / nowhere), do the one sanctioned move, verify the flip; also clears a 409 REBASE_REQUIRED claim gate (task 1003365)
|
|
436
|
+
├── tweak/SKILL.md ← /tweak — apply the next SUBMITTED page tweak (Tweak Mode, ADR 0341): claim the round, copy-apply.js --batch every line (refusals named, never skipped), tweak-renders.js before/after desktop+phone light+dark into named task-visual slots, ship so the passed grade waits at completed for the artist (task 1004322)
|
|
436
437
|
└── worktree-clean/SKILL.md ← /worktree-clean — list the per-claim worktrees whose branch already landed on main (scripts/gds/stale-worktrees.js) and remove each through the junction-safe worktree.js remove; never raw git worktree remove (task 1003365)
|
|
437
438
|
```
|
|
438
439
|
<!-- END GENERATED FILE-MAP .claude/skills/ -->
|
|
@@ -2613,5 +2613,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2613
2613
|
landed since 1.19.1061 with no explicit bump. run 36428277106. (task 1002620)
|
|
2614
2614
|
1.19.1063 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2615
2615
|
landed since 1.19.1062 with no explicit bump. run 36435823058. (task 1002620)
|
|
2616
|
+
1.19.1064 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2617
|
+
landed since 1.19.1063 with no explicit bump. run 36441707814. (task 1002620)
|
|
2616
2618
|
---------------------------------------------------------------------------
|
|
2617
2619
|
```
|
|
@@ -15,6 +15,7 @@ const { pool, withTx } = api;
|
|
|
15
15
|
const seams = api;
|
|
16
16
|
const { logGradeAttempt } = require('./db-analytics.js');
|
|
17
17
|
const { mostRecentClaimHolderId } = require('./db-shared.js');
|
|
18
|
+
const tweakHold = require('./page-tweak-hold.js');
|
|
18
19
|
|
|
19
20
|
// applyGrade — V3.R18 (#209). Writes the subagent grader result to task_grades
|
|
20
21
|
// and, if the grade passed, promotes completed→confirmed + awards credits.
|
|
@@ -132,7 +133,18 @@ async function applyGrade({ taskId, builderId, gradeResult, committedFiles = nul
|
|
|
132
133
|
// state-machine SQL (FOR UPDATE, task_grades upsert, UPDATE tasks status,
|
|
133
134
|
// claim-holder lookup, logGradeAttempt) stays here in the lifecycle.
|
|
134
135
|
const reward = seams.resolveOptional('reward');
|
|
136
|
+
|
|
137
|
+
// THE HOLD (ADR 0341 D7; task 1004322 / BV2.TW11). A page tweak whose grade
|
|
138
|
+
// passes WAITS at completed for its artist: the grade row above is written,
|
|
139
|
+
// and the promotion below is withheld. TW12's approve performs it. Asked only
|
|
140
|
+
// on a pass at completed, and false for every task whose source is not
|
|
141
|
+
// exactly 'page-tweak', so every other grade takes the unchanged path below.
|
|
142
|
+
let held = false;
|
|
135
143
|
if (gradeResult.passed && task.status === 'completed') {
|
|
144
|
+
const { rows: srcRows } = await client.query(`SELECT source FROM tasks WHERE id = $1`, [taskId]);
|
|
145
|
+
held = tweakHold.isPageTweak(srcRows[0] || null);
|
|
146
|
+
}
|
|
147
|
+
if (gradeResult.passed && task.status === 'completed' && !held) {
|
|
136
148
|
await client.query(
|
|
137
149
|
`UPDATE tasks
|
|
138
150
|
SET status = 'confirmed',
|
|
@@ -195,6 +207,7 @@ async function applyGrade({ taskId, builderId, gradeResult, committedFiles = nul
|
|
|
195
207
|
// !passed → task_grades row written above; task stays at completed; no
|
|
196
208
|
// credits, no achievements. Builder can manual-confirm via /confirm if the
|
|
197
209
|
// grade was wrong.
|
|
210
|
+
// held → the same row, the same completed; the pass is recorded and waits.
|
|
198
211
|
|
|
199
212
|
return {
|
|
200
213
|
taskId,
|
|
@@ -207,6 +220,9 @@ async function applyGrade({ taskId, builderId, gradeResult, committedFiles = nul
|
|
|
207
220
|
ideaBonus, // V3.R27 (#247): { ideaBonusAwarded, bonusBuilderId, ideaId } | null
|
|
208
221
|
netNegativeBonus,
|
|
209
222
|
advanceToMerge: finalStatus === 'confirmed',
|
|
223
|
+
// task 1004322: true only for a page tweak whose pass is waiting for its
|
|
224
|
+
// artist. ship.js reads it to publish the branch without landing it.
|
|
225
|
+
held,
|
|
210
226
|
justUnlocked,
|
|
211
227
|
grade: {
|
|
212
228
|
score: gradeResult.score,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
-- lifecycle_013_task_visual_slots.sql — a task can carry NAMED visuals, not only
|
|
2
|
+
-- the one (task 1004322 / BV2.TW11, goal 1000095; ADR 0341 D7 and its builder
|
|
3
|
+
-- pick 9).
|
|
4
|
+
--
|
|
5
|
+
-- WHY. core_223 gave a task ONE optional image (tasks.visual_url + visual_alt),
|
|
6
|
+
-- the picture beside its value summary. A page tweak needs eight: the applied
|
|
7
|
+
-- page before and after, on desktop and on a phone, in light and in dark. The
|
|
8
|
+
-- Approval queue (TW12) shows the artist exactly those renders, images of the
|
|
9
|
+
-- applied branch, never a page built from the batch, which is what keeps ADR
|
|
10
|
+
-- 0233's "nothing renders a task description as product copy" true. One column
|
|
11
|
+
-- cannot hold eight pictures, and eight columns would be a schema for one
|
|
12
|
+
-- feature, so a visual gets a NAME and a row.
|
|
13
|
+
--
|
|
14
|
+
-- THE SINGLE VISUAL IS UNTOUCHED. tasks.visual_url stays the ship-time picture
|
|
15
|
+
-- every existing reader renders (the hall, the status feed); a slot is a
|
|
16
|
+
-- separate, named picture a reader asks for by name. Nothing here moves,
|
|
17
|
+
-- copies or backfills the existing column.
|
|
18
|
+
--
|
|
19
|
+
-- A MODULE TABLE, because ADR 0083 lets a module migration create only its own
|
|
20
|
+
-- lifecycle_-namespaced tables; it references the core tasks table the way
|
|
21
|
+
-- lifecycle_010 does, and never alters it.
|
|
22
|
+
--
|
|
23
|
+
-- THE SAME AUDIENCE AS THE ONE VISUAL. A slot's file is stored and served by the
|
|
24
|
+
-- existing task-visuals store (task-<id>-<hex>.<ext>, GET /task-visuals/:name),
|
|
25
|
+
-- whose serve route derives who may see an image from the TASK it names. So a
|
|
26
|
+
-- slot adds no privacy axis, exactly as core_223 promised of the column.
|
|
27
|
+
--
|
|
28
|
+
-- PORTABLE and replay-safe: IF NOT EXISTS throughout, no instance data.
|
|
29
|
+
|
|
30
|
+
BEGIN;
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS lifecycle_task_visual_slots (
|
|
33
|
+
task_id bigint NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
34
|
+
|
|
35
|
+
-- The slot's name: lowercase words joined by dashes, such as
|
|
36
|
+
-- 'after-phone-dark'. The vocabulary a page tweak uses is declared once in
|
|
37
|
+
-- modules/lifecycle/task-visuals.js (TWEAK_RENDER_SLOTS); the column checks only
|
|
38
|
+
-- the SHAPE, so a later feature can name its own slots without a migration.
|
|
39
|
+
slot text NOT NULL CHECK (slot ~ '^[a-z0-9]+(-[a-z0-9]+){0,5}$' AND length(slot) <= 48),
|
|
40
|
+
|
|
41
|
+
-- The served URL (/api/bongos/task-visuals/<file>), minted by the store.
|
|
42
|
+
visual_url text NOT NULL,
|
|
43
|
+
visual_alt text,
|
|
44
|
+
|
|
45
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
46
|
+
|
|
47
|
+
-- One picture per (task, slot): attaching a slot again REPLACES it, and the
|
|
48
|
+
-- route unlinks the superseded file.
|
|
49
|
+
PRIMARY KEY (task_id, slot)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
COMMIT;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// modules/lifecycle/page-tweak-hold.js — the ONE definition of "a page tweak
|
|
2
|
+
// waiting for its artist" (task 1004322 / BV2.TW11, ADR 0341 D7).
|
|
3
|
+
//
|
|
4
|
+
// THE HOLD. A page tweak transcribes words an artist wrote, so its artist must
|
|
5
|
+
// see the applied page before it ships (spec decision 4). When a page tweak's
|
|
6
|
+
// grade PASSES, applyGrade records the grade but does not promote `completed`
|
|
7
|
+
// to `confirmed`: the task waits at `completed` until the artist approves it
|
|
8
|
+
// (TW12's approve route does the promotion the grade withheld) or sends it back.
|
|
9
|
+
//
|
|
10
|
+
// WHY ONE FILE. Five readers must agree on this one fact, and a second copy of
|
|
11
|
+
// it is how they would drift: applyGrade (do not promote), the publish-branch
|
|
12
|
+
// route (publish, but never arm auto-merge), the publish-status route (never
|
|
13
|
+
// nudge the merge), ship.js (publish the branch and say the page is waiting)
|
|
14
|
+
// and strand-watch / grade-recover (a held pass is not a failed grade). Every
|
|
15
|
+
// one of them asks the questions below.
|
|
16
|
+
//
|
|
17
|
+
// PURE: no require, no fs, no DB. The scripts under scripts/gds/ require it
|
|
18
|
+
// directly, so it must load without the module doorway.
|
|
19
|
+
//
|
|
20
|
+
// Every other task is untouched by construction: each predicate is false unless
|
|
21
|
+
// the task's source is exactly 'page-tweak'.
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
// The task source the page claim writes (modules/copy-desk/pages.js SOURCE).
|
|
26
|
+
// Restated, not required: copy-desk is another module, and this file requires
|
|
27
|
+
// nothing so that the CLI can load it.
|
|
28
|
+
const PAGE_TWEAK_SOURCE = 'page-tweak';
|
|
29
|
+
|
|
30
|
+
// The derived round state this hold produces (ADR 0341 D7's table).
|
|
31
|
+
const HOLD_STATE = 'waiting_for_artist';
|
|
32
|
+
|
|
33
|
+
function isPageTweak(task) {
|
|
34
|
+
return !!task && task.source === PAGE_TWEAK_SOURCE;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Is this task held for its artist? `gradePassed` is the task's grade row's
|
|
38
|
+
// `passed` (task_grades keeps one row per task, the latest). Only a strict
|
|
39
|
+
// `true` holds: a missing grade, or a failed one, is the ordinary `completed`
|
|
40
|
+
// that /grade-recover handles.
|
|
41
|
+
function isHeldForArtist(task, gradePassed) {
|
|
42
|
+
return isPageTweak(task) && task.status === 'completed' && gradePassed === true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// What ship.js prints when it parks a held pass. One place, so the ship, the
|
|
46
|
+
// re-grade and the resume all say the same thing.
|
|
47
|
+
function heldLines(taskId, { prUrl = null } = {}) {
|
|
48
|
+
return [
|
|
49
|
+
'',
|
|
50
|
+
`Task #${taskId} passed its grade and is WAITING FOR THE ARTIST (ADR 0341 D7).`,
|
|
51
|
+
' This is a page tweak: the artist approves the applied page before it ships.',
|
|
52
|
+
prUrl
|
|
53
|
+
? ` The branch and its PR are published (${prUrl}); auto-merge is NOT armed.`
|
|
54
|
+
: ' The branch is published; auto-merge is NOT armed.',
|
|
55
|
+
' The task stays at completed. Approve promotes it to land; send-back returns it to the /tweak queue.',
|
|
56
|
+
' Nothing more to do here.',
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The auto-merge result the publish route reports for a held pass, in the shape
|
|
61
|
+
// enableAutoMergeDetailed returns, so the route's response carries its reason
|
|
62
|
+
// on the field it already has (auto_merge_reason).
|
|
63
|
+
const HELD_NOT_ARMED = Object.freeze({
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: 'held for the artist (ADR 0341 D7): a page tweak lands only after its artist approves it',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
HELD_NOT_ARMED,
|
|
70
|
+
HOLD_STATE,
|
|
71
|
+
isPageTweak,
|
|
72
|
+
isHeldForArtist,
|
|
73
|
+
heldLines,
|
|
74
|
+
};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
const api = require('../../src/module-api');
|
|
15
|
+
const hold = require('./page-tweak-hold');
|
|
15
16
|
|
|
16
17
|
// Every page-tweak task, oldest first, with what the round state (D7) and the
|
|
17
18
|
// changelog (D8) need beside the task itself:
|
|
@@ -93,4 +94,35 @@ async function shippedTasksTouchingSince({ files, since, limit = 50 } = {}) {
|
|
|
93
94
|
return rows;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
// Is this task a page tweak held for its artist (ADR 0341 D7; task 1004322)?
|
|
98
|
+
// The publish routes ask it before they arm or nudge a merge: a held pass is
|
|
99
|
+
// published so the Approval queue can show the applied branch, but it must not
|
|
100
|
+
// land until the artist approves. One row: the task's source and status, and its
|
|
101
|
+
// grade (task_grades keeps one row per task). The verdict is the shared pure
|
|
102
|
+
// predicate, so this read and applyGrade cannot disagree about what "held" means.
|
|
103
|
+
async function readTweakHold(taskId) {
|
|
104
|
+
const { rows } = await api.pool.query(
|
|
105
|
+
`SELECT t.source, t.status, g.passed AS grade_passed
|
|
106
|
+
FROM tasks t
|
|
107
|
+
LEFT JOIN task_grades g ON g.task_id = t.id
|
|
108
|
+
WHERE t.id = $1`,
|
|
109
|
+
[taskId]
|
|
110
|
+
);
|
|
111
|
+
const row = rows[0] || null;
|
|
112
|
+
return { held: !!row && hold.isHeldForArtist(row, row.grade_passed), page_tweak: hold.isPageTweak(row) };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The publish routes' form of the question, FAIL-CLOSED: a read that throws
|
|
116
|
+
// answers "held", so the route neither arms auto-merge nor nudges the merge.
|
|
117
|
+
// That is the safe direction for every task: an ordinary confirmed task whose
|
|
118
|
+
// PR was left un-armed still lands, because the publish reconciler's sweep
|
|
119
|
+
// merges green PRs of confirmed tasks on its own (task 1162).
|
|
120
|
+
async function heldForArtistFailClosed(taskId, read = readTweakHold) {
|
|
121
|
+
try {
|
|
122
|
+
return (await read(taskId)).held;
|
|
123
|
+
} catch (_) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { listPageTweakTasks, shippedTasksTouchingSince, heldForArtistFailClosed };
|
|
@@ -35,6 +35,7 @@ const { depRefusalRow } = require('../dead-deps.js');
|
|
|
35
35
|
const { gradeQualityWhere } = require('../db-shared');
|
|
36
36
|
const auth = api;
|
|
37
37
|
const db = require('../db');
|
|
38
|
+
const pageTweakReads = require('../page-tweak-reads'); const { HELD_NOT_ARMED } = require('../page-tweak-hold'); // ADR 0341 D7 (tests stub the read)
|
|
38
39
|
const hierarchy = require('../hierarchy');
|
|
39
40
|
const { suggestCriteria } = require('../criterion-suggest');
|
|
40
41
|
// The orphan-task advisory (task 1002539) — pure, fail-open; see goal-advisory.js.
|
|
@@ -1184,7 +1185,8 @@ module.exports = function buildTasksRouter() {
|
|
|
1184
1185
|
// task 1001431: ask WHY when arming fails. `auto_merge: false` on its own told the
|
|
1185
1186
|
// builder nothing, and the commonest cause is a repo with allow_auto_merge OFF —
|
|
1186
1187
|
// a permanent condition that reads exactly like a transient blip.
|
|
1187
|
-
|
|
1188
|
+
// task 1004322 (ADR 0341 D7): a page tweak held for its artist is PUBLISHED, never armed.
|
|
1189
|
+
const autoMergeResult = (await pageTweakReads.heldForArtistFailClosed(id)) ? HELD_NOT_ARMED : await githubPush.enableAutoMergeDetailed({ nodeId: pr.node_id });
|
|
1188
1190
|
const autoMerge = autoMergeResult.ok;
|
|
1189
1191
|
// task 1139 (ADR 0058): attest the author's trust tier on the pushed sha so the
|
|
1190
1192
|
// gate-review check can auto-approve a Metic+ author's gate-surface PR with no
|
|
@@ -1326,7 +1328,7 @@ module.exports = function buildTasksRouter() {
|
|
|
1326
1328
|
// mirrors the reconcile-on-read precedent above (a GET that finishes the
|
|
1327
1329
|
// land); best-effort so a merge-driver miss never turns the poll into a 502.
|
|
1328
1330
|
let merge_driver = null;
|
|
1329
|
-
if (!reconciled && !status.merged && status.pr_state === 'open') {
|
|
1331
|
+
if (!reconciled && !status.merged && status.pr_state === 'open' && !(await pageTweakReads.heldForArtistFailClosed(id))) { // a held page tweak is never nudged (ADR 0341 D7)
|
|
1330
1332
|
try {
|
|
1331
1333
|
merge_driver = await githubPush.mergeIfGreen({ branch });
|
|
1332
1334
|
} catch (e) {
|