@bongos/core 1.21.133 → 1.21.135

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 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.21.133",
6
- "core_contract": "1.21.133",
7
- "source_commit": "e3e005eed0218082a8724a9aa638657aced70c56",
5
+ "core_version": "1.21.135",
6
+ "core_contract": "1.21.135",
7
+ "source_commit": "92eebc078b819402dfac89117a1ea3cd6211094e",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-15T20:42:02.660Z",
9
+ "built_at": "2026-10-15T21:47:43.041Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 607,
12
+ "docs_redacted": 608,
13
13
  "agent_docs_stubbed": 28,
14
14
  "functional_verbatim": 3149,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 3785,
20
- "tree_sha256": "cf35d4f10854a481a256f0d2cec544ac8af781a48de1b57c1c1d796da2247311",
19
+ "file_count": 3786,
20
+ "tree_sha256": "eba8be00953d70046ec4a91b870c28a7382c4a8bee2294650001d9aff411caa2",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2369,10 +2369,15 @@
2369
2369
  "mode": "0000644",
2370
2370
  "sha256": "12602b29d3a701e7ad1e6c411f7cbf6de3c3148b9c08624b6197968de8079c5f"
2371
2371
  },
2372
+ {
2373
+ "path": "docs/adr/0382-a-hardware-card-is-a-passkey-step-up-an-archon-switches-on-per-builder.md",
2374
+ "mode": "0000644",
2375
+ "sha256": "ae20b78283a004ba64d66cd0eec7db2c0d1a6ba211560e37f99d188a94f11668"
2376
+ },
2372
2377
  {
2373
2378
  "path": "docs/adr/README.md",
2374
2379
  "mode": "0000644",
2375
- "sha256": "6d2a97e2c7ce557aad5e82dd02c529dc05f0bdf6c014df9a812d3e65c7216763"
2380
+ "sha256": "c5e8e4a27cf07799da677172e50a0c06d9be9bdf31f511416b40e392d268ffc0"
2376
2381
  },
