@bongos/core 1.19.975 → 1.19.977
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 +63 -23
- package/.claude/skills/goal-create/SKILL.md +8 -0
- package/.claude/skills/goal-review/SKILL.md +8 -0
- package/docs/adr/0176-private-repo-deploy-keys.md +4 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/lifecycle/db-tasks.js +43 -0
- package/modules/lifecycle/dependency-advisory.js +99 -0
- package/modules/lifecycle/missing-dep-classify.js +95 -0
- package/modules/lifecycle/routes/task-write-routes.js +10 -1
- package/modules/provisioning/migrations/provisioning_025_repo_private.sql +34 -0
- package/modules/provisioning/provisioning.js +3 -3
- package/modules/provisioning/repo-private.js +29 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/audit-deps.js +13 -61
- package/scripts/gds/provision-repo-private.js +86 -0
- package/scripts/gds/provision.js +1 -1
- package/src/bongos/github-repos.js +22 -9
- package/src/module-api.js +1 -1
- package/tests/dependency_advisory.mjs +231 -0
- package/tests/github_repos_helpers.mjs +55 -4
- package/tests/missing_dep_classify.mjs +108 -0
- package/tests/provision_repo_private.mjs +260 -0
- package/tests/provisioning_settings_apply.mjs +1 -1
- package/tests/provisioning_teardown_intent.mjs +2 -2
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.977",
|
|
6
|
+
"core_contract": "1.19.977",
|
|
7
|
+
"source_commit": "97ba854401629212275dafc294e6632004516781",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-23T15:21:57.063Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 516,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2386,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2928,
|
|
20
|
+
"tree_sha256": "bc6990ad901c42ffd73df27e947932152a51800591640250f55acda99925c20f",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -142,12 +142,12 @@
|
|
|
142
142
|
{
|
|
143
143
|
"path": ".claude/skills/goal-create/SKILL.md",
|
|
144
144
|
"mode": "0000644",
|
|
145
|
-
"sha256": "
|
|
145
|
+
"sha256": "fef368de4b2a0b42aad43f505b053d7b84aac34bf8f1da7525053970e8e27f42"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"path": ".claude/skills/goal-review/SKILL.md",
|
|
149
149
|
"mode": "0000644",
|
|
150
|
-
"sha256": "
|
|
150
|
+
"sha256": "39016b0c0bf7c42e0b0ee1dc9bff23b90401a1ad29c34f7a475406660855be79"
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"path": ".claude/skills/grade-audit/SKILL.md",
|
|
@@ -1362,7 +1362,7 @@
|
|
|
1362
1362
|
{
|
|
1363
1363
|
"path": "docs/adr/0176-private-repo-deploy-keys.md",
|
|
1364
1364
|
"mode": "0000644",
|
|
1365
|
-
"sha256": "
|
|
1365
|
+
"sha256": "c10a9b3039835676e84c1cedac77b3897fc03f5db689ecc34b8b658c51210d5f"
|
|
1366
1366
|
},
|
|
1367
1367
|
{
|
|
1368
1368
|
"path": "docs/adr/0177-goal-membership-kind.md",
|
|
@@ -2702,7 +2702,7 @@
|
|
|
2702
2702
|
{
|
|
2703
2703
|
"path": "docs/module-api-changelog.md",
|
|
2704
2704
|
"mode": "0000644",
|
|
2705
|
-
"sha256": "
|
|
2705
|
+
"sha256": "5df4d92085d7624d9c5acbf8f99af5a02d10731359071102d4cbe8b8eb73820c"
|
|
2706
2706
|
},
|
|
2707
2707
|
{
|
|
2708
2708
|
"path": "docs/modules-contract.md",
|
|
@@ -5797,7 +5797,7 @@
|
|
|
5797
5797
|
{
|
|
5798
5798
|
"path": "modules/lifecycle/db-tasks.js",
|
|
5799
5799
|
"mode": "0000644",
|
|
5800
|
-
"sha256": "
|
|
5800
|
+
"sha256": "d0844d8f53c003e6026918ad2c132262a358b8c64951aa7927eace1e37fcd41f"
|
|
5801
5801
|
},
|
|
5802
5802
|
{
|
|
5803
5803
|
"path": "modules/lifecycle/db-versions.js",
|
|
@@ -5814,6 +5814,11 @@
|
|
|
5814
5814
|
"mode": "0000644",
|
|
5815
5815
|
"sha256": "4738131a48e85dd7bf0644ddd3686c85d0d1d0da55ee623b1834d048c614b91c"
|
|
5816
5816
|
},
|
|
5817
|
+
{
|
|
5818
|
+
"path": "modules/lifecycle/dependency-advisory.js",
|
|
5819
|
+
"mode": "0000644",
|
|
5820
|
+
"sha256": "b2d93f29010a27f50d5da5712e485b7fac0ef6582f3e531052699b321c693d5e"
|
|
5821
|
+
},
|
|
5817
5822
|
{
|
|
5818
5823
|
"path": "modules/lifecycle/done-when.js",
|
|
5819
5824
|
"mode": "0000644",
|
|
@@ -5964,6 +5969,11 @@
|
|
|
5964
5969
|
"mode": "0000644",
|
|
5965
5970
|
"sha256": "85f7c058dcd6bc3f3d7e4d46ee1c58047347c9bff833148ec715f2a29fe7d7a7"
|
|
5966
5971
|
},
|
|
5972
|
+
{
|
|
5973
|
+
"path": "modules/lifecycle/missing-dep-classify.js",
|
|
5974
|
+
"mode": "0000644",
|
|
5975
|
+
"sha256": "d89f4659debd15dfac18258ba1e900f58c757e02937444d42391acec727afef0"
|
|
5976
|
+
},
|
|
5967
5977
|
{
|
|
5968
5978
|
"path": "modules/lifecycle/module.json",
|
|
5969
5979
|
"mode": "0000644",
|
|
@@ -6107,7 +6117,7 @@
|
|
|
6107
6117
|
{
|
|
6108
6118
|
"path": "modules/lifecycle/routes/task-write-routes.js",
|
|
6109
6119
|
"mode": "0000644",
|
|
6110
|
-
"sha256": "
|
|
6120
|
+
"sha256": "7f9419719070ec7048a2c81149427cbdaf3bf0e4a33993c1c79453161bdc06cc"
|
|
6111
6121
|
},
|
|
6112
6122
|
{
|
|
6113
6123
|
"path": "modules/lifecycle/routes/tasks.js",
|
|
@@ -6719,6 +6729,11 @@
|
|
|
6719
6729
|
"mode": "0000644",
|
|
6720
6730
|
"sha256": "1287440b0088a6451dd4df234e578e06fc6f3e892915e64cd6c6e1f09dadd9d5"
|
|
6721
6731
|
},
|
|
6732
|
+
{
|
|
6733
|
+
"path": "modules/provisioning/migrations/provisioning_025_repo_private.sql",
|
|
6734
|
+
"mode": "0000644",
|
|
6735
|
+
"sha256": "949130ba321fc382c30fd7bfb76572e8415492b8641f644719cef3658a415cb9"
|
|
6736
|
+
},
|
|
6722
6737
|
{
|
|
6723
6738
|
"path": "modules/provisioning/module.json",
|
|
6724
6739
|
"mode": "0000644",
|
|
@@ -6757,7 +6772,7 @@
|
|
|
6757
6772
|
{
|
|
6758
6773
|
"path": "modules/provisioning/provisioning.js",
|
|
6759
6774
|
"mode": "0000644",
|
|
6760
|
-
"sha256": "
|
|
6775
|
+
"sha256": "a6c7b6cdaca50106bd59b86e0272a2e24fe269a43e4a7f6e3a63504e73fa74d5"
|
|
6761
6776
|
},
|
|
6762
6777
|
{
|
|
6763
6778
|
"path": "modules/provisioning/rate-limit.js",
|
|
@@ -6769,6 +6784,11 @@
|
|
|
6769
6784
|
"mode": "0000644",
|
|
6770
6785
|
"sha256": "4c933aad1307378496d47960e24c9d797a1f122f28aefa27b769f336e4cf2e75"
|
|
6771
6786
|
},
|
|
6787
|
+
{
|
|
6788
|
+
"path": "modules/provisioning/repo-private.js",
|
|
6789
|
+
"mode": "0000644",
|
|
6790
|
+
"sha256": "0adee6537b5f127ea3fa5589bc92acc5b6accf6f100f395fe802640ff74b018f"
|
|
6791
|
+
},
|
|
6772
6792
|
{
|
|
6773
6793
|
"path": "modules/provisioning/routes/body-validators.js",
|
|
6774
6794
|
"mode": "0000644",
|
|
@@ -8122,12 +8142,12 @@
|
|
|
8122
8142
|
{
|
|
8123
8143
|
"path": "package-lock.json",
|
|
8124
8144
|
"mode": "0000644",
|
|
8125
|
-
"sha256": "
|
|
8145
|
+
"sha256": "94174c5cd82588020aca94cba69a1e64e311efc037ab4768df616028056268d3"
|
|
8126
8146
|
},
|
|
8127
8147
|
{
|
|
8128
8148
|
"path": "package.json",
|
|
8129
8149
|
"mode": "0000644",
|
|
8130
|
-
"sha256": "
|
|
8150
|
+
"sha256": "97a39697bb451c4ace97d3d753dc66b8e6d77aa0c832fdc91f8d9dd60c10fea9"
|
|
8131
8151
|
},
|
|
8132
8152
|
{
|
|
8133
8153
|
"path": "public-docs/index.html",
|
|
@@ -8262,7 +8282,7 @@
|
|
|
8262
8282
|
{
|
|
8263
8283
|
"path": "scripts/gds/audit-deps.js",
|
|
8264
8284
|
"mode": "0000644",
|
|
8265
|
-
"sha256": "
|
|
8285
|
+
"sha256": "529829a6ba6de16a0166ac3bf4108366c2831d6ac76eac1b448b9fe3b397d775"
|
|
8266
8286
|
},
|
|
8267
8287
|
{
|
|
8268
8288
|
"path": "scripts/gds/audit-exports.js",
|
|
@@ -9104,6 +9124,11 @@
|
|
|
9104
9124
|
"mode": "0000644",
|
|
9105
9125
|
"sha256": "a113cdae4d6dcb3f8d968d41c2f1ce67b34d77229fe0a19d6bc5023f7ed469b5"
|
|
9106
9126
|
},
|
|
9127
|
+
{
|
|
9128
|
+
"path": "scripts/gds/provision-repo-private.js",
|
|
9129
|
+
"mode": "0000644",
|
|
9130
|
+
"sha256": "f2c31eaf1808ee19aaf7266a82509a05ae39d7f638ffaf3aeffcc33e304262aa"
|
|
9131
|
+
},
|
|
9107
9132
|
{
|
|
9108
9133
|
"path": "scripts/gds/provision-repo.js",
|
|
9109
9134
|
"mode": "0000644",
|
|
@@ -9122,7 +9147,7 @@
|
|
|
9122
9147
|
{
|
|
9123
9148
|
"path": "scripts/gds/provision.js",
|
|
9124
9149
|
"mode": "0000644",
|
|
9125
|
-
"sha256": "
|
|
9150
|
+
"sha256": "7747b46f241bddb4257a56461703f82603dd58e18d22bd2c547fd7137749d0e6"
|
|
9126
9151
|
},
|
|
9127
9152
|
{
|
|
9128
9153
|
"path": "scripts/gds/publish-credential-check.js",
|
|
@@ -9892,7 +9917,7 @@
|
|
|
9892
9917
|
{
|
|
9893
9918
|
"path": "src/bongos/github-repos.js",
|
|
9894
9919
|
"mode": "0000644",
|
|
9895
|
-
"sha256": "
|
|
9920
|
+
"sha256": "3e0087226edf1d877e56cf647b2af805430ececabf9b51ecf56df280f959b418"
|
|
9896
9921
|
},
|
|
9897
9922
|
{
|
|
9898
9923
|
"path": "src/bongos/goal-scope-check.js",
|
|
@@ -10147,7 +10172,7 @@
|
|
|
10147
10172
|
{
|
|
10148
10173
|
"path": "src/module-api.js",
|
|
10149
10174
|
"mode": "0000644",
|
|
10150
|
-
"sha256": "
|
|
10175
|
+
"sha256": "a666eed999520506fa357a4b21c5f304f7237bad0b4ca25953505485a8eb14bc"
|
|
10151
10176
|
},
|
|
10152
10177
|
{
|
|
10153
10178
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11194,6 +11219,11 @@
|
|
|
11194
11219
|
"mode": "0000644",
|
|
11195
11220
|
"sha256": "9d16748bfadb1b13969613bd7a43bed42ea3e1bdcc7254ab26fba14ea8b4c036"
|
|
11196
11221
|
},
|
|
11222
|
+
{
|
|
11223
|
+
"path": "tests/dependency_advisory.mjs",
|
|
11224
|
+
"mode": "0000644",
|
|
11225
|
+
"sha256": "9141b1e9544eceeaf94d5156a4c150236db7011f769dafec9a70f5fd5f51bc25"
|
|
11226
|
+
},
|
|
11197
11227
|
{
|
|
11198
11228
|
"path": "tests/deploy_audit_gate.mjs",
|
|
11199
11229
|
"mode": "0000644",
|
|
@@ -11532,7 +11562,7 @@
|
|
|
11532
11562
|
{
|
|
11533
11563
|
"path": "tests/github_repos_helpers.mjs",
|
|
11534
11564
|
"mode": "0000644",
|
|
11535
|
-
"sha256": "
|
|
11565
|
+
"sha256": "66fd3d349445aa00e55fa77589958d6c98980e99f3cdaf4746ff55494202333a"
|
|
11536
11566
|
},
|
|
11537
11567
|
{
|
|
11538
11568
|
"path": "tests/github_token_store.mjs",
|
|
@@ -12759,6 +12789,11 @@
|
|
|
12759
12789
|
"mode": "0000644",
|
|
12760
12790
|
"sha256": "1da3144c843ebcacc914f8ccc6e4e0578b2efc40bd3d2feb99d3665989923512"
|
|
12761
12791
|
},
|
|
12792
|
+
{
|
|
12793
|
+
"path": "tests/missing_dep_classify.mjs",
|
|
12794
|
+
"mode": "0000644",
|
|
12795
|
+
"sha256": "0bd80e5c2a08bf90509563a878cceea2485d7755022f7818f036c37fe60a32e6"
|
|
12796
|
+
},
|
|
12762
12797
|
{
|
|
12763
12798
|
"path": "tests/model_alloc_prefs.mjs",
|
|
12764
12799
|
"mode": "0000644",
|
|
@@ -13274,6 +13309,11 @@
|
|
|
13274
13309
|
"mode": "0000644",
|
|
13275
13310
|
"sha256": "755e93592b581593c463af59bad68b7e5b753c8305b80ff759501d878a31b209"
|
|
13276
13311
|
},
|
|
13312
|
+
{
|
|
13313
|
+
"path": "tests/provision_repo_private.mjs",
|
|
13314
|
+
"mode": "0000644",
|
|
13315
|
+
"sha256": "9140b670c8ab76069c76238cf47bfdd539141198a40ef23f443813543947c4aa"
|
|
13316
|
+
},
|
|
13277
13317
|
{
|
|
13278
13318
|
"path": "tests/provision_restart.mjs",
|
|
13279
13319
|
"mode": "0000644",
|
|
@@ -13382,7 +13422,7 @@
|
|
|
13382
13422
|
{
|
|
13383
13423
|
"path": "tests/provisioning_settings_apply.mjs",
|
|
13384
13424
|
"mode": "0000644",
|
|
13385
|
-
"sha256": "
|
|
13425
|
+
"sha256": "ab01149d73158276a73464bc0feca3474ee2f054db03af378c9b43874a2056fb"
|
|
13386
13426
|
},
|
|
13387
13427
|
{
|
|
13388
13428
|
"path": "tests/provisioning_settings_env.mjs",
|
|
@@ -13402,7 +13442,7 @@
|
|
|
13402
13442
|
{
|
|
13403
13443
|
"path": "tests/provisioning_teardown_intent.mjs",
|
|
13404
13444
|
"mode": "0000644",
|
|
13405
|
-
"sha256": "
|
|
13445
|
+
"sha256": "b23049faa369d75b867a9c31237fe073424e694588627217a1105a5522c16fc6"
|
|
13406
13446
|
},
|
|
13407
13447
|
{
|
|
13408
13448
|
"path": "tests/provisioning_teardown_states.mjs",
|
|
@@ -153,6 +153,14 @@ Run the phases above **with the orphan task as the seed** — its title, descrip
|
|
|
153
153
|
|
|
154
154
|
**A goal-less task is not an error and was never blocked.** `goal_advisory` is advice on a request that already succeeded. Never fail a build, refuse work, or badger the builder over it — if they say "later", file the idea and move on.
|
|
155
155
|
|
|
156
|
+
## The sibling advisory: `dependency_advisory` (task 1002827)
|
|
157
|
+
|
|
158
|
+
The same `POST /tasks` response can also carry a non-null **`dependency_advisory`** — a *different* check, unrelated to which goal the task landed in: it names OPEN tasks in the same version whose `touches[]` overlap this one's, with no declared dependency between them (`scripts/gds/audit-deps.js`'s detector, run live for the first time).
|
|
159
|
+
|
|
160
|
+
- `dependency_advisory.candidates` is sorted by confidence (`high`/`medium`/`low`) — read each candidate's title before acting; most overlap is parallel work on a shared file, not a real build-order dependency.
|
|
161
|
+
- Only backfill a **high**-confidence candidate, and only after reading both task descriptions. Add the edge with `POST /tasks/<id>/dependencies --body '{"depends_on_task_id":<dep_id>}'`.
|
|
162
|
+
- **Same non-blocking contract as `goal_advisory`** — it is advice on a request that already succeeded, never a reason to redo the create, and a wrong edge GATES the dependent (`POST /claims` → 409 `DEPS_NOT_SHIPPED`) until the named blocker ships, so silence (skip it) beats a guessed edge.
|
|
163
|
+
|
|
156
164
|
## Why we run this
|
|
157
165
|
|
|
158
166
|
Nothing in the system creates a goal on its own, and nothing errors when a task has none. `db.createTask` falls back to the version's `"<version> — general"` catch-all, and those rows were created **only** by migration 160, for the versions that existed at that moment — `db.createVersion` makes no such goal and there is no trigger. So on any newer version, every task quietly gets `goal_id = NULL`: no scope wall, no criteria gating it, no workspace where anyone reviews it. This skill is how a real goal comes to exist before that happens.
|
|
@@ -96,6 +96,14 @@ Report to the user:
|
|
|
96
96
|
- Deferred (count + ids — these surface again next session)
|
|
97
97
|
- New queue `count`
|
|
98
98
|
|
|
99
|
+
**Step 6 — dependency check (task 1002827, idea 1000276).** A cheap housekeeping pass over the same corpus this session already has open: run the likely-missing-dependency detector and surface any `high`-confidence candidate for a human to triage.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
node scripts/gds/audit-deps.js --min high
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Each line names an open task and an unshipped task whose files it overlaps with no declared dependency between them. **Read both task descriptions before backfilling an edge** — most overlap is parallel work on a shared file, not a build order; a wrong edge gates the dependent (`POST /claims` → 409 `DEPS_NOT_SHIPPED`) until the named blocker ships. Report the count in the same summary as Step 5; zero is the common case and needs no further comment. This is advisory only — it never blocks the review, and skipping it on a rushed pass is fine (it resurfaces next session, same as a deferred criterion).
|
|
106
|
+
|
|
99
107
|
## Why we run this
|
|
100
108
|
|
|
101
109
|
Without a closing cadence, criteria pile up "done but unconfirmed" and goals never flip to achieved — the version looks perpetually in-progress even when the work has shipped. A fast pass keeps the rollup honest. As with `/idea-triage`, the cadence is the discipline; a quick walk with mostly defers beats skipping it.
|
|
@@ -51,6 +51,10 @@ best-effort revokes the key by title. Pushes keep preferring the owner-token HTT
|
|
|
51
51
|
- The flip-to-private refusal on the manage page (task 1003045) stays until existing
|
|
52
52
|
already-provisioned instances get the same wiring on flip — a follow-up task, since their
|
|
53
53
|
origin still rides the shared alias.
|
|
54
|
+
**Progress (2026-09-23):** the runner half has landed — task 1004167 let the wiring run
|
|
55
|
+
before a flip (`intendedPrivate`), and task 1004193 added the `repo-private` intent whose
|
|
56
|
+
runner leg wires the key and only then flips. The refusal stays until task 1003078 adds the
|
|
57
|
+
route and the manage-page control that enqueue it.
|
|
54
58
|
- Builder **dev boxes** of a private-repo project were parked here as out of scope.
|
|
55
59
|
**Answered on hardware 2026-08-25 (task 1003247): a box CAN read the private repo, and the
|
|
56
60
|
auth design needed no change.** The GDS-backed credential helper (`otb-git-credential`,
|
|
@@ -2424,5 +2424,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2424
2424
|
landed since 1.19.973 with no explicit bump. run 35816317024. (task 1002620)
|
|
2425
2425
|
1.19.975 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2426
2426
|
landed since 1.19.974 with no explicit bump. run 35871083707. (task 1002620)
|
|
2427
|
+
1.19.976 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2428
|
+
landed since 1.19.975 with no explicit bump. run 35878415178. (task 1002620)
|
|
2429
|
+
1.19.977 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2430
|
+
landed since 1.19.976 with no explicit bump. run 35880930697. (task 1002620)
|
|
2427
2431
|
---------------------------------------------------------------------------
|
|
2428
2432
|
```
|
|
@@ -765,6 +765,48 @@ async function listTasks({ versionId, status, kind, discipline, goalId, createdB
|
|
|
765
765
|
return rows;
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
+
// task 1002827 — the candidate pool for the dependency advisory (POST /tasks'
|
|
769
|
+
// `dependency_advisory` + the periodic sweep): OPEN tasks in this version that
|
|
770
|
+
// carry touches[], the same OPEN set scripts/gds/audit-deps.js already treats
|
|
771
|
+
// as "auditable" (ready, backlog, active, blocked). Bounded and cheap —
|
|
772
|
+
// measured 2026-09-22, BONGOS-V2 carries ~51 such rows, since most tasks
|
|
773
|
+
// declare no touches[] at all (ADR 0049 demoted the field to advisory, so
|
|
774
|
+
// most authors skip it). `dependencies` is not selected: it lives in a join
|
|
775
|
+
// table, not this one (see addTaskDependency), and this advisory only needs
|
|
776
|
+
// the NEW task's own declared deps, supplied by its caller.
|
|
777
|
+
//
|
|
778
|
+
// VISIBILITY (ADR 0112's leak rule — the goal-graph.js precedent). A task's
|
|
779
|
+
// title and touched paths are exactly the "prose" that rule protects: a task
|
|
780
|
+
// under a PRIVATE goal the caller neither manages nor is a member of is
|
|
781
|
+
// EXCLUDED here, not merely redacted — unlike the graph, this advisory has
|
|
782
|
+
// no structural need to keep an invisible node in the picture (omitting a
|
|
783
|
+
// candidate says nothing false; it just offers no advice about it). A
|
|
784
|
+
// goal-less task carries no visibility concern (`t.goal_id IS NULL`).
|
|
785
|
+
// `LIMIT` bounds the candidate count so a growing corpus cannot turn
|
|
786
|
+
// classify()'s O(|a|×|b|) pairwise scan (dependency-advisory.js) into an
|
|
787
|
+
// unbounded synchronous cost on this write path.
|
|
788
|
+
const DEPENDENCY_ADVISORY_CANDIDATE_LIMIT = 200;
|
|
789
|
+
async function listOpenTasksWithTouches(versionId, excludeId, { viewerId = null, viewerIsArchon = false } = {}) {
|
|
790
|
+
const { rows } = await pool.query(
|
|
791
|
+
`SELECT t.id, t.title, t.status, t.touches
|
|
792
|
+
FROM tasks t
|
|
793
|
+
LEFT JOIN goals g ON g.id = t.goal_id
|
|
794
|
+
LEFT JOIN goal_members gm ON gm.goal_id = g.id AND gm.builder_id = $4::bigint
|
|
795
|
+
WHERE t.version_id = $1
|
|
796
|
+
AND t.status = ANY($2::text[])
|
|
797
|
+
AND t.touches <> '{}'
|
|
798
|
+
AND ($3::bigint IS NULL OR t.id <> $3)
|
|
799
|
+
AND (t.goal_id IS NULL
|
|
800
|
+
OR g.visibility IS DISTINCT FROM 'private'
|
|
801
|
+
OR $5::boolean
|
|
802
|
+
OR gm.builder_id IS NOT NULL)
|
|
803
|
+
ORDER BY t.id
|
|
804
|
+
LIMIT ${DEPENDENCY_ADVISORY_CANDIDATE_LIMIT}`,
|
|
805
|
+
[versionId, ['ready', 'backlog', 'active', 'blocked'], excludeId, viewerId, viewerIsArchon]
|
|
806
|
+
);
|
|
807
|
+
return rows;
|
|
808
|
+
}
|
|
809
|
+
|
|
768
810
|
// Tasks that are ready, unblocked, and have no touches[] overlap
|
|
769
811
|
// with currently active claims. Excludes tasks already claimed by this
|
|
770
812
|
// builder (so the same builder doesn't see their own active claim listed
|
|
@@ -1269,6 +1311,7 @@ module.exports = {
|
|
|
1269
1311
|
listActiveClaims,
|
|
1270
1312
|
listClaimableTasks,
|
|
1271
1313
|
listOpenArtistReviews,
|
|
1314
|
+
listOpenTasksWithTouches,
|
|
1272
1315
|
listTasks,
|
|
1273
1316
|
promoteUnblockedBacklogTasks,
|
|
1274
1317
|
rankCrossDisciplineRecommendations,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/lifecycle/dependency-advisory.js — the MISSING-DEPENDENCY advisory
|
|
4
|
+
// (task 1002827, goal 1000089).
|
|
5
|
+
//
|
|
6
|
+
// THE HOLE. scripts/gds/audit-deps.js has shipped a working
|
|
7
|
+
// likely-missing-dependency detector since task 1000349 (unit-tested,
|
|
8
|
+
// tests/audit_deps.mjs, tests/missing_dep_classify.mjs) — and nothing ever
|
|
9
|
+
// called it. A task can be created touching the exact same files an
|
|
10
|
+
// unshipped task already claims, with no declared dependency between them,
|
|
11
|
+
// and nothing says so until a human happens to run the CLI by hand.
|
|
12
|
+
//
|
|
13
|
+
// This module wires that SAME detector's predicate —
|
|
14
|
+
// modules/lifecycle/missing-dep-classify.js's `candidateEdge`/`classify`,
|
|
15
|
+
// not a second copy of either — into two surfaces, mirroring
|
|
16
|
+
// goal-advisory.js's own two-surface shape (task 1002539):
|
|
17
|
+
// 1. POST /tasks attaches `dependency_advisory` to its 201 body (this
|
|
18
|
+
// file's `buildDependencyAdvisory`), read by the /goal-create skill's
|
|
19
|
+
// "sibling advisory" section (.claude/skills/goal-create/SKILL.md).
|
|
20
|
+
// 2. The periodic sweep: `/goal-review`'s daily walk runs
|
|
21
|
+
// `node scripts/gds/audit-deps.js --min high` as one more housekeeping
|
|
22
|
+
// step (idea 1000276's half — see .claude/skills/goal-review/SKILL.md).
|
|
23
|
+
//
|
|
24
|
+
// HARD CONSTRAINT, same as goal-advisory.js and ADR 0162: advisory only. This
|
|
25
|
+
// must never refuse a POST, and `buildDependencyAdvisory` swallows its own
|
|
26
|
+
// failures for the same reason goal-advisory.js's does — the task row is
|
|
27
|
+
// ALREADY COMMITTED by the time this runs, so a throw here would turn a
|
|
28
|
+
// successful create into a 500 the caller then retries.
|
|
29
|
+
//
|
|
30
|
+
// VISIBILITY (ADR 0112's leak rule, the goal-graph.js precedent) — enforced
|
|
31
|
+
// in the query, not here: `db.listOpenTasksWithTouches` excludes a candidate
|
|
32
|
+
// under a PRIVATE goal the caller neither manages nor belongs to, so this
|
|
33
|
+
// file never even sees a task it should not name in a 201 body.
|
|
34
|
+
//
|
|
35
|
+
// PURE core (`adviseDependencies`) + a DB-read wrapper, the criterion-suggest /
|
|
36
|
+
// goal-advisory precedent, so every branch is pinned by a DB-free unit test.
|
|
37
|
+
|
|
38
|
+
const { candidateEdge } = require('./missing-dep-classify.js');
|
|
39
|
+
|
|
40
|
+
// How many candidates the advisory offers. Small on purpose (the
|
|
41
|
+
// goal-advisory.js CANDIDATE_LIMIT precedent) — this is a hint for the author
|
|
42
|
+
// to triage, not a browsable report; the full corpus scan already exists as
|
|
43
|
+
// `node scripts/gds/audit-deps.js` for that. Separate from (and much smaller
|
|
44
|
+
// than) db.listOpenTasksWithTouches's own query-side LIMIT, which bounds cost
|
|
45
|
+
// rather than display.
|
|
46
|
+
const CANDIDATE_LIMIT = 5;
|
|
47
|
+
|
|
48
|
+
// adviseDependencies — PURE. `task` is the just-created (or just-fetched) row,
|
|
49
|
+
// `candidates` is the OPEN-with-touches, visibility-filtered pool a caller
|
|
50
|
+
// already fetched. Returns null when there is nothing worth naming (fail-open
|
|
51
|
+
// on ambiguity, same posture as adviseGoal): no touches[] on the task at all,
|
|
52
|
+
// or no candidate clears the shared classify() confidence bar.
|
|
53
|
+
function adviseDependencies({ task, candidates = [] } = {}) {
|
|
54
|
+
if (!task || !Array.isArray(task.touches) || !task.touches.length) return null;
|
|
55
|
+
const declared = new Set((task.dependencies || []).map(String));
|
|
56
|
+
const edges = [];
|
|
57
|
+
for (const o of candidates) {
|
|
58
|
+
const e = candidateEdge(task, o, { declared });
|
|
59
|
+
if (e) edges.push({ dep_id: e.dep_id, dep_title: (o && o.title) || null, dep_status: e.dep_status, confidence: e.confidence, paths: e.paths });
|
|
60
|
+
}
|
|
61
|
+
if (!edges.length) return null;
|
|
62
|
+
const order = { high: 3, medium: 2, low: 1 };
|
|
63
|
+
edges.sort((a, b) => order[b.confidence] - order[a.confidence]);
|
|
64
|
+
return {
|
|
65
|
+
reason: 'possible_missing_dependency',
|
|
66
|
+
message: `${edges.length} open task(s) touch the same files as this one and are not declared `
|
|
67
|
+
+ 'dependencies — most touches[] overlap is parallel work on a shared surface, not a build-order '
|
|
68
|
+
+ 'dependency, so read each candidate before adding an edge (a wrong one gates the claim).',
|
|
69
|
+
candidates: edges.slice(0, CANDIDATE_LIMIT),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// buildDependencyAdvisory — the whole advisory, DB read included, so POST
|
|
74
|
+
// /tasks calls one line (the buildGoalAdvisory precedent). Fails open: any
|
|
75
|
+
// throw degrades to null rather than failing an otherwise-successful create.
|
|
76
|
+
// `viewerId`/`viewerIsArchon` ride straight into the query's own visibility
|
|
77
|
+
// filter — this file never post-filters what the query already excluded.
|
|
78
|
+
async function buildDependencyAdvisory({ db, versionId, task, viewerId = null, viewerIsArchon = false }) {
|
|
79
|
+
try {
|
|
80
|
+
if (!Array.isArray(task.touches) || !task.touches.length) return null;
|
|
81
|
+
const candidates = await db.listOpenTasksWithTouches(versionId, task.id, { viewerId, viewerIsArchon });
|
|
82
|
+
const verdict = adviseDependencies({ task, candidates });
|
|
83
|
+
if (!verdict) return null;
|
|
84
|
+
return {
|
|
85
|
+
...verdict,
|
|
86
|
+
next_step: 'Review each candidate; if it is a real build-order dependency, add it via '
|
|
87
|
+
+ `POST /tasks/${task.id}/dependencies.`,
|
|
88
|
+
};
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// Through the published logger seam (api.logger), not a raw console call
|
|
91
|
+
// — the est-advisory.js precedent. Lazy-required so the DB-free unit test
|
|
92
|
+
// that exercises this branch stays hermetic.
|
|
93
|
+
require('../../src/module-api').logger('lifecycle')
|
|
94
|
+
.error('dependency-advisory (non-blocking)', e && e.message);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { adviseDependencies, buildDependencyAdvisory, CANDIDATE_LIMIT };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/lifecycle/missing-dep-classify.js — the PURE core of the
|
|
4
|
+
// likely-missing-dependency detector (task 349, moved here from
|
|
5
|
+
// scripts/gds/audit-deps.js by task 1002827 so a live route can reuse it
|
|
6
|
+
// without pulling in that script's CLI-only machinery — cliClient(),
|
|
7
|
+
// resolveDefaultVersion() — into the server process. The `dead-deps.js`
|
|
8
|
+
// precedent beside this file: a pure lifecycle module, consumed both by the
|
|
9
|
+
// audit-deps.js CLI (its --stranded arm) and now by dependency-advisory.js's
|
|
10
|
+
// live POST /tasks path.
|
|
11
|
+
//
|
|
12
|
+
// THIS IS ADVISORY. Most touches[] overlaps are PARALLEL work on a shared
|
|
13
|
+
// surface (src/bongos/db.js, migrations/, CLAUDE.md, …), NOT a build-order
|
|
14
|
+
// dependency. Backfilling a wrong edge GATES the dependent (POST /claims →
|
|
15
|
+
// 409 DEPS_NOT_SHIPPED) until the named blocker ships. So: act only on
|
|
16
|
+
// `high` confidence, by hand, after reading both task descriptions. Reuses
|
|
17
|
+
// the ONE canonical overlap matcher (src/bongos/path-match.touchesOverlap,
|
|
18
|
+
// reached via the module-api doorway) so it can't drift from the claim
|
|
19
|
+
// gate's notion of "overlap".
|
|
20
|
+
|
|
21
|
+
const api = require('../../src/module-api');
|
|
22
|
+
const { touchesOverlap } = api.pathMatch;
|
|
23
|
+
|
|
24
|
+
// Lifecycle statuses that count as "open" (auditable) vs "an unshipped blocker".
|
|
25
|
+
const OPEN = new Set(['ready', 'backlog', 'active', 'blocked']);
|
|
26
|
+
const isUnshippedBlocker = (s) => s !== 'shipped' && s !== 'abandoned';
|
|
27
|
+
|
|
28
|
+
// Shared collaboration surfaces: an overlap here is a parallel-safety (touches[])
|
|
29
|
+
// signal, not a dependency. A candidate whose ONLY overlap is one of these (or a
|
|
30
|
+
// bare directory) is demoted to `low`. Deliberately broad; tune as the repo moves.
|
|
31
|
+
const HOTSPOTS = [
|
|
32
|
+
'src/bongos/db.js', 'src/bongos/routes.js', 'server.js', 'migrations/', 'CLAUDE.md',
|
|
33
|
+
'modules/hall-ui/', 'tests/', 'scripts/gds/', 'src/bongos/', 'src/bongos/routes/',
|
|
34
|
+
'scripts/gds/ship.js', 'scripts/gds/api.js', 'scripts/gds/cli-lib.js',
|
|
35
|
+
'modules/grading/grader.js', 'docs/', 'docs/adr/', 'docs/adr/README.md', 'docs/recipes/',
|
|
36
|
+
'docs/architecture.md', 'docs/file-map.md', '.claude/skills/', '.claude/hooks/',
|
|
37
|
+
'infra/', 'public/assets/tiles/',
|
|
38
|
+
];
|
|
39
|
+
const isHotspot = (p) => HOTSPOTS.includes(p);
|
|
40
|
+
|
|
41
|
+
// Confidence heuristic (advisory; a human decides):
|
|
42
|
+
// high — an exact SAME non-hotspot, non-bare-dir FILE both tasks name.
|
|
43
|
+
// medium — a narrow (non-hotspot, non-bare-dir) overlap exists.
|
|
44
|
+
// low — overlap only on a shared hotspot / bare directory.
|
|
45
|
+
function classify(tTouches, oTouches) {
|
|
46
|
+
const pairs = [];
|
|
47
|
+
for (const a of tTouches) for (const b of oTouches) {
|
|
48
|
+
if (touchesOverlap([a], [b])) pairs.push({ a, b, exact: a === b });
|
|
49
|
+
}
|
|
50
|
+
if (!pairs.length) return null;
|
|
51
|
+
const narrow = pairs.filter((p) => !isHotspot(p.a) && !isHotspot(p.b) && !p.a.endsWith('/') && !p.b.endsWith('/'));
|
|
52
|
+
const confidence = narrow.some((p) => p.exact) ? 'high' : narrow.length ? 'medium' : 'low';
|
|
53
|
+
const paths = [...new Set(pairs.map((p) => (p.exact ? p.a : `${p.a} ~ ${p.b}`)))];
|
|
54
|
+
return { confidence, paths };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// candidateEdge — THE ONE predicate for "does `o` qualify as a missing-dependency
|
|
58
|
+
// candidate for `t`", pulled out (task 1002827 grade round 1) so findMissingDeps
|
|
59
|
+
// and dependency-advisory.js's live adviseDependencies call the SAME check
|
|
60
|
+
// instead of two hand-typed copies that can drift on the next rule change.
|
|
61
|
+
// `declared` is precomputed and passed in by findMissingDeps (one Set per `t`,
|
|
62
|
+
// not per pair); a caller checking a single `t` against many candidates may
|
|
63
|
+
// omit it and pay one Set build instead. Runs `classify` exactly ONCE — it
|
|
64
|
+
// already returns null on no overlap, so a separate touchesOverlap pre-check
|
|
65
|
+
// was dead work computing the same answer twice.
|
|
66
|
+
function candidateEdge(t, o, { isBlocker = isUnshippedBlocker, declared } = {}) {
|
|
67
|
+
if (!o || String(o.id) === String(t.id)) return null;
|
|
68
|
+
if (!isBlocker(o.status)) return null;
|
|
69
|
+
if (!Array.isArray(o.touches) || !o.touches.length) return null;
|
|
70
|
+
const declaredSet = declared || new Set(((t && t.dependencies) || []).map(String));
|
|
71
|
+
if (declaredSet.has(String(o.id))) return null;
|
|
72
|
+
if (!Array.isArray(t.touches) || !t.touches.length) return null;
|
|
73
|
+
const c = classify(t.touches, o.touches);
|
|
74
|
+
if (!c) return null;
|
|
75
|
+
return { task_id: t.id, dep_id: o.id, dep_status: o.status, ...c };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Pure core — unit-tested with fixtures (no API). `tasks` is an array of
|
|
79
|
+
// { id, status, touches:[], dependencies:[ids] }. Returns candidate edges
|
|
80
|
+
// { task_id, dep_id, dep_status, confidence, paths }.
|
|
81
|
+
function findMissingDeps(tasks, { open = OPEN, isBlocker = isUnshippedBlocker } = {}) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const t of tasks) {
|
|
84
|
+
if (!open.has(t.status)) continue;
|
|
85
|
+
if (!Array.isArray(t.touches) || !t.touches.length) continue;
|
|
86
|
+
const declared = new Set((t.dependencies || []).map(String));
|
|
87
|
+
for (const o of tasks) {
|
|
88
|
+
const e = candidateEdge(t, o, { isBlocker, declared });
|
|
89
|
+
if (e) out.push(e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = { OPEN, isUnshippedBlocker, HOTSPOTS, isHotspot, classify, candidateEdge, findMissingDeps };
|
|
@@ -22,6 +22,7 @@ const db = require('../db');
|
|
|
22
22
|
const hierarchy = require('../hierarchy');
|
|
23
23
|
const { suggestCriteria } = require('../criterion-suggest');
|
|
24
24
|
const { buildGoalAdvisory } = require('../goal-advisory');
|
|
25
|
+
const { buildDependencyAdvisory } = require('../dependency-advisory');
|
|
25
26
|
const { classifyTaskKind, VALID_KINDS, WRITABLE_DISCIPLINES, resolveWriteDiscipline, VOTABLE_TASK_STATUSES, TASK_FIELD_BOUNDS } = require('../task-classifier');
|
|
26
27
|
const { isModuleKey, moduleKeys } = api.moduleScopeMap;
|
|
27
28
|
const { validateOrRespond, LIMITS, parseId } = api;
|
|
@@ -282,13 +283,21 @@ module.exports = function registerTaskWriteRoutes(router) {
|
|
|
282
283
|
const goal_advisory = await buildGoalAdvisory({
|
|
283
284
|
db, versionId: body.version_id, task, hasCriterionLink: criteria.length > 0,
|
|
284
285
|
});
|
|
286
|
+
// task 1002827 — the missing-dependency advisory (scripts/gds/audit-deps.js's
|
|
287
|
+
// detector, wired live for the first time): OPEN tasks in this version that
|
|
288
|
+
// touch the same files as this one and are not declared dependencies.
|
|
289
|
+
// Non-blocking by construction — see dependency-advisory.js.
|
|
290
|
+
const dependency_advisory = await buildDependencyAdvisory({
|
|
291
|
+
db, versionId: body.version_id, task,
|
|
292
|
+
viewerId: req.builder.id, viewerIsArchon: req.builder.rank === 'archon',
|
|
293
|
+
});
|
|
285
294
|
res.status(201).json({
|
|
286
295
|
task,
|
|
287
296
|
credits_reward: task.credits_reward,
|
|
288
297
|
needs_migration: needsMigration,
|
|
289
298
|
criteria,
|
|
290
299
|
suggested_criteria,
|
|
291
|
-
goal_advisory, est_advisory: await require('../est-advisory').buildEstAdvisory({ db, task }), // task 1003148
|
|
300
|
+
goal_advisory, dependency_advisory, est_advisory: await require('../est-advisory').buildEstAdvisory({ db, task }), // task 1003148
|
|
292
301
|
});
|
|
293
302
|
} catch (err) {
|
|
294
303
|
log.error('[gds] POST /tasks', err);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- provisioning_025_repo_private.sql — take an existing project's repository private
|
|
2
|
+
-- (task 1004193, split (b) of task 1003078; ADR 0176).
|
|
3
|
+
--
|
|
4
|
+
-- WHY THIS EXISTS. A project stood up while its repository was public deploys through the
|
|
5
|
+
-- platform's shared key, which cannot read a private repository. Flipping such a repository
|
|
6
|
+
-- private from GitHub breaks the project's next deploy pull (walk finding N4), so the manage
|
|
7
|
+
-- page has refused the flip since task 1003045. Standups after ADR 0176 give a private
|
|
8
|
+
-- repository its own read-only deploy key; this action gives an EXISTING project the same
|
|
9
|
+
-- wiring and only then flips it.
|
|
10
|
+
--
|
|
11
|
+
-- WHY AN INTENT. The wiring runs ssh-keygen and git config inside the project's checkout on
|
|
12
|
+
-- the control-plane box, with the owner's stored GitHub credential. The web tier holds
|
|
13
|
+
-- neither, so it may only ask; the runner (scripts/gds/provision-repo-private.js) does it —
|
|
14
|
+
-- deploy key FIRST, flip SECOND, so there is never a moment the repository is private and
|
|
15
|
+
-- the checkout still reads with the shared key.
|
|
16
|
+
--
|
|
17
|
+
-- WHY NO PAYLOAD. The action name IS the direction: it only ever means "make it private".
|
|
18
|
+
-- Going public needs no wiring and stays a direct call on the core route. So nothing new
|
|
19
|
+
-- is carried, and the target_version CHECK from provisioning_020 already holds for this
|
|
20
|
+
-- action as written (repo-private is not an upgrade action, and carries no target).
|
|
21
|
+
--
|
|
22
|
+
-- Additive and namespaced (ADR 0083): no down-migration, idempotent — safe to re-run.
|
|
23
|
+
|
|
24
|
+
BEGIN;
|
|
25
|
+
|
|
26
|
+
-- INTENT_ACTIONS in modules/provisioning/provisioning.js is the other spelling of this list;
|
|
27
|
+
-- tests/provisioning_restart.mjs and tests/core_upgrade_door.mjs fail if the two disagree.
|
|
28
|
+
ALTER TABLE provisioning_intents DROP CONSTRAINT IF EXISTS provisioning_intents_action_check;
|
|
29
|
+
ALTER TABLE provisioning_intents ADD CONSTRAINT provisioning_intents_action_check
|
|
30
|
+
CHECK (action IN ('provision', 'teardown', 'oauth-manifest', 'domain-attach',
|
|
31
|
+
'settings-apply', 'restart', 'disconnect', 'core-upgrade',
|
|
32
|
+
'core-upgrade-preflight', 'repo-private'));
|
|
33
|
+
|
|
34
|
+
COMMIT;
|