2377
2382
  {
2378
2383
  "path": "docs/api-reference.md",
@@ -2407,12 +2412,12 @@
2407
2412
  {
2408
2413
  "path": "docs/copy-inventory.md",
2409
2414
  "mode": "0000644",
2410
- "sha256": "999f2aebe366e15d3a90e5f5eae8895d083b6bfab971ce5dae5689abf746b148"
2415
+ "sha256": "befe58f901848724fd48c74e38ee396b31d2f523313708dbfe90138b9f86a2b2"
2411
2416
  },
2412
2417
  {
2413
2418
  "path": "docs/copy-registry.json",
2414
2419
  "mode": "0000644",
2415
- "sha256": "c2800908ad74f7719dd80dd441bd0720733f3219a45a43aa8e3aecb939bad703"
2420
+ "sha256": "26b2738a40324254a1222b3493eafceb00d77dbe07bfc9a5c882f576901040e0"
2416
2421
  },
2417
2422
  {
2418
2423
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2922,7 +2927,7 @@
2922
2927
  {
2923
2928
  "path": "docs/module-api-changelog.md",
2924
2929
  "mode": "0000644",
2925
- "sha256": "aea7064cd17fd4d1fc01c0200f2308dd2a86d619c5597a08ec6e48ed0fea84c1"
2930
+ "sha256": "820f5b58bcabe2204f9afdd3b19cc08e487d4cc27b749dedeb5748f3a43e48e0"
2926
2931
  },
2927
2932
  {
2928
2933
  "path": "docs/modules-contract.md",
@@ -3012,7 +3017,7 @@
3012
3017
  {
3013
3018
  "path": "docs/page-readings.json",
3014
3019
  "mode": "0000644",
3015
- "sha256": "688397e5f326c13764b78b90929ed54213c683df661043d357c37cfc85b8ce35"
3020
+ "sha256": "7fefba487e6aee704f8d23d876d4db7720c9c1231ab61e94f5903f060d96c424"
3016
3021
  },
3017
3022
  {
3018
3023
  "path": "docs/project-context.template.md",
@@ -5922,12 +5927,12 @@
5922
5927
  {
5923
5928
  "path": "modules/hall-ui/public/goal-author-lib.js",
5924
5929
  "mode": "0000644",
5925
- "sha256": "3d962593e1d29665ba54f4bb356291123e3cccf53178a1eab79838f7e54bd09d"
5930
+ "sha256": "cacce0bf30ff04fbd040783aab51f53393259b537df7537664aedd084412eaef"
5926
5931
  },
5927
5932
  {
5928
5933
  "path": "modules/hall-ui/public/goal-author.js",
5929
5934
  "mode": "0000644",
5930
- "sha256": "e4d5355eb3d2a9c894cce901c20051f769e4817f85bb3ba0ec7ebe4d67921306"
5935
+ "sha256": "ef1c80876cf921aa03f2eefc4f04efa40d97428d8c1537e070f6b16e1d137602"
5931
5936
  },
5932
5937
  {
5933
5938
  "path": "modules/hall-ui/public/goal-forest-lib.js",
@@ -10517,12 +10522,12 @@
10517
10522
  {
10518
10523
  "path": "package-lock.json",
10519
10524
  "mode": "0000644",
10520
- "sha256": "1cd22cb8399eb1ee3a2d88e4daa62bf7e297bd5fe8c4b2dff3d479d22424397a"
10525
+ "sha256": "9a1ff520eecca180a4845d8b3485c1d2045d5a04416b264ed007caf8d1903e84"
10521
10526
  },
10522
10527
  {
10523
10528
  "path": "package.json",
10524
10529
  "mode": "0000644",
10525
- "sha256": "fab9569c9c6d74288d0600330226824092a5f614cad6dc56fa2aeadc66390562"
10530
+ "sha256": "4f1a3afb1e80fd281507467f3ce7c3c6813882e18afe9225515566ae592dac87"
10526
10531
  },
10527
10532
  {
10528
10533
  "path": "public-docs/index.html",
@@ -10542,7 +10547,7 @@
10542
10547
  {
10543
10548
  "path": "release-notes.json",
10544
10549
  "mode": "0000644",
10545
- "sha256": "f1ff443de9dc0455809075798cb9d83b321c92432a551e23531cd53487d9aad7"
10550
+ "sha256": "e648eee9b85160b8871a678d78ba4ded897ef4e16b64d51047c796c88ecda2cd"
10546
10551
  },
10547
10552
  {
10548
10553
  "path": "scripts/cron/style_guide_audit.sh",
@@ -12897,7 +12902,7 @@
12897
12902
  {
12898
12903
  "path": "src/module-api.js",
12899
12904
  "mode": "0000644",
12900
- "sha256": "e23647f9c7de84f53386722f8666995b6e49ee459ff78e037cef956c9d4874eb"
12905
+ "sha256": "0ace35c177cb9560521ce1c86cb354981310dcce49e45ac31f527afb1002ff30"
12901
12906
  },
12902
12907
  {
12903
12908
  "path": "src/module-loader/catalog.js",
@@ -14687,7 +14692,7 @@
14687
14692
  {
14688
14693
  "path": "tests/goal_author_ui.mjs",
14689
14694
  "mode": "0000644",
14690
- "sha256": "78d7caca514961f3f21a86de47bad2c76408434bbb887d302f9b688fb46387bf"
14695
+ "sha256": "fe81fe8835244f6e98aad16fd46b304ac23bc0d755b74481b9d720f19af6c3d2"
14691
14696
  },
14692
14697
  {
14693
14698
  "path": "tests/goal_category.mjs",
@@ -0,0 +1,64 @@
1
+ # 0382 — A hardware security card is a passkey step-up check, switched on per builder by an Archon
2
+
3
+ - **Status:** Proposed (research draft — nothing is built by this ADR)
4
+ - **Date:** 2026-10-15
5
+ - **Task:** task 1004676 (from idea 1001144)
6
+ - **Decided by:** pending owner review
7
+ - **Related:** [ADR 0016](0016-trust-boundary-server-enforced-permissions.md) (trust boundary: the server enforces)
8
+
9
+ ## Context
10
+
11
+ A builder should be able to tap an NFC card on a reader to prove who they are before reaching sensitive features. The owner has an NFC biometric card with FIDO on it and wants to test it.
12
+
13
+ Findings from the code (2026-10-15):
14
+
15
+ 1. **No passkey / WebAuthn support exists.** `grep` over `src/` and `package.json` finds nothing. Sign-in is GitHub OAuth (`src/bongos/auth-github.js`) or, for hosted projects, the hub as identity provider (`auth-idp.js`). Both end in the same `builder_sessions` row (`auth.js`), and rank is read fresh from the DB on every request.
16
+ 2. **A step-up check fits the existing gates.** `requireBuilder` / `requireRank` / `requirePermission` in `auth.js` already run per request, so one more gate can ask "has this session proven a card recently?".
17
+ 3. **A plain RFID / PC-SC reader is not needed.** A FIDO2 card is a standard WebAuthn authenticator: the browser talks to it over NFC (or USB). No driver or PC/SC middleware is required for this path.
18
+
19
+ ## Decision
20
+
21
+ **D1 — Use WebAuthn (FIDO2) via a vetted library** (`@simplewebauthn/server` + `@simplewebauthn/browser`). No hand-rolled CBOR/COSE parsing.
22
+
23
+ **D2 — The card is a second factor, never the first.** GitHub (or hub) sign-in stays. The card adds a *step-up*: a session that has done a card check in the last **10 minutes** may reach the protected actions. The proof is a timestamp column on the server-side `builder_sessions` row, set only by the verify route; nothing the client sends counts as proof.
24
+
25
+ **D3 — Register first.** A builder registers a card while signed in the normal way. The requirement can only be switched on for a builder who has at least one registered credential, so nobody is locked out by a switch. The Archon route refuses (409) when the builder has no credential.
26
+
27
+ **D4 — An Archon switches the requirement on or off per builder.** Default is off for everyone. The switch is a server-side flag on the builder, set through an Archon-ranked route (rank checked fresh, per ADR 0016). Local file edits cannot flip it.
28
+
29
+ **D5 — What the card guards (first cut, owner to confirm):** rank changes, deploy / go-live, session revocation, and any payout or treasury action. Chosen as the actions where a stolen session does the most damage.
30
+
31
+ **D6 — Challenge and credential hygiene.** Random single-use challenges, server-stored, short-lived; verify origin and RP ID; `userVerification: 'required'` (the card's biometric is the second proof); credentials table with `credential_id`, `public_key`, `sign_count`, `transports`, `aaguid`, `nickname`, `created_at`, `last_used_at`; an opaque random user handle, never the email. A `sign_count` that goes backwards (when the card reports a counter) fails the check and is logged as a possible clone. Indexes on `credential_id` and `builder_id`. Challenges carry a TTL and are swept.
32
+
33
+ **D7 — RP ID.** The RP ID is the registrable domain. Changing it later invalidates every credential, so it is set once, by the owner. Hosted projects on other subdomains need their own decision (one RP ID per host, or the hub as the sole place a card is checked).
34
+
35
+ **D8 — Recovery is its own decision, not part of this ADR.** The owner will define the lost-card protocol. Until then the Archon off-switch (D4) is the recovery path, and a builder may register more than one card.
36
+
37
+ ## Alternatives considered
38
+
39
+ - **PC/SC reader middleware** reading the card's UID: rejected. A UID is a copyable identifier, not a cryptographic proof, and it needs drivers on every machine.
40
+ - **Card as the only login:** rejected. It removes the fallback and makes a lost card a lockout.
41
+
42
+ ## Testing and rollout
43
+
44
+ - **Local:** passkeys work on `http://localhost`. Chrome DevTools' virtual authenticator tests without hardware; the real card is tested on the owner's machine.
45
+ - **Rollout:** ship with the switch off for everyone (safe to land); the owner turns it on for their own account and tries the real card on the live site; then an Archon enables it builder by builder as cards are handed out.
46
+
47
+ ## Hardware and cost
48
+
49
+ One FIDO2 NFC card (owner already has one), plus an NFC reader only if the computer has no NFC (a FIDO-compatible USB NFC reader is roughly $20–40). Phones with NFC need nothing extra.
50
+
51
+ ## Follow-up tasks
52
+
53
+ Filed from this research: tasks 1004746 (registration page), 1004747 (step-up gate), 1004748 (Archon switch), 1004749 (lost-card recovery), and idea 1001246 (WebAuthn foundation). Sizing: one dependency, one table, roughly five small tasks, about a working week in total.
54
+
55
+ 1. WebAuthn foundation: library, credentials table, challenge store.
56
+ 2. Card registration page for a signed-in builder (+ list / rename / remove).
57
+ 3. The step-up gate and the protected actions of D5.
58
+ 4. The Archon per-builder switch (D4) and its audit-log entry.
59
+ 5. Lost-card recovery (waits on the owner's protocol).
60
+
61
+ ## Consequences
62
+
63
+ - One new dependency and one new table; no change to how anyone signs in until the switch is turned on.
64
+ - The protected-action list must be kept in step with new sensitive routes (a fitness check is a worthwhile follow-up).
@@ -500,3 +500,4 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
500
500
  | 0379 | [**The treasury is pots per goal, a reserve, and borrowing, over real spend** ([task 1004712](https://cloudbongos.com/builders#/task/1004712), goal 1000111). **D1:** spend is cost_log through each task's goal, this month, read every time. **D2:** a pot is a budget the Treasury's holder gives a goal (`government_pots`, government_031). **D3:** the reserve is last month's spend no pot was given, less loans; it may be overdrawn. **D4:** a dry pot files a borrow (poller), its ask stored on `treasury_spending:<id>` and routed to the Treasury. **D5:** answered once (lend, half, no) by its seat. **D6:** the spend cap is a project setting, off by default. **D7:** the docket source and the hall's pots, reserve, beacon and book are real.](0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md) | government / treasury / city |
501
501
  | 0380 | [**A strictness preset is a bundle of laws, switched by one laws vote and read from the laws in force** ([task 1004715](https://cloudbongos.com/builders#/task/1004715), goal 1000111). **D1:** Open workshop, Guild, Factory are catalogues of laws in `presets.js`; every lock names a stored personal setting. **D2:** a switch is ONE laws amendment through `board.proposeLaws` (`POST /government/board/presets`); only its passing writes. **D3:** the preset in force is read from the laws in force, never stored; nothing is seeded. **D4:** `GET /government/presets` says what each would lock; My agents and the Charter are real.](0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md) | government / laws / my agents |
502
502
  | 0381 | [**The hub moves each hosted project's core on its own, by that project's update rule** ([task 1004729](https://cloudbongos.com/builders#/task/1004729), goal 1000090). Amends ADR 0293's "not automatic" for hosted projects. **D1:** after each snapshot the runner queues a preflight for `recommended`, then the move once that preflight passed. **D2:** through `enqueueIntent` as `runner:auto-upgrade`; never the control-plane row. **D3:** one automatic intent per tick. **D4:** a target that already failed or was refused is held (read from existing rows; task 1004730 is the richer hold). **D5:** `BONGOS_AUTO_CORE_UPGRADE=off` stops it platform-wide; default on; `pinned` is the per-project switch.](0381-the-hub-moves-each-hosted-project-by-its-update-rule.md) | provisioning / core upgrade / automation |
503
+ | 0382 | [**A hardware card is a passkey step-up check, switched on per builder by an Archon** ([task 1004676](https://cloudbongos.com/builders#/task/1004676), proposed). No WebAuthn exists today; sign-in is GitHub OAuth or the hub IdP. **D1:** WebAuthn via a vetted library. **D2:** the card is a second factor, a server-recorded step-up, never the first. **D3:** register first; the requirement needs a registered card. **D4:** an Archon switches it on per builder, default off. **D5:** guards rank changes, deploy, session revoke, payouts. No PC/SC reader needed. Rejected: PC/SC middleware, card as sole login.](0382-a-hardware-card-is-a-passkey-step-up-an-archon-switches-on-per-builder.md) | auth / security |
@@ -4,13 +4,13 @@
4
4
  > Deterministic on a given tree — regenerate and commit rather than hand-editing.
5
5
  > Machine-readable twin: [`copy-registry.json`](copy-registry.json).
6
6
 
7
- **2739 strings** across 3 surfaces · 2317 distinct pieces of copy · 2739 certain, 0 heuristic · 242 shown in more than one place.
7
+ **2742 strings** across 3 surfaces · 2320 distinct pieces of copy · 2742 certain, 0 heuristic · 242 shown in more than one place.
8
8
 
9
9
  ## Surfaces
10
10
 
11
11
  | surface | lives at | files | strings |
12
12
  |---|---|---|---|
13
- | `builders-hall` | `modules/hall-ui/public` | 145 | 1931 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 145 | 1934 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 100 |
15
15
  | `landing` | `modules/public-landing/public` | 9 | 708 |
16
16
 
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
78
78
  | `8226c979db4d` | 3x | Not now | modules/hall-ui/public/collab.js:668, modules/hall-ui/public/paintbrush.js:119, modules/hall-ui/public/paintbrush.js:183 |
79
79
  | … | | _202 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1931 strings
81
+ ## `builders-hall` — 1934 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -151,10 +151,12 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
151
151
  | `524188fe0078` | certain | Active sessions | `modules/hall-ui/public/settings.html:159` |
152
152
  | `d628b36ec58e` | certain | Activity, by role | `modules/hall-ui/public/profile.html:138` |
153
153
  | `32509514479f` | certain | Add a done-when criterion | `modules/hall-ui/public/goal-author.js:38` |
154
+ | `83beba462b20` | certain | Add a task | `modules/hall-ui/public/goal-author.js:38` |
154
155
  | `f4c5a03f715c` | certain | Add criterion | `modules/hall-ui/public/goal-author.js:38` |
155
156
  | `2b94171ce525` | certain | Add dependency | `modules/hall-ui/public/task.js:592` |
156
157
  | `a70b72ec4dcc` | certain | Add images | `modules/hall-ui/public/page-notes.js:120` |
157
158
  | `da8659b0099b` | certain | Add something you wrote — notes, a standard, a worked example. It is indexed for recall, not pasted into every | `modules/hall-ui/public/settings-specialities.js:354` |
159
+ | `8b51e2e70cfb` | certain | Add task | `modules/hall-ui/public/goal-author.js:38` |
158
160
  | `82a8bd77d374` | certain | Add these ranks | `modules/hall-ui/public/government.js:929` |
159
161
  | `eded707fab69` | certain | Add to what it knows | `modules/hall-ui/public/settings-specialities.js:354` |
160
162
  | `cc4125ca7cff` | certain | Added to this version after it started building — an Archon override of the scope gate. Open the goal to see w | `modules/hall-ui/public/goals-render.js:194` |
@@ -641,6 +643,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
641
643
  | `ef224c9de7c8` | certain | Ideas — {{worldName}} | `modules/hall-ui/public/ideas.html:6` |
642
644
  | `9e9f8a44276b` | certain | Identity vouched | `modules/hall-ui/public/watch.js:396` |
643
645
  | `3c24eedc9f2d` | certain | If that page does not let you in, ask {…} to install it for you.{…} | `modules/hall-ui/public/settings-software-update.js:101` |
646
+ | `65977bbcda48` | certain | If the goal still has a “Spec goal” task that is not done, this task waits behind it until the spec is finishe | `modules/hall-ui/public/goal-author.js:38` |
644
647
  | `7d6a7de7a69e` | certain | If the {…} has invited you, sign in with GitHub and you're in. If not, ask them for an invite — this project d | `modules/hall-ui/public/hall-render.js:292` |
645
648
  | `4ef9f2a4df3b` | certain | If you own this project, | `modules/hall-ui/public/deploy.js:424` |
646
649
  | `b6c47d4ee42c` | certain | Image key removed. | `modules/hall-ui/public/settings.js:1012` |
@@ -1365,7 +1368,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1365
1368
  | `bcdbded93d4e` | certain | The work board failed to load. Reload to try again. | `modules/hall-ui/public/work.js:1214` |
1366
1369
  | `996d2aa0e2f0` | certain | Then copy the project onto your machine, install its dependencies, and sign in: | `modules/hall-ui/public/hall-render.js:837` |
1367
1370
  | `383018c7df70` | certain | There is no transcript you can read for this session. | `modules/hall-ui/public/sessions.js:234` |
1368
- | `9f09494abfa1` | certain | These open goals look related — use one instead? | `modules/hall-ui/public/goal-author.js:125` |
1371
+ | `9f09494abfa1` | certain | These open goals look related — use one instead? | `modules/hall-ui/public/goal-author.js:144` |
1369
1372
  | `d6b9826f1bd3` | certain | These words are the studio page itself. Take the page, type over them here, and submit it from the editor when | `modules/hall-ui/public/studio.html:106` |
1370
1373
  | `775a73b066e3` | certain | These words come from the skill’s own description, which was written for Claude to recognise rather than for y | `modules/hall-ui/public/settings-specialities.js:128` |
1371
1374
  | `a0140098c0e0` | certain | They already have an invitation or a join-request pending on this goal. | `modules/hall-ui/public/goals-render.js:1302` |
@@ -1970,11 +1973,11 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1970
1973
  | `e31a588bb9e1` | certain | — moved {…}, and the box confirmed it. | `modules/hall-ui/public/deploy.js:400` |
1971
1974
  | `417adabd1a00` | certain | — no goal suggested — open the goal board and pick one — | `modules/hall-ui/public/task-detail.js:412` |
1972
1975
  | `1f9504667df5` | certain | — none — | `modules/hall-ui/public/goal-author.js:38` |
1973
- | `1f9504667df5` | certain | — none — | `modules/hall-ui/public/goal-author.js:109` |
1976
+ | `1f9504667df5` | certain | — none — | `modules/hall-ui/public/goal-author.js:128` |
1974
1977
  | `da9962dc1729` | certain | — or open any diagram fullscreen. | `modules/hall-ui/public/diagrams.html:33` |
1975
1978
  | `b1ca53ebfc10` | certain | — pick a goal — | `modules/hall-ui/public/goal-author.js:38` |
1976
- | `b1ca53ebfc10` | certain | — pick a goal — | `modules/hall-ui/public/goal-author.js:94` |
1977
- | `825930577f01` | certain | — pick a version — | `modules/hall-ui/public/goal-author.js:106` |
1979
+ | `b1ca53ebfc10` | certain | — pick a goal — | `modules/hall-ui/public/goal-author.js:112` |
1980
+ | `825930577f01` | certain | — pick a version — | `modules/hall-ui/public/goal-author.js:125` |
1978
1981
  | `61ebc5d684fd` | certain | — stops the project and its nightly backups but keeps everything: its database, its backups and your repositor | `modules/hall-ui/public/project-settings.html:60` |
1979
1982
  | `e5c5e94152b3` | certain | — that's all the tooling you need up front. | `modules/hall-ui/public/hall-render.js:837` |
1980
1983
  | `85f1dd2ee732` | certain | — the whole project | `modules/hall-ui/public/goal-map.html:69` |
@@ -3,9 +3,9 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 2739,
7
- "unique_ids": 2317,
8
- "certain": 2739,
6
+ "strings": 2742,
7
+ "unique_ids": 2320,
8
+ "certain": 2742,
9
9
  "heuristic": 0,
10
10
  "duplicate_ids": 242
11
11
  },
@@ -13,7 +13,7 @@
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
15
  "files": 145,
16
- "strings": 1931
16
+ "strings": 1934
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -2615,7 +2615,7 @@
2615
2615
  "occurrences": 2,
2616
2616
  "at": [
2617
2617
  "modules/hall-ui/public/goal-author.js:38",
2618
- "modules/hall-ui/public/goal-author.js:109"
2618
+ "modules/hall-ui/public/goal-author.js:128"
2619
2619
  ]
2620
2620
  },
2621
2621
  {
@@ -2625,7 +2625,7 @@
2625
2625
  "occurrences": 2,
2626
2626
  "at": [
2627
2627
  "modules/hall-ui/public/goal-author.js:38",
2628
- "modules/hall-ui/public/goal-author.js:94"
2628
+ "modules/hall-ui/public/goal-author.js:112"
2629
2629
  ]
2630
2630
  },
2631
2631
  {
@@ -3261,6 +3261,15 @@
3261
3261
  "origin": "js-markup",
3262
3262
  "confidence": "certain"
3263
3263
  },
3264
+ {
3265
+ "id": "83beba462b20",
3266
+ "surface": "builders-hall",
3267
+ "text": "Add a task",
3268
+ "file": "modules/hall-ui/public/goal-author.js",
3269
+ "line": 38,
3270
+ "origin": "js-markup",
3271
+ "confidence": "certain"
3272
+ },
3264
3273
  {
3265
3274
  "id": "f4c5a03f715c",
3266
3275
  "surface": "builders-hall",
@@ -3297,6 +3306,15 @@
3297
3306
  "origin": "js-markup",
3298
3307
  "confidence": "certain"
3299
3308
  },
3309
+ {
3310
+ "id": "8b51e2e70cfb",
3311
+ "surface": "builders-hall",
3312
+ "text": "Add task",
3313
+ "file": "modules/hall-ui/public/goal-author.js",
3314
+ "line": 38,
3315
+ "origin": "js-markup",
3316
+ "confidence": "certain"
3317
+ },
3300
3318
  {
3301
3319
  "id": "82a8bd77d374",
3302
3320
  "surface": "builders-hall",
@@ -7671,6 +7689,15 @@
7671
7689
  "origin": "js-markup",
7672
7690
  "confidence": "certain"
7673
7691
  },
7692
+ {
7693
+ "id": "65977bbcda48",
7694
+ "surface": "builders-hall",
7695
+ "text": "If the goal still has a “Spec goal” task that is not done, this task waits behind it until the spec is finished.",
7696
+ "file": "modules/hall-ui/public/goal-author.js",
7697
+ "line": 38,
7698
+ "origin": "js-markup",
7699
+ "confidence": "certain"
7700
+ },
7674
7701
  {
7675
7702
  "id": "7d6a7de7a69e",
7676
7703
  "surface": "builders-hall",
@@ -14192,7 +14219,7 @@
14192
14219
  "surface": "builders-hall",
14193
14220
  "text": "These open goals look related — use one instead?",
14194
14221
  "file": "modules/hall-ui/public/goal-author.js",
14195
- "line": 125,
14222
+ "line": 144,
14196
14223
  "origin": "js-markup",
14197
14224
  "confidence": "certain"
14198
14225
  },
@@ -19637,7 +19664,7 @@
19637
19664
  "surface": "builders-hall",
19638
19665
  "text": "— none —",
19639
19666
  "file": "modules/hall-ui/public/goal-author.js",
19640
- "line": 109,
19667
+ "line": 128,
19641
19668
  "origin": "js-markup",
19642
19669
  "confidence": "certain"
19643
19670
  },
@@ -19664,7 +19691,7 @@
19664
19691
  "surface": "builders-hall",
19665
19692
  "text": "— pick a goal —",
19666
19693
  "file": "modules/hall-ui/public/goal-author.js",
19667
- "line": 94,
19694
+ "line": 112,
19668
19695
  "origin": "js-markup",
19669
19696
  "confidence": "certain"
19670
19697
  },
@@ -19673,7 +19700,7 @@
19673
19700
  "surface": "builders-hall",
19674
19701
  "text": "— pick a version —",
19675
19702
  "file": "modules/hall-ui/public/goal-author.js",
19676
- "line": 106,
19703
+ "line": 125,
19677
19704
  "origin": "js-markup",
19678
19705
  "confidence": "certain"
19679
19706
  },
@@ -3103,5 +3103,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3103
3103
  landed since 1.21.131 with no explicit bump. run 38783817499. (task 1002620)
3104
3104
  1.21.133 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3105
3105
  landed since 1.21.132 with no explicit bump. run 38803950047. (task 1002620)
3106
+ 1.21.134 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3107
+ landed since 1.21.133 with no explicit bump. run 38810062389. (task 1002620)
3108
+ 1.21.135 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3109
+ landed since 1.21.134 with no explicit bump. run 38812449926. (task 1002620)
3106
3110
  ---------------------------------------------------------------------------
3107
3111
  ```
@@ -1201,7 +1201,7 @@
1201
1201
  "surface": "builders",
1202
1202
  "title": "Goals",
1203
1203
  "states_read": ["list","filters","cards","detail","dependencies","out","uat","uat-panel","author"],
1204
- "files_hash": "f7f8e781b83d7c32",
1204
+ "files_hash": "3f07bbb487b850ec",
1205
1205
  "reading_hash": "7aa1a6e1946dc077",
1206
1206
  "counts": {"lines":156,"placed":52,"shared":8,"unplaced":96},
1207
1207
  "omitted": {"data":268},
@@ -89,6 +89,29 @@
89
89
  return null;
90
90
  }
91
91
 
92
+ // POST /tasks body (task 1003462). The goal and its version are the whole of
93
+ // what places a task; the server validates goal-belongs-to-version, the open
94
+ // goal, the rank floor and the kind/discipline inference, so none of those are
95
+ // sent or pre-checked here.
96
+ function buildTaskBody(f) {
97
+ const body = {
98
+ version_id: clean(f.version_id),
99
+ goal_id: Number(f.goal_id),
100
+ title: clean(f.title),
101
+ };
102
+ const description = clean(f.description);
103
+ if (description) body.description = description;
104
+ return body;
105
+ }
106
+
107
+ function taskFormProblem(f) {
108
+ if (!(Number(f.goal_id) > 0)) return 'Pick the goal this task belongs to.';
109
+ const t = clean(f.title);
110
+ if (!t) return 'Give the task a name.';
111
+ if (t.length > TITLE_MAX) return `Keep the name under ${TITLE_MAX} characters.`;
112
+ return null;
113
+ }
114
+
92
115
  // The refusal, in words. The server's envelope is { error: { code, message } };
93
116
  // an older route may send { error: 'code' } or { message }. A refusal the server
94
117
  // phrased keeps its own sentence — this only supplies one where the server sent
@@ -99,6 +122,7 @@
99
122
  criterion_exists: 'A criterion with that name already exists on this version. Reword the outcome slightly.',
100
123
  reserved_criterion_slug: 'That name looks like a position (C1, C2…). Reword the outcome.',
101
124
  bad_goal_id: 'That goal is not on this version.',
125
+ goal_id_required: 'Pick the goal this task belongs to.',
102
126
  goal_not_yours: 'Only a goal’s owner or manager (or an Archon) can add criteria to it.',
103
127
  version_not_found: 'That version no longer exists.',
104
128
  };
@@ -124,6 +148,6 @@
124
148
 
125
149
  window.OTBGoalAuthor = {
126
150
  TITLE_MAX, criterionSlug, buildGoalBody, goalFormProblem,
127
- buildCriterionRequest, criterionFormProblem, refusalText, openVersions,
151
+ buildCriterionRequest, criterionFormProblem, buildTaskBody, taskFormProblem, refusalText, openVersions,
128
152
  };
129
153
  })();
@@ -2,7 +2,7 @@
2
2
  // panel (task 1003463, goal 1000089; ADR 0366 D1.2/D1.3 + D3). The hall could
3
3
  // SATISFY a criterion and never AUTHOR one, and had no POST /goals call at all.
4
4
  //
5
- // Two forms, nothing else:
5
+ // Three forms, nothing else (the third, a task against a goal, is task 1003462):
6
6
  // · GOAL — a name, a one-line subtitle, an optional longer description, the
7
7
  // version, an optional category. NO scope field: the goal is created with
8
8
  // spec_task:true, so it arrives with a "Spec goal N" task that gates its other
@@ -75,6 +75,23 @@
75
75
  <p class="goal-author__err" id="ga-crit-err" role="alert" hidden></p>
76
76
  <button type="submit" class="btn btn--primary" id="ga-crit-go">Add criterion</button>
77
77
  </form>
78
+
79
+ <h2 class="scroll__h goal-author__h2" id="ga-task-h">Add a task</h2>
80
+ <form id="ga-task" class="goal-author__form" novalidate aria-labelledby="ga-task-h">
81
+ <label class="goal-author__field">Goal
82
+ <select id="ga-task-goal" required><option value="">— pick a goal —</option></select>
83
+ </label>
84
+ <label class="goal-author__field">Name
85
+ <input id="ga-task-title" type="text" maxlength="${lib.TITLE_MAX}" autocomplete="off" required>
86
+ </label>
87
+ <label class="goal-author__field">Description <span class="goal-author__hint">optional</span>
88
+ <textarea id="ga-task-desc" rows="3"></textarea>
89
+ </label>
90
+ <p class="goal-author__note">If the goal still has a “Spec goal” task that is not done, this task waits behind it until the spec is finished.</p>
91
+ <p class="goal-author__note" id="ga-task-ok" role="status" aria-live="polite" hidden></p>
92
+ <p class="goal-author__err" id="ga-task-err" role="alert" hidden></p>
93
+ <button type="submit" class="btn btn--primary" id="ga-task-go">Add task</button>
94
+ </form>
78
95
  </section>`;
79
96
 
80
97
  const $q = (id) => root.querySelector(`#${id}`);
@@ -88,12 +105,14 @@
88
105
  }
89
106
 
90
107
  function fillGoalChoices() {
91
- const sel = $q('ga-crit-goal');
92
- const keep = sel.value;
93
108
  const goals = (getGoals() || []).filter((g) => g && g.status === 'open');
94
- sel.innerHTML = '<option value="">— pick a goal —</option>' + goals.map((g) =>
95
- `<option value="${escapeHtml(String(g.id))}" data-version="${escapeHtml(String(g.version_id || ''))}">${escapeHtml(`${g.version_id || ''} · ${g.title}`)}</option>`).join('');
96
- sel.value = keep;
109
+ for (const id of ['ga-crit-goal', 'ga-task-goal']) {
110
+ const sel = $q(id);
111
+ const keep = sel.value;
112
+ sel.innerHTML = '<option value="">— pick a goal —</option>' + goals.map((g) =>
113
+ `<option value="${escapeHtml(String(g.id))}" data-version="${escapeHtml(String(g.version_id || ''))}">${escapeHtml(`${g.version_id || ''} · ${g.title}`)}</option>`).join('');
114
+ sel.value = keep;
115
+ }
97
116
  }
98
117
 
99
118
  async function loadChoices() {
@@ -191,6 +210,36 @@
191
210
  showErr('ga-crit-err', 'Could not reach the server. Try again in a moment.');
192
211
  } finally { btn.disabled = false; }
193
212
  });
213
+
214
+ // task 1003462 — a task against a goal. Sends what was typed to POST /tasks and
215
+ // lets the server's refusals (floor, goal/version, closed goal) speak.
216
+ $q('ga-task').addEventListener('submit', async (e) => {
217
+ e.preventDefault();
218
+ showErr('ga-task-err', '');
219
+ const ok = $q('ga-task-ok');
220
+ ok.hidden = true;
221
+ const sel = $q('ga-task-goal');
222
+ const opt = sel.options[sel.selectedIndex];
223
+ const fields = {
224
+ goal_id: sel.value, version_id: opt ? opt.getAttribute('data-version') : '',
225
+ title: $q('ga-task-title').value, description: $q('ga-task-desc').value,
226
+ };
227
+ const problem = lib.taskFormProblem(fields);
228
+ if (problem) return showErr('ga-task-err', problem);
229
+ const btn = $q('ga-task-go');
230
+ btn.disabled = true;
231
+ try {
232
+ const res = await api.request('POST', `${API}/tasks`, { body: lib.buildTaskBody(fields) });
233
+ if (!res.ok) return showErr('ga-task-err', lib.refusalText(res.status, res.data));
234
+ const task = (res.data && res.data.task) || {};
235
+ $q('ga-task-title').value = '';
236
+ $q('ga-task-desc').value = '';
237
+ ok.textContent = task.id != null ? `Task ${task.id} added to the goal. It waits behind the goal’s spec task if that is not done yet.` : 'Task added.';
238
+ ok.hidden = false;
239
+ } catch (_) {
240
+ showErr('ga-task-err', 'Could not reach the server. Try again in a moment.');
241
+ } finally { btn.disabled = false; }
242
+ });
194
243
  }
195
244
 
196
245
  window.OTBGoalAuthorUI = { mount };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.133",
3
+ "version": "1.21.135",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.133",
9
+ "version": "1.21.135",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.133",
3
+ "version": "1.21.135",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -9800,5 +9800,17 @@
9800
9800
  "id": "1003463",
9801
9801
  "text": "The Goals page can now create a goal and add a done-when criterion to it."
9802
9802
  }
9803
+ ],
9804
+ "1.21.134": [
9805
+ {
9806
+ "id": "1004676",
9807
+ "text": "Researched adding a tap-your-security-card check to Bongos. Plan written up and follow-up build work filed."
9808
+ }
9809
+ ],
9810
+ "1.21.135": [
9811
+ {
9812
+ "id": "1003462",
9813
+ "text": "Adds an Add-a-task form to the Goals page: pick an open goal, name the task, optional description. Sends POST /tasks unchanged; the server's rank floor, goal/version and closed-goal refusals render as sentences. States that a"
9814
+ }
9803
9815
  ]
9804
9816
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.133'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.135'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -208,3 +208,30 @@ test('wall: both routes still sit behind their permission atoms', () => {
208
208
  assert.match(g, /router\.post\('\/goals', auth\.requireBuilder, auth\.requirePermission\('goal\.create'\)/);
209
209
  assert.match(d, /router\.post\('\/versions\/:id\/done-when', auth\.requireBuilder, auth\.requirePermission\('criterion\.create'\)/);
210
210
  });
211
+
212
+ // ── 4. creating a task against a goal (task 1003462) ─────────────────────────
213
+
214
+ test('the task body names the goal and its version and nothing the server owns', () => {
215
+ const body = lib.buildTaskBody({
216
+ version_id: ' V9 ', goal_id: '12', title: ' Write it down ', description: ' more ',
217
+ status: 'ready', requires_rank: 'xenos', credits_reward: 5, created_by: 'x',
218
+ });
219
+ assert.deepEqual(plain(body), { version_id: 'V9', goal_id: 12, title: 'Write it down', description: 'more' });
220
+ });
221
+
222
+ test('an empty description is omitted; the form asks only for goal and name', () => {
223
+ assert.deepEqual(plain(lib.buildTaskBody({ version_id: 'V9', goal_id: 3, title: 'x', description: ' ' })), { version_id: 'V9', goal_id: 3, title: 'x' });
224
+ assert.match(lib.taskFormProblem({ goal_id: '', title: 'x' }), /goal/i);
225
+ assert.match(lib.taskFormProblem({ goal_id: '3', title: ' ' }), /name/i);
226
+ assert.match(lib.taskFormProblem({ goal_id: '3', title: 'x'.repeat(lib.TITLE_MAX + 1) }), /under/);
227
+ assert.equal(lib.taskFormProblem({ goal_id: '3', title: 'x' }), null);
228
+ });
229
+
230
+ test('the task form is wired, reuses the suggester, and states the spec-task wait', () => {
231
+ const src = read('goal-author.js');
232
+ assert.match(src, /ga-task/);
233
+ assert.match(src, /buildTaskBody/);
234
+ assert.match(src, /\$\{API\}\/tasks`/);
235
+ assert.match(src, /waits behind/i);
236
+ assert.ok(!/requires_rank|task\.create/.test(src), 'no client-side authority pre-filter');
237
+ });