@bongos/core 1.19.804 → 1.19.806
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 +267 -242
- package/.claude/skills/ask-for-help/SKILL.md +6 -6
- package/.claude/skills/backlog-review/SKILL.md +1 -1
- package/.claude/skills/blocker-review/SKILL.md +7 -7
- package/.claude/skills/blocker-solve/SKILL.md +5 -5
- package/.claude/skills/bug-triage/SKILL.md +7 -7
- package/.claude/skills/builder-claim/SKILL.md +3 -3
- package/.claude/skills/builder-cost/SKILL.md +2 -2
- package/.claude/skills/builder-end/SKILL.md +2 -2
- package/.claude/skills/builder-exit/SKILL.md +7 -7
- package/.claude/skills/builder-key/SKILL.md +2 -2
- package/.claude/skills/builder-reauth/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +7 -7
- package/.claude/skills/builder-release/SKILL.md +2 -2
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-setup/SKILL.md +3 -3
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/builder-stage/SKILL.md +1 -1
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/fix-task/SKILL.md +4 -4
- package/.claude/skills/goal-create/SKILL.md +18 -18
- package/.claude/skills/goal-review/SKILL.md +7 -7
- package/.claude/skills/grade-audit/SKILL.md +5 -5
- package/.claude/skills/grade-recover/SKILL.md +4 -4
- package/.claude/skills/grader-health/SKILL.md +6 -6
- package/.claude/skills/idea-triage/SKILL.md +14 -14
- package/.claude/skills/merge-mode/SKILL.md +3 -3
- package/.claude/skills/planning-session/SKILL.md +20 -20
- package/.claude/skills/priority-session/SKILL.md +10 -10
- package/.claude/skills/recall/SKILL.md +1 -1
- package/.claude/skills/scan-before-install/SKILL.md +3 -3
- package/.claude/skills/status/SKILL.md +2 -2
- package/.claude/skills/strand-fix/SKILL.md +2 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +45 -11
- package/docs/api-reference.md +11 -10
- package/docs/copy-inventory.md +47 -34
- package/docs/copy-registry.json +200 -72
- package/docs/module-api-changelog.md +4 -0
- package/modules/builder-settings/profile-nudge-prefs.js +1 -1
- package/modules/builder-settings/render-prefs.js +1 -1
- package/modules/dev-box/app/README.md +1 -1
- package/modules/dev-box/app/src/gds-client.js +11 -9
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/dev-box/app/test/unit.mjs +13 -13
- package/modules/dev-box/box-access.js +3 -3
- package/modules/dev-box/box-onboard.js +7 -7
- package/modules/discord/bug-attachments.js +1 -1
- package/modules/economy/cost-classify.js +6 -6
- package/modules/grading/grader-rubric.json +1 -1
- package/modules/grading/grader-score.js +1 -1
- package/modules/hall-ui/module.json +1 -1
- package/modules/hall-ui/public/drachmae.js +3 -3
- package/modules/hall-ui/public/goals-page.js +1 -1
- package/modules/hall-ui/public/goals-render.js +144 -22
- package/modules/hall-ui/public/goals.css +16 -1
- package/modules/hall-ui/public/idea.js +1 -1
- package/modules/hall-ui/public/md-reader.js +1 -1
- package/modules/hall-ui/public/people.html +1 -1
- package/modules/hall-ui/public/people.js +2 -2
- package/modules/hall-ui/public/primer.js +4 -4
- package/modules/hall-ui/public/roster.js +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/task.js +1 -1
- package/modules/ideas/inbox.js +2 -2
- package/modules/lifecycle/db-analytics.js +1 -1
- package/modules/lifecycle/done-when.js +4 -4
- package/modules/lifecycle/version-close.js +1 -1
- package/modules/memory/learnings.js +2 -2
- package/modules/memory/memory.js +1 -1
- package/modules/onboarding/onboarding.js +1 -1
- package/modules/platform-identity/routes/my-projects.js +1 -1
- package/modules/platform-identity/routes/sso.js +2 -2
- package/modules/public-landing/public/index.html +2 -2
- package/modules/public-landing/public/projects-floor.states.json +1 -1
- package/modules/public-landing/public/projects.html +1 -1
- package/modules/public-landing/public/projects.states.json +1 -1
- package/modules/sessions/db.js +120 -0
- package/modules/sessions/module.json +2 -2
- package/modules/sessions/pollers/session-upload-health.js +111 -0
- package/modules/sessions/pulse.js +2 -2
- package/modules/sessions/routes/sessions.js +46 -0
- package/modules/sessions/upload-health.js +197 -0
- package/modules/status-ui/module.json +1 -1
- package/modules/status-ui/public/explore.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/agent-invoke.js +3 -3
- package/scripts/gds/api.js +4 -4
- package/scripts/gds/api.sh +1 -1
- package/scripts/gds/architect-audit.js +1 -1
- package/scripts/gds/artifact-audit.js +2 -2
- package/scripts/gds/audit-ships.js +2 -2
- package/scripts/gds/backfill-cost.js +1 -1
- package/scripts/gds/backfill-sessions.js +1 -1
- package/scripts/gds/backfill-subagent-rewards.js +1 -1
- package/scripts/gds/backfill-task-rewards.js +2 -2
- package/scripts/gds/box-auth-check.js +1 -1
- package/scripts/gds/box-sync.js +3 -3
- package/scripts/gds/box.js +10 -10
- package/scripts/gds/build-cli-package.js +3 -3
- package/scripts/gds/capture.js +2 -2
- package/scripts/gds/ci-grade.js +1 -1
- package/scripts/gds/claim-recall.js +2 -2
- package/scripts/gds/cli-lib.js +3 -3
- package/scripts/gds/context-pack.js +1 -1
- package/scripts/gds/copy-apply.js +2 -2
- package/scripts/gds/dev-lib.js +2 -2
- package/scripts/gds/fetch-art-key.js +2 -2
- package/scripts/gds/fetch-sound-prefs.js +3 -3
- package/scripts/gds/grade-smoke.js +1 -1
- package/scripts/gds/hooks/pre-push.js +1 -1
- package/scripts/gds/init.js +9 -9
- package/scripts/gds/key-card.js +1 -1
- package/scripts/gds/learning-capture.js +2 -2
- package/scripts/gds/linkify-refs.js +2 -2
- package/scripts/gds/local-preview-lib.js +1 -1
- package/scripts/gds/login.js +11 -10
- package/scripts/gds/main-audit.js +1 -1
- package/scripts/gds/newcomer-floor.config.json +1 -1
- package/scripts/gds/newcomer-floor.js +2 -2
- package/scripts/gds/newcomer-floor.templates.json +2 -2
- package/scripts/gds/onboarding-ship.js +1 -1
- package/scripts/gds/paste-token.js +3 -3
- package/scripts/gds/portability-smoke-memory.sh +5 -5
- package/scripts/gds/prove-strict-versioning.js +2 -2
- package/scripts/gds/provision-net.js +2 -2
- package/scripts/gds/provision-units.js +3 -3
- package/scripts/gds/pulse.js +2 -2
- package/scripts/gds/push-path-brief.js +2 -2
- package/scripts/gds/reauth.js +1 -1
- package/scripts/gds/recall.js +1 -1
- package/scripts/gds/redteam-patrol.js +3 -3
- package/scripts/gds/render-ideas.js +5 -5
- package/scripts/gds/repo-metrics.js +1 -1
- package/scripts/gds/sandbox-stage.js +3 -3
- package/scripts/gds/seed-adopt-tasks.js +3 -3
- package/scripts/gds/seed-board-room-tasks.js +1 -1
- package/scripts/gds/seed-bongos-app-tasks.js +2 -2
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +3 -3
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +4 -4
- package/scripts/gds/seed-bongos-tasks.js +5 -5
- package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +1 -1
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +1 -1
- package/scripts/gds/seed-bv1-community-tasks.js +1 -1
- package/scripts/gds/seed-bv1-privacy-invites-tasks.js +1 -1
- package/scripts/gds/seed-bv1-profile-page-tasks.js +1 -1
- package/scripts/gds/seed-bv1-profiles-tasks.js +1 -1
- package/scripts/gds/seed-bv1-r01-delta-tasks.js +1 -1
- package/scripts/gds/seed-gds-v4-tasks.js +3 -3
- package/scripts/gds/seed-medusa-tasks.js +1 -1
- package/scripts/gds/seed-productization-tasks.js +1 -1
- package/scripts/gds/seed-project-admin-console-tasks.js +1 -1
- package/scripts/gds/seed-provisioning-tasks.js +2 -2
- package/scripts/gds/seed-strict-versioning-tasks.js +3 -3
- package/scripts/gds/seed-v2-world-tasks.js +2 -2
- package/scripts/gds/seed-verify-criteria.js +1 -1
- package/scripts/gds/session-digest.js +1 -1
- package/scripts/gds/session.js +2 -2
- package/scripts/gds/setup.js +7 -7
- package/scripts/gds/ship-flow.js +3 -3
- package/scripts/gds/ship-grade-steps.js +3 -3
- package/scripts/gds/ship-memory-sync.js +1 -1
- package/scripts/gds/ship-session-upload.js +3 -3
- package/scripts/gds/smoke-batch-claim.sh +10 -10
- package/scripts/gds/smoke-batch-release.sh +14 -14
- package/scripts/gds/smoke-claim-consistency.sh +19 -19
- package/scripts/gds/smoke-dependencies.sh +9 -9
- package/scripts/gds/smoke-write.sh +11 -11
- package/scripts/gds/start.js +1 -1
- package/scripts/gds/status.js +2 -2
- package/scripts/gds/strand-watch.js +1 -1
- package/scripts/gds/triage.js +4 -4
- package/scripts/gds/version-close.js +2 -2
- package/scripts/hall-preview/README.md +1 -1
- package/scripts/hall-preview/server.js +1 -1
- package/src/bongos/auth-admission.js +3 -3
- package/src/bongos/auth-idp.js +2 -2
- package/src/bongos/auth.js +3 -3
- package/src/bongos/db.js +2 -2
- package/src/bongos/llm-cache.js +2 -2
- package/src/bongos/metrics.js +1 -1
- package/src/bongos/middleware/audit.js +3 -3
- package/src/bongos/middleware/rate-limit.js +1 -1
- package/src/bongos/platform-visibility-gate.js +2 -2
- package/src/bongos/routes/_helpers.js +1 -1
- package/src/bongos/routes/audit-log.js +1 -1
- package/src/bongos/routes/auth.js +5 -5
- package/src/bongos/routes/backup.js +1 -1
- package/src/bongos/routes/builders.js +1 -1
- package/src/bongos/routes/instance.js +1 -1
- package/src/bongos/routes/live.js +1 -1
- package/src/bongos/routes/me.js +2 -2
- package/src/bongos/routes/modules.js +2 -2
- package/src/bongos/routes/public.js +1 -1
- package/src/bongos/routes/security.js +4 -4
- package/src/bongos/routes.js +1 -1
- package/src/bongos/serve-internal.js +6 -6
- package/src/branding.js +1 -1
- package/src/module-api.js +1 -1
- package/src/module-seams.js +2 -2
- package/src/platform-server.js +2 -2
- package/tests/api_alias_exceptions.mjs +67 -0
- package/tests/application_echo.mjs +12 -12
- package/tests/artifact_audit.mjs +1 -1
- package/tests/auth_page_gate.mjs +4 -4
- package/tests/auth_web_recovery.mjs +11 -11
- package/tests/bongos_login.mjs +7 -4
- package/tests/box_env.mjs +6 -6
- package/tests/bug_attachments.mjs +1 -1
- package/tests/cli_sessions.mjs +1 -1
- package/tests/dev_lib.mjs +1 -1
- package/tests/discord_bugs.mjs +2 -2
- package/tests/discord_inbound.mjs +2 -2
- package/tests/explore_visible_states.mjs +1 -1
- package/tests/goal_invite_consent.mjs +307 -0
- package/tests/government_page_gates.mjs +1 -1
- package/tests/hall_reference_world.mjs +1 -1
- package/tests/idea_ideate_full.mjs +1 -1
- package/tests/idp_federation.mjs +2 -2
- package/tests/llm-cache.mjs +1 -1
- package/tests/manage_manifest_shared_read.mjs +3 -3
- package/tests/people_directory_clip.mjs +1 -1
- package/tests/planning_session_skill.mjs +1 -1
- package/tests/platform_visibility_gate.mjs +15 -15
- package/tests/profile_route.mjs +25 -25
- package/tests/projects_hub.mjs +2 -2
- package/tests/provision.mjs +2 -2
- package/tests/provision_settings_apply.mjs +4 -4
- package/tests/sandbox_stage.mjs +8 -8
- package/tests/scouting_owner_access_and_reach.mjs +3 -3
- package/tests/session_upload_health.mjs +329 -0
- package/tests/setup_device_flow_fallback.mjs +1 -1
- package/tests/skill_ask_for_help.mjs +3 -3
- package/tests/skill_grade_audit.mjs +2 -2
- package/tests/skill_grader_health.mjs +3 -3
- package/tests/smoke_write_contract.mjs +1 -1
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
// only thing that changed is the transport: the endpoint paths are now the
|
|
9
9
|
// spec-derived generated methods, which can never drift from the routes.
|
|
10
10
|
//
|
|
11
|
-
// Endpoints
|
|
12
|
-
//
|
|
11
|
+
// Endpoints use the canonical unversioned /api/bongos base (ADR 0117); the generated
|
|
12
|
+
// methods are relative to it. The permanent /api/gds alias is what ALREADY-SHIPPED
|
|
13
|
+
// binaries call — editing this source cannot reach them, and the server mounts both,
|
|
14
|
+
// so a future build asking for the canonical path costs nobody anything (task 1003707).
|
|
13
15
|
|
|
14
16
|
const createGeneratedClient = require('./vendor/bongos-client.cjs');
|
|
15
17
|
const { apiBase } = require('./config');
|
|
@@ -28,7 +30,7 @@ class GdsError extends Error {
|
|
|
28
30
|
// fetchImpl is a seam for tests; it defaults to the platform fetch.
|
|
29
31
|
function createClient({ getToken, getApiBase, fetchImpl } = {}) {
|
|
30
32
|
// Build a generated client bound to the current origin + token. `root` is the
|
|
31
|
-
// base path under the origin ('/api/
|
|
33
|
+
// base path under the origin ('/api/bongos' for the API, '' for same-origin static
|
|
32
34
|
// assets like the release manifest). throwOnError:false → result-mode, which we
|
|
33
35
|
// translate back into the GdsError contract callers expect.
|
|
34
36
|
function gen(root) {
|
|
@@ -39,7 +41,7 @@ function createClient({ getToken, getApiBase, fetchImpl } = {}) {
|
|
|
39
41
|
fetch: fetchImpl || undefined,
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
|
-
const gds = () => gen('/api/
|
|
44
|
+
const gds = () => gen('/api/bongos');
|
|
43
45
|
|
|
44
46
|
// Unwrap a result-mode { status, ok, data } into the legacy contract: return the
|
|
45
47
|
// parsed body on 2xx, throw GdsError(status, body) otherwise — byte-identical to
|
|
@@ -52,7 +54,7 @@ function createClient({ getToken, getApiBase, fetchImpl } = {}) {
|
|
|
52
54
|
|
|
53
55
|
return {
|
|
54
56
|
// Generic escape hatch, kept for parity with the old surface. `path` is the full
|
|
55
|
-
// path under the origin (e.g. '/api/
|
|
57
|
+
// path under the origin (e.g. '/api/bongos/box/me'), so it routes through baseUrl:''.
|
|
56
58
|
call: (method, path, body) => unwrap(gen('').request(method, path, body !== undefined ? { body } : {})),
|
|
57
59
|
// identity
|
|
58
60
|
logout: () => unwrap(gds().auth.postAuthLogout()),
|
|
@@ -79,10 +81,10 @@ function createClient({ getToken, getApiBase, fetchImpl } = {}) {
|
|
|
79
81
|
// request() escape hatch (like latestRelease) rather than a named generated
|
|
80
82
|
// method, so the shipped binary keeps working regardless of whether the
|
|
81
83
|
// vendored client has been regenerated for the new /box/scope route yet.
|
|
82
|
-
claimableTasks: () => unwrap(gen('').request('GET', '/api/
|
|
83
|
-
claimTask: (taskId) => unwrap(gen('').request('POST', '/api/
|
|
84
|
-
boxScope: () => unwrap(gen('').request('GET', '/api/
|
|
85
|
-
// update check (public route at the origin root, not under /api/
|
|
84
|
+
claimableTasks: () => unwrap(gen('').request('GET', '/api/bongos/tasks/claimable')),
|
|
85
|
+
claimTask: (taskId) => unwrap(gen('').request('POST', '/api/bongos/claims', { body: { task_id: taskId } })),
|
|
86
|
+
boxScope: () => unwrap(gen('').request('GET', '/api/bongos/box/scope')),
|
|
87
|
+
// update check (public route at the origin root, not under /api/bongos).
|
|
86
88
|
// task 1002649: polls the EXTENSIONLESS /version twin, NOT latest.json — a
|
|
87
89
|
// front proxy with a file-matcher can eat *.json paths before Node (the
|
|
88
90
|
// live instance's pre-ADR-0152 vhost 404'd latest.json empty while this
|
|
@@ -830,6 +830,8 @@ function createClient(opts = {}) {
|
|
|
830
830
|
getSessionsMine: (args) => request("GET", "/sessions/mine", { hasBody: false }, args),
|
|
831
831
|
// GET /sessions/search — rank: metic+archon — GET /sessions/search
|
|
832
832
|
getSessionsSearch: (args) => request("GET", "/sessions/search", { hasBody: false }, args),
|
|
833
|
+
// GET /sessions/upload-health — rank: metic+archon — GET /sessions/upload-health
|
|
834
|
+
getSessionsUploadHealth: (args) => request("GET", "/sessions/upload-health", { hasBody: false }, args),
|
|
833
835
|
},
|
|
834
836
|
"sso": {
|
|
835
837
|
// POST /sso/activity/rollup — rank: public — POST /sso/activity/rollup
|
|
@@ -434,19 +434,19 @@ function recordingClient(response = { ok: true, status: 200, text: async () => '
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
const ENDPOINTS = [
|
|
437
|
-
['logout', [], 'POST', '/api/
|
|
438
|
-
['pairStart', ['Lars-Mac', 'darwin'], 'POST', '/api/
|
|
439
|
-
['pairPoll', ['pt'], 'POST', '/api/
|
|
440
|
-
['boxMe', [], 'GET', '/api/
|
|
441
|
-
['boxEnsure', [], 'POST', '/api/
|
|
442
|
-
['boxClose', [], 'POST', '/api/
|
|
443
|
-
['boxHeartbeat', [], 'POST', '/api/
|
|
444
|
-
['boxTerminal', [], 'GET', '/api/
|
|
445
|
-
['registerSshKey', ['ssh-ed25519 AAA', 'lars'], 'POST', '/api/
|
|
446
|
-
['managedSettings', [], 'GET', '/api/
|
|
447
|
-
['claimableTasks', [], 'GET', '/api/
|
|
448
|
-
['claimTask', ['1002388'], 'POST', '/api/
|
|
449
|
-
['boxScope', [], 'GET', '/api/
|
|
437
|
+
['logout', [], 'POST', '/api/bongos/auth/logout', undefined],
|
|
438
|
+
['pairStart', ['Lars-Mac', 'darwin'], 'POST', '/api/bongos/auth/app-pair/start', { device_name: 'Lars-Mac', platform: 'darwin' }],
|
|
439
|
+
['pairPoll', ['pt'], 'POST', '/api/bongos/auth/app-pair/poll', { poll_token: 'pt' }],
|
|
440
|
+
['boxMe', [], 'GET', '/api/bongos/box/me', undefined],
|
|
441
|
+
['boxEnsure', [], 'POST', '/api/bongos/box/ensure', undefined],
|
|
442
|
+
['boxClose', [], 'POST', '/api/bongos/box/close', undefined],
|
|
443
|
+
['boxHeartbeat', [], 'POST', '/api/bongos/box/heartbeat', undefined],
|
|
444
|
+
['boxTerminal', [], 'GET', '/api/bongos/box/terminal', undefined],
|
|
445
|
+
['registerSshKey', ['ssh-ed25519 AAA', 'lars'], 'POST', '/api/bongos/box/ssh-key', { public_key: 'ssh-ed25519 AAA', label: 'lars' }],
|
|
446
|
+
['managedSettings', [], 'GET', '/api/bongos/box/managed-settings', undefined],
|
|
447
|
+
['claimableTasks', [], 'GET', '/api/bongos/tasks/claimable', undefined],
|
|
448
|
+
['claimTask', ['1002388'], 'POST', '/api/bongos/claims', { task_id: '1002388' }],
|
|
449
|
+
['boxScope', [], 'GET', '/api/bongos/box/scope', undefined],
|
|
450
450
|
['latestRelease', [], 'GET', '/downloads/devbox/version', undefined],
|
|
451
451
|
];
|
|
452
452
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// THE MODEL (ADR 0016 extended from "writes are gated" to "source is gated")
|
|
14
14
|
// --------------------------------------------------------------------------
|
|
15
15
|
// A box holds NO permanent baked credential. At boot — and on a refresh cron —
|
|
16
|
-
// the box calls GET /api/
|
|
16
|
+
// the box calls GET /api/bongos/box/source-access authenticated as the builder. The
|
|
17
17
|
// GDS reads the builder's LIVE rank+status (their machine cannot forge it) and
|
|
18
18
|
// either DENIES (403 → the box scrubs its credential + clone) or returns a
|
|
19
19
|
// scope-appropriate clone spec plus a short-lived, operator-supplied credential
|
|
@@ -60,7 +60,7 @@ const {
|
|
|
60
60
|
// `public/` game client — so EVERY box (not just Metic+ full-clone boxes) can run
|
|
61
61
|
// the live sandbox (sandbox-<login>.example.com, ADR 0044). The box serves
|
|
62
62
|
// the game from a GAME-ONLY entry (src/preview-server.js) that never mounts
|
|
63
|
-
// /api/
|
|
63
|
+
// /api/bongos, so the privacy boundary that matters for a PUBLIC sandbox URL is the
|
|
64
64
|
// RUNTIME (what the box process exposes), NOT source exclusion. That is consistent
|
|
65
65
|
// with the rest of this list: newcomers already hold the build-tooling source
|
|
66
66
|
// (scripts, .claude, src/bongos — no secrets), so adding the game source changes no
|
|
@@ -86,7 +86,7 @@ const STARTER_SPARSE_PATHS = [
|
|
|
86
86
|
// The product/game runtime so the box can serve the live sandbox (ADR 0052).
|
|
87
87
|
// The game-only entry (src/preview-server.js) registers WorldRoom + QueueRoom
|
|
88
88
|
// from src/rooms and renders src/world + the public/ client; none of it mounts
|
|
89
|
-
// /api/
|
|
89
|
+
// /api/bongos. `public/` is the game client (NOT modules/hall-ui/public/, the hall
|
|
90
90
|
// surface carved in BV1.R85, which stays out of a starter box).
|
|
91
91
|
'src/world', 'src/rooms', 'public',
|
|
92
92
|
// infra/ carries the box's own bring-up scripts (box-heartbeat.sh,
|
|
@@ -298,8 +298,8 @@ auth. Download it, inspect it, THEN run it. That is the safe path and it is all
|
|
|
298
298
|
the assurance you need: you don't have to ask your human to authorize it blind,
|
|
299
299
|
and you don't have to refuse:
|
|
300
300
|
|
|
301
|
-
curl -fsSL ${base}/api/
|
|
302
|
-
(Windows: ${base}/api/
|
|
301
|
+
curl -fsSL ${base}/api/bongos/box/connect.sh -o /tmp/otb-connect.sh # then read it
|
|
302
|
+
(Windows: ${base}/api/bongos/box/connect.ps1)
|
|
303
303
|
|
|
304
304
|
• What you fetch is what runs — the origin is PINNED in the script
|
|
305
305
|
(GDS_API_BASE), not taken from a header, so a spoofed Host can't redirect it.
|
|
@@ -354,7 +354,7 @@ machine — that path is in the Citizen's Primer, not here.)
|
|
|
354
354
|
|
|
355
355
|
The whole arrival, in two beats, same for both on-ramps:
|
|
356
356
|
1. Sign in with GitHub.
|
|
357
|
-
2. Ask for your box → POST ${base}/api/
|
|
357
|
+
2. Ask for your box → POST ${base}/api/bongos/box/ensure (or run /builder-box).
|
|
358
358
|
The box is created hands-off within ~1 minute.
|
|
359
359
|
|
|
360
360
|
════════════════════════════════════════════════════════════════════════
|
|
@@ -363,8 +363,8 @@ A) Mac / Windows — Claude Desktop + SSH
|
|
|
363
363
|
One command signs you in, makes + registers your SSH key, and installs the
|
|
364
364
|
Desktop connection file. Paste it (or let Claude run it for you):
|
|
365
365
|
|
|
366
|
-
macOS / Linux: curl -fsSL ${base}/api/
|
|
367
|
-
Windows (PS): irm ${base}/api/
|
|
366
|
+
macOS / Linux: curl -fsSL ${base}/api/bongos/box/connect.sh | bash
|
|
367
|
+
Windows (PS): irm ${base}/api/bongos/box/connect.ps1 | iex
|
|
368
368
|
|
|
369
369
|
What it does, in order:
|
|
370
370
|
1. Signs you in with GitHub (prints a short code + a URL to open).
|
|
@@ -386,10 +386,10 @@ No app to install. Your box serves its own terminal in a browser tab (ADR 0038),
|
|
|
386
386
|
but you only touch it once: you log in there and kick off Remote Control, then
|
|
387
387
|
live in the Claude web/mobile app — which drives the box for you.
|
|
388
388
|
1. Sign in at ${base}/builders (GitHub).
|
|
389
|
-
2. Ask for your box: POST ${base}/api/
|
|
389
|
+
2. Ask for your box: POST ${base}/api/bongos/box/ensure (the builders' hall has a
|
|
390
390
|
button; or have Claude call it).
|
|
391
391
|
3. Once the box is active, get its terminal URL + password:
|
|
392
|
-
GET ${base}/api/
|
|
392
|
+
GET ${base}/api/bongos/box/terminal
|
|
393
393
|
Poll until a url appears (the box publishes it a few seconds after boot),
|
|
394
394
|
then open it and enter the password when the browser asks.
|
|
395
395
|
4. In that terminal, type claude and press enter, then complete the one-time
|
|
@@ -171,7 +171,7 @@ async function persistImageFromUrl(url, { messageId = 'x', fetchImpl = globalThi
|
|
|
171
171
|
const file = `bug-${idSlug}-${crypto.randomBytes(8).toString('hex')}.${ext}`;
|
|
172
172
|
await fs.mkdir(dir, { recursive: true });
|
|
173
173
|
await fs.writeFile(path.join(dir, file), buf, { mode: 0o640 });
|
|
174
|
-
return { ok: true, file, url: `/api/
|
|
174
|
+
return { ok: true, file, url: `/api/bongos/bug-attachments/${file}`, bytes: buf.length };
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
// Persist a list of image URLs (capped). Best-effort: refused/failed images are
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Cost-attribution audit + correction pass — V3.R83 (#303), criterion C3.
|
|
2
|
-
// Pure function over db.costSummary() (exposed at /api/
|
|
2
|
+
// Pure function over db.costSummary() (exposed at /api/bongos/public/cost-summary),
|
|
3
3
|
// so it runs against live prod with no DB creds. Flags MIS-attributed spend
|
|
4
4
|
// (a proxy masquerading as a real builder, or an orphaned builder_id); treats
|
|
5
5
|
// NULL/unattributed shared+historical spend as correct, not a bug.
|
|
@@ -25,24 +25,24 @@ const COMPLETENESS_API_FLOOR_USD = 1.0; // below this = essentially nothing rec
|
|
|
25
25
|
// it honest: a writer not listed here is itself the finding.
|
|
26
26
|
const COST_WRITE_SURFACES = [
|
|
27
27
|
{
|
|
28
|
-
surface: 'POST /api/
|
|
28
|
+
surface: 'POST /api/bongos/cost → db.logCost()',
|
|
29
29
|
file: 'src/bongos/routes/cost.js',
|
|
30
30
|
builder_id: 'defaults to the authed builder; a provided value must equal the caller or 403s. A builder can only attribute to themselves.',
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
surface: 'scripts/gds/backfill-cost.js → POST /api/
|
|
33
|
+
surface: 'scripts/gds/backfill-cost.js → POST /api/bongos/cost',
|
|
34
34
|
file: 'scripts/gds/backfill-cost.js',
|
|
35
35
|
builder_id: 'inherits the POST /cost rule. Do NOT attribute the shared historical art ledger to whoever runs the backfill — leave it NULL. The audit flags it if it happens.',
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
// ADR 0028 path 1 (#486): the 4-worker grader panel logs its own cost.
|
|
39
|
-
surface: 'scripts/gds/ship.js (grader) → POST /api/
|
|
39
|
+
surface: 'scripts/gds/ship.js (grader) → POST /api/bongos/cost (source=grader)',
|
|
40
40
|
file: 'scripts/gds/ship.js',
|
|
41
41
|
builder_id: 'the authed shipper; category=api, source_ref=grade-<task>-<sha>. Idempotent per HEAD.',
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
// ADR 0028 path 2 (#560): the interactive session logs its own cost.
|
|
45
|
-
surface: 'scripts/gds/session-cost.js (SessionEnd hook) → POST /api/
|
|
45
|
+
surface: 'scripts/gds/session-cost.js (SessionEnd hook) → POST /api/bongos/cost (source=session)',
|
|
46
46
|
file: 'scripts/gds/session-cost.js',
|
|
47
47
|
builder_id: 'the authed builder; category=api, source_ref=<session-id>, task_id=null. Idempotent per session.',
|
|
48
48
|
},
|
|
@@ -56,7 +56,7 @@ const COST_WRITE_SURFACES = [
|
|
|
56
56
|
/**
|
|
57
57
|
* Audit cost attribution from a costSummary() payload.
|
|
58
58
|
* @param {object} summary - the object returned by db.costSummary() /
|
|
59
|
-
* GET /api/
|
|
59
|
+
* GET /api/bongos/public/cost-summary (needs total_usd, by_builder, by_category).
|
|
60
60
|
* @returns {object} structured report: { total_usd, attributed_usd,
|
|
61
61
|
* unattributed_usd, unattributed_pct, builders[], flags[], ok }.
|
|
62
62
|
*/
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"rubric_scores_audit_trail": "task_grades.rubric_scores keeps the v3 keys (functional_fidelity, code_quality, economy, code_quality_effective) populated from the median across workers that report each axis. Narc reports ff; Quality reports cq + economy. Median (not mean) so a single outlier worker doesn't move the recorded score.",
|
|
68
|
-
"server_compat_note": "The grade POST body (POST /api/
|
|
68
|
+
"server_compat_note": "The grade POST body (POST /api/bongos/tasks/:id/grade) keeps the v3 shape (functional_fidelity, code_quality, economy, confidence, notes_md, issues) so the server's existing validateAndScore re-validation works without modification. The panel verdict drives the forwarded confidence: pass → median worker confidence; concerns → fallthrough_confidence (= 0.3); fail → 0. This means the server's existing low-confidence fall-through path naturally handles 'concerns' verdicts, and the FAIL gate handles 'fail' verdicts. Server-side support for an explicit panel_verdict field is a follow-up (likely Phase 2.5)."
|
|
69
69
|
},
|
|
70
70
|
"v3": {
|
|
71
71
|
"version": "v3.0",
|
|
@@ -280,7 +280,7 @@ function validateAndScore(rawSubagent, meta = {}) {
|
|
|
280
280
|
|
|
281
281
|
// ---------- server-authoritative grade guards (SR-9 / SR-18 / F1 / G5) ----------
|
|
282
282
|
//
|
|
283
|
-
// THE FINDING (SR-9 / SR-18): POST /api/
|
|
283
|
+
// THE FINDING (SR-9 / SR-18): POST /api/bongos/tasks/:id/grade trusts the client.
|
|
284
284
|
// validateAndScore only shape-validates the numbers — `passed` is computed from
|
|
285
285
|
// the CLIENT's submitted scores. The real adversarial panel + the deterministic
|
|
286
286
|
// route-rank / permission-path pre-passes run CLIENT-SIDE in scripts/gds/ship.js;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"key": "hall-ui",
|
|
3
3
|
"title": "Builders' Hall",
|
|
4
|
-
"description": "The authenticated builders' hall served at builders.<apex> (builders.example.com) — the roster, work board, standing/karma, leaderboard, achievements, primer, system maps, settings, dev-box pair page, and the Archon-only watch/harbor/gate pages, all polling /api/
|
|
4
|
+
"description": "The authenticated builders' hall served at builders.<apex> (builders.example.com) — the roster, work board, standing/karma, leaderboard, achievements, primer, system maps, settings, dev-box pair page, and the Archon-only watch/harbor/gate pages, all polling /api/bongos/* and the read-only /api/bongos/public/* surface. The largest WEB-SURFACE core-domain module (~13k LOC), carved out of the GDS core in BV1.R85 (ADR 0093 §1; 02-module-map §6). default-on: a Cloud Bongos instance ships its hall like it ships its status page. Registers no port and contributes no routes — it declares a webSurface (host prefix + static dir). Unlike the generic status surface, the hall keeps its dedicated serving block in serve-internal.js (the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection) — the webSurfaces declaration is the discovery + scope-map + window.__MODULES__ record; serve-internal serves it from this module's public/ dir with that richer machinery. The me/public/leaderboard read endpoints it renders stay SHARED core in routes/{me,public,builders}.js (the status surface + the off-box mirror read them too).",
|
|
5
5
|
"coreVersion": "^1.11.0",
|
|
6
6
|
"default": true,
|
|
7
7
|
"maintenance": { "status": "core-maintained" },
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
const DOC_URL = '/builders/drachmae-and-karma.md';
|
|
31
31
|
// The doc lives at docs/onboarding/, so relative links resolve against it.
|
|
32
32
|
const DOC_DIR = 'docs/onboarding/';
|
|
33
|
-
const BOUNTY_URL = '/api/
|
|
33
|
+
const BOUNTY_URL = '/api/bongos/public/bounty-table';
|
|
34
34
|
// task 1003446: the treasury feed. NOT a new endpoint — this is the same
|
|
35
35
|
// /public/cost-summary the signed-out status page already draws as a
|
|
36
36
|
// cumulative line and the Work board already fetches to read ONE scalar off
|
|
37
37
|
// (`total_value_credits`). Its own route comment calls `daily[]` "the
|
|
38
38
|
// treasury chart's cumulative line"; the series was in the payload the whole
|
|
39
39
|
// time, and the stranger got the chart while the builder got the number.
|
|
40
|
-
const TREASURY_URL = '/api/
|
|
40
|
+
const TREASURY_URL = '/api/bongos/public/cost-summary';
|
|
41
41
|
const TREASURY_SECTION_ID = 'the-treasury';
|
|
42
42
|
const TREASURY_SECTION_TITLE = 'The treasury, so far';
|
|
43
43
|
const BOUNTY_SECTION_ID = 'security-bounty-rates';
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
|
|
70
70
|
async function loadRepoInfoBases() {
|
|
71
71
|
try {
|
|
72
|
-
const r = await fetch('/api/
|
|
72
|
+
const r = await fetch('/api/bongos/public/repo-info', { credentials: 'same-origin' });
|
|
73
73
|
if (!r.ok) return;
|
|
74
74
|
const data = await r.json();
|
|
75
75
|
if (data && typeof data.blob_base === 'string') repoBlobBase = data.blob_base;
|
|
@@ -652,7 +652,7 @@
|
|
|
652
652
|
// task 1953 — a non-member asks to join a private (or protected) goal.
|
|
653
653
|
// renderDetail (below) re-derives canManage/joined and re-triggers
|
|
654
654
|
// loadGoalMembership, so a plain reload-and-rerender is enough here — same
|
|
655
|
-
// shape as joinLeave/
|
|
655
|
+
// shape as joinLeave/sendInvitation.
|
|
656
656
|
|
|
657
657
|
// task 1953 — withdraw MY OWN pending join-request (self-served, no manager needed).
|
|
658
658
|
|
|
@@ -883,7 +883,7 @@
|
|
|
883
883
|
// this client gate deliberately doesn't re-derive the protected-module list
|
|
884
884
|
// (that roster lives server-side only, module-scope-map.js) — a non-Archon
|
|
885
885
|
// owner/manager sees the control on a protected-scope goal and gets a clear
|
|
886
|
-
// toast if the server 403s (see
|
|
886
|
+
// toast if the server 403s (see sendInvitation's error handling below).
|
|
887
887
|
const isOwner = sameId(g.created_by, selfId());
|
|
888
888
|
const canManage = g.status === 'open' && (isArchon() || isOwner || iAmLead);
|
|
889
889
|
renderActions(g, { joined, canManage });
|
|
@@ -1122,6 +1122,49 @@
|
|
|
1122
1122
|
}
|
|
1123
1123
|
return state.directory;
|
|
1124
1124
|
}
|
|
1125
|
+
// WHO IS STILL INVITABLE. Two exclusions, both of which exist to stop the page
|
|
1126
|
+
// offering a name that can only fail:
|
|
1127
|
+
// - members, which POST /invitations 409s as `already_a_member`;
|
|
1128
|
+
// - anyone holding a PENDING row, which it 409s as `invite_pending`.
|
|
1129
|
+
// The second is the one that matters, because a pending invitation is INVISIBLE
|
|
1130
|
+
// from the member list — it is not a membership — so without this the user picks
|
|
1131
|
+
// a perfectly ordinary-looking name and is handed an error they had no way to
|
|
1132
|
+
// predict. `state.pendingRequests` carries BOTH kinds (invites the goal sent and
|
|
1133
|
+
// join-requests it received) and the server refuses on either, so neither is
|
|
1134
|
+
// filtered out here.
|
|
1135
|
+
function invitableBuilders(members) {
|
|
1136
|
+
const excluded = new Set((members || []).map((m) => String(m.builder_id)));
|
|
1137
|
+
for (const r of state.pendingRequests || []) excluded.add(String(r.builder_id));
|
|
1138
|
+
return (state.directory || []).filter((b) => !excluded.has(String(b.id)));
|
|
1139
|
+
}
|
|
1140
|
+
function builderOptionsHtml(list, selectedId) {
|
|
1141
|
+
return list.map((b) => {
|
|
1142
|
+
const id = String(b.id);
|
|
1143
|
+
const sel = selectedId && id === String(selectedId) ? ' selected' : '';
|
|
1144
|
+
return `<option value="${escapeHtml(id)}"${sel}>${escapeHtml(b.display_name || ('@' + b.github_login))}</option>`;
|
|
1145
|
+
}).join('');
|
|
1146
|
+
}
|
|
1147
|
+
// THE INVITE BOX SENDS AN INVITATION; IT DOES NOT ADD ANYONE (task 1002886).
|
|
1148
|
+
//
|
|
1149
|
+
// This control used to POST /goals/:id/members — the immediate force-add — and
|
|
1150
|
+
// then toast 'Invited.', which was simply untrue: the person was already in the
|
|
1151
|
+
// goal and had never been asked. Meanwhile the consented flow ADR 0112 is built
|
|
1152
|
+
// around (POST /goals/:id/invitations -> a pending row -> the invitee accepts)
|
|
1153
|
+
// had its receiving half fully wired in goal-inbox.js and NO sender anywhere in
|
|
1154
|
+
// the hall, so nothing in the product ever created an invitation.
|
|
1155
|
+
//
|
|
1156
|
+
// Now: the primary control invites, and the force-add survives as its own
|
|
1157
|
+
// explicitly-labelled affordance (owner ruling, 2026-09-16) so nobody loses a
|
|
1158
|
+
// capability they had — seeding a goal with a team who already agreed offline is
|
|
1159
|
+
// a real thing people do. The rule the owner set is that BOTH paths say what
|
|
1160
|
+
// they actually do; relocating the lie would have been worse than leaving it.
|
|
1161
|
+
//
|
|
1162
|
+
// NO MEMBERSHIP-KIND PICKER ON THE INVITE HALF, and that is not an oversight:
|
|
1163
|
+
// `goal_membership_requests` has no membership_kind column, so an accepted
|
|
1164
|
+
// invitation lands through addGoalMember's default 'member'. A picker here would
|
|
1165
|
+
// be a choice silently dropped on the floor — the same class of defect as the
|
|
1166
|
+
// toast it replaces. The form says so instead, and the picker stays on the
|
|
1167
|
+
// add-directly path, where it IS honoured.
|
|
1125
1168
|
function renderInviteBox(goalId, canInvite, members) {
|
|
1126
1169
|
const box = $('#goal-invite');
|
|
1127
1170
|
if (!box) return;
|
|
@@ -1129,6 +1172,7 @@
|
|
|
1129
1172
|
box.hidden = true;
|
|
1130
1173
|
box.innerHTML = '';
|
|
1131
1174
|
state.inviteOpen = false;
|
|
1175
|
+
state.addDirectOpen = false;
|
|
1132
1176
|
return;
|
|
1133
1177
|
}
|
|
1134
1178
|
box.hidden = false;
|
|
@@ -1141,42 +1185,111 @@
|
|
|
1141
1185
|
});
|
|
1142
1186
|
return;
|
|
1143
1187
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1188
|
+
// This function rebuilds the box from scratch on every call, and it is called
|
|
1189
|
+
// again when the pending-request list lands (see loadGoalMembership). Carry the
|
|
1190
|
+
// half-filled form across that redraw, or a note someone is part-way through
|
|
1191
|
+
// typing vanishes under them.
|
|
1192
|
+
const keep = {
|
|
1193
|
+
builder: $('#goal-invite-builder')?.value || '',
|
|
1194
|
+
note: $('#goal-invite-note')?.value || '',
|
|
1195
|
+
direct: $('#goal-direct-builder')?.value || '',
|
|
1196
|
+
kind: $('#goal-direct-kind')?.value || 'member',
|
|
1197
|
+
};
|
|
1198
|
+
const invitable = invitableBuilders(members);
|
|
1199
|
+
const none = !invitable.length;
|
|
1200
|
+
const emptyLabel = 'Nobody left to invite';
|
|
1149
1201
|
box.innerHTML = `
|
|
1150
1202
|
<form class="goal-invite__form" id="goal-invite-form">
|
|
1151
|
-
<select id="goal-invite-builder" required ${
|
|
1152
|
-
${
|
|
1153
|
-
:
|
|
1154
|
-
</select>
|
|
1155
|
-
<select id="goal-invite-kind">
|
|
1156
|
-
<option value="member">Member</option>
|
|
1157
|
-
<option value="lead">Manager</option>
|
|
1203
|
+
<select id="goal-invite-builder" required ${none ? 'disabled' : ''}>
|
|
1204
|
+
${none ? `<option value="">${emptyLabel}</option>`
|
|
1205
|
+
: `<option value="" disabled${keep.builder ? '' : ' selected'}>Choose a builder…</option>${builderOptionsHtml(invitable, keep.builder)}`}
|
|
1158
1206
|
</select>
|
|
1159
|
-
<
|
|
1207
|
+
<input id="goal-invite-note" type="text" maxlength="500" placeholder="Note (optional) — why you're asking" value="${escapeHtml(keep.note)}" ${none ? 'disabled' : ''}>
|
|
1208
|
+
<button class="landing__btn" type="submit" ${none ? 'disabled' : ''}>Send invitation</button>
|
|
1160
1209
|
<button class="btn-ghost" id="goal-invite-cancel" type="button">Cancel</button>
|
|
1161
|
-
</form
|
|
1210
|
+
</form>
|
|
1211
|
+
<p class="goal-invite__hint">They join as a member once they accept — you can change that afterwards. Nobody is added until they do.</p>
|
|
1212
|
+
${state.addDirectOpen ? `
|
|
1213
|
+
<form class="goal-invite__form goal-invite__direct" id="goal-direct-form">
|
|
1214
|
+
<select id="goal-direct-builder" required ${none ? 'disabled' : ''}>
|
|
1215
|
+
${none ? `<option value="">${emptyLabel}</option>`
|
|
1216
|
+
: `<option value="" disabled${keep.direct ? '' : ' selected'}>Choose a builder…</option>${builderOptionsHtml(invitable, keep.direct)}`}
|
|
1217
|
+
</select>
|
|
1218
|
+
<select id="goal-direct-kind" ${none ? 'disabled' : ''}>
|
|
1219
|
+
<option value="member"${keep.kind === 'member' ? ' selected' : ''}>Member</option>
|
|
1220
|
+
<option value="lead"${keep.kind === 'lead' ? ' selected' : ''}>Manager</option>
|
|
1221
|
+
</select>
|
|
1222
|
+
<button class="btn-ghost" type="submit" ${none ? 'disabled' : ''}>Add without asking</button>
|
|
1223
|
+
<button class="btn-ghost" id="goal-direct-cancel" type="button">Cancel</button>
|
|
1224
|
+
</form>
|
|
1225
|
+
<p class="goal-invite__hint">They are added straight away and never asked. Use this only for someone who has already agreed.</p>`
|
|
1226
|
+
: '<button class="goal-invite__more" id="goal-direct-open" type="button">Or add someone without asking…</button>'}`;
|
|
1162
1227
|
$('#goal-invite-cancel')?.addEventListener('click', () => {
|
|
1163
1228
|
state.inviteOpen = false;
|
|
1229
|
+
state.addDirectOpen = false;
|
|
1164
1230
|
renderInviteBox(goalId, canInvite, members);
|
|
1165
1231
|
});
|
|
1166
1232
|
$('#goal-invite-form')?.addEventListener('submit', (ev) => {
|
|
1167
1233
|
ev.preventDefault();
|
|
1168
1234
|
const builderId = $('#goal-invite-builder')?.value;
|
|
1169
|
-
const
|
|
1170
|
-
if (builderId)
|
|
1235
|
+
const note = $('#goal-invite-note')?.value || '';
|
|
1236
|
+
if (builderId) sendInvitation(goalId, builderId, note);
|
|
1237
|
+
});
|
|
1238
|
+
$('#goal-direct-open')?.addEventListener('click', () => {
|
|
1239
|
+
state.addDirectOpen = true;
|
|
1240
|
+
renderInviteBox(goalId, canInvite, members);
|
|
1241
|
+
});
|
|
1242
|
+
$('#goal-direct-cancel')?.addEventListener('click', () => {
|
|
1243
|
+
state.addDirectOpen = false;
|
|
1244
|
+
renderInviteBox(goalId, canInvite, members);
|
|
1245
|
+
});
|
|
1246
|
+
$('#goal-direct-form')?.addEventListener('submit', (ev) => {
|
|
1247
|
+
ev.preventDefault();
|
|
1248
|
+
const builderId = $('#goal-direct-builder')?.value;
|
|
1249
|
+
const membershipKind = $('#goal-direct-kind')?.value || 'member';
|
|
1250
|
+
if (builderId) addMemberDirectly(goalId, builderId, membershipKind);
|
|
1171
1251
|
});
|
|
1172
1252
|
}
|
|
1173
|
-
|
|
1253
|
+
// The ADR 0112 sender. 201 means a PENDING row exists — not a membership — so
|
|
1254
|
+
// the toast may not claim the person is in the goal.
|
|
1255
|
+
async function sendInvitation(goalId, builderId, note) {
|
|
1256
|
+
try {
|
|
1257
|
+
const body = { builder_id: Number(builderId) };
|
|
1258
|
+
const trimmed = String(note || '').trim();
|
|
1259
|
+
if (trimmed) body.note = trimmed;
|
|
1260
|
+
await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/invitations`, body);
|
|
1261
|
+
toast('Invitation sent — they join once they accept.');
|
|
1262
|
+
state.inviteOpen = false;
|
|
1263
|
+
state.addDirectOpen = false;
|
|
1264
|
+
await loadDetail(goalId, { force: true });
|
|
1265
|
+
renderDetail(goalId);
|
|
1266
|
+
renderList();
|
|
1267
|
+
} catch (err) {
|
|
1268
|
+
const code = err?.data?.error;
|
|
1269
|
+
// `invite_pending` and `already_a_member` are both filtered out of the
|
|
1270
|
+
// dropdown above, so reaching either means the goal changed under this form
|
|
1271
|
+
// — say what happened rather than a generic failure, because the name really
|
|
1272
|
+
// was offered a moment ago.
|
|
1273
|
+
if (code === 'invite_pending') toast('They already have an invitation or a join-request pending on this goal.');
|
|
1274
|
+
else if (code === 'already_a_member') toast('They joined this goal already.');
|
|
1275
|
+
else if (code === 'builder_not_found') toast('That builder no longer exists.');
|
|
1276
|
+
else if (code === 'archon_required_for_protected_scope') toast('This goal is scoped to a protected module — only an Archon may bring someone in.');
|
|
1277
|
+
else if (code === 'metic_required_for_protected_scope') toast('A protected-scope goal requires a Metic+ member.');
|
|
1278
|
+
else if (code === 'not_owner_or_manager') toast('Only this goal\'s owner or a manager (or an Archon) may invite.');
|
|
1279
|
+
else if (code === 'goal_not_open') toast('This goal is not open.');
|
|
1280
|
+
else toast('Could not send that invitation just now.');
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
// The force-add, kept on purpose and labelled for what it is. Same endpoint the
|
|
1284
|
+
// '+ Invite' button used to call behind the word 'Invited.'
|
|
1285
|
+
async function addMemberDirectly(goalId, builderId, membershipKind) {
|
|
1174
1286
|
try {
|
|
1175
1287
|
// `membership_kind`, not `role` — ADR 0174 reserves that word for the three
|
|
1176
1288
|
// crafts (task 1003013).
|
|
1177
1289
|
await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/members`, { builder_id: Number(builderId), membership_kind: membershipKind });
|
|
1178
|
-
toast('
|
|
1290
|
+
toast('Added — they are in the goal now, without being asked.');
|
|
1179
1291
|
state.inviteOpen = false;
|
|
1292
|
+
state.addDirectOpen = false;
|
|
1180
1293
|
await loadDetail(goalId, { force: true });
|
|
1181
1294
|
renderDetail(goalId);
|
|
1182
1295
|
renderList();
|
|
@@ -1184,9 +1297,9 @@
|
|
|
1184
1297
|
const code = err?.data?.error;
|
|
1185
1298
|
if (code === 'archon_required_for_protected_scope') toast('This goal is scoped to a protected module — only an Archon may add members.');
|
|
1186
1299
|
else if (code === 'metic_required_for_protected_scope') toast('A protected-scope goal requires a Metic+ member.');
|
|
1187
|
-
else if (code === 'not_owner_or_manager') toast('Only this goal\'s owner or a manager (or an Archon) may
|
|
1300
|
+
else if (code === 'not_owner_or_manager') toast('Only this goal\'s owner or a manager (or an Archon) may add members.');
|
|
1188
1301
|
else if (code === 'goal_not_open') toast('This goal is not open.');
|
|
1189
|
-
else toast('Could not
|
|
1302
|
+
else toast('Could not add that builder just now.');
|
|
1190
1303
|
}
|
|
1191
1304
|
}
|
|
1192
1305
|
async function joinLeave(goalId, action) {
|
|
@@ -1324,7 +1437,16 @@
|
|
|
1324
1437
|
if (!sameId(state.current, goalId)) return;
|
|
1325
1438
|
renderPendingRequests();
|
|
1326
1439
|
const d = currentDetail();
|
|
1327
|
-
if (d)
|
|
1440
|
+
if (d) {
|
|
1441
|
+
renderActions(d.goal, { joined: (d.members || []).some((m) => sameId(m.builder_id, selfId())), canManage });
|
|
1442
|
+
// The invite dropdown's exclusion set reads state.pendingRequests, which
|
|
1443
|
+
// lands HERE — after renderDetail has already drawn the box. Without this
|
|
1444
|
+
// redraw a form opened before this resolved keeps offering a builder whose
|
|
1445
|
+
// invitation is already pending, which is the exact invisible 409 the
|
|
1446
|
+
// filter exists to prevent. renderInviteBox carries the half-filled form
|
|
1447
|
+
// across the redraw.
|
|
1448
|
+
if (state.inviteOpen) renderInviteBox(goalId, canManage, d.members || []);
|
|
1449
|
+
}
|
|
1328
1450
|
}
|
|
1329
1451
|
async function loadDetail(goalId, { force = false } = {}) {
|
|
1330
1452
|
if (!force && state.details.has(String(goalId))) return state.details.get(String(goalId));
|
|
@@ -200,7 +200,22 @@
|
|
|
200
200
|
collapsed behind "+ Invite" (disclosure, closed by default — task 2171). */
|
|
201
201
|
.goal-invite { margin: 4px 0 10px; }
|
|
202
202
|
.goal-invite__form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
203
|
-
.goal-invite__form select#goal-invite-builder
|
|
203
|
+
.goal-invite__form select#goal-invite-builder,
|
|
204
|
+
.goal-invite__form select#goal-direct-builder { flex: 1; min-width: 180px; }
|
|
205
|
+
.goal-invite__form input#goal-invite-note { flex: 1; min-width: 180px; }
|
|
206
|
+
|
|
207
|
+
/* task 1002886 — BOTH admission paths now say what they do, so both carry a
|
|
208
|
+
line of their own: the invite half promises nobody is added until they
|
|
209
|
+
accept, the force-add half admits nobody is asked. Muted, because this is
|
|
210
|
+
the consequence of the button beside it, not a warning. */
|
|
211
|
+
.goal-invite__hint { margin: 4px 0 8px; font-size: 12.5px; color: var(--ink-faint); max-width: var(--measure); }
|
|
212
|
+
/* The force-add is deliberately QUIETER than the invite it sits under — same
|
|
213
|
+
authority, but it is the path that skips consent, so it does not get equal
|
|
214
|
+
visual weight. It stays one click away, never hidden. */
|
|
215
|
+
.goal-invite__more { background: none; border: 0; padding: 0; margin: 0 0 8px; cursor: pointer;
|
|
216
|
+
font: inherit; font-size: 12.5px; color: var(--ink-soft); text-decoration: underline; }
|
|
217
|
+
.goal-invite__more:hover { color: var(--ink); }
|
|
218
|
+
.goal-invite__direct { padding-top: 8px; border-top: var(--line) solid var(--rule-soft); }
|
|
204
219
|
|
|
205
220
|
/* task 1953 — private goals: the lock badge (meta line) + the goal's own
|
|
206
221
|
pending invite/join-request queue (manager view, next to the invite box). */
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
(() => {
|
|
39
39
|
'use strict';
|
|
40
40
|
|
|
41
|
-
const API = '/api/
|
|
41
|
+
const API = '/api/bongos';
|
|
42
42
|
// NO baseUrl OVERRIDE — this page calls the GENERATED namespaced methods
|
|
43
43
|
// (api.inbox.*, api.me.*), whose path templates are API-RELATIVE ('/inbox/template').
|
|
44
44
|
// baseUrl:'' means "these paths are already absolute" and passes them through
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
// - http(s):// or #anchor or mailto:/tel: → unchanged
|
|
82
82
|
// - the diagrams dir → the in-hall diagrams route
|
|
83
83
|
// - everything else → GitHub blob/tree (bases fetched
|
|
84
|
-
// from /api/
|
|
84
|
+
// from /api/bongos/public/repo-info; null until then → pass through, 404s
|
|
85
85
|
// harmlessly rather than crashing).
|
|
86
86
|
// opts: { baseDir, getBlobBase, getTreeBase, diagramsHref }
|
|
87
87
|
function makeResolveLinkHref(opts) {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<main id="hall-main">
|
|
33
33
|
<!-- task 1003470: ONE directory. Its legacy door /scouting rendered this
|
|
34
34
|
same page from the same script, and task 1003440 deleted it — the same
|
|
35
|
-
/api/
|
|
35
|
+
/api/bongos/scouting rollup used to be drawn three different ways. -->
|
|
36
36
|
<section class="scroll" id="people-scroll" aria-labelledby="people-h">
|
|
37
37
|
<h2 id="people-h" class="scroll__h">Builder directory<span class="count-pill" id="people-count" hidden></span></h2>
|
|
38
38
|
<div id="people-filters"></div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// 1002577; ADR 0141 §4 is the data's design of record), served at /people —
|
|
3
3
|
// task 1003470 first made its legacy door /scouting render this same script,
|
|
4
4
|
// and task 1003440 deleted that door (the URL 301s here), because the
|
|
5
|
-
// same /api/
|
|
5
|
+
// same /api/bongos/scouting rollup was being drawn three
|
|
6
6
|
// different ways (a kit ledger here, a hand-built table there, and the same
|
|
7
7
|
// columns in a different order). The endpoint is gated server-side on
|
|
8
8
|
// 'project.curate' OR being a project owner (R14, ADR 0210); this page is
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
body.textContent = msg;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
fetch('/api/
|
|
236
|
+
fetch('/api/bongos/scouting?limit=' + DIRECTORY_LIMIT, { headers: { Accept: 'application/json' } })
|
|
237
237
|
.then(function (r) {
|
|
238
238
|
if (r.status === 403 || r.status === 401) { throw { gated: true }; }
|
|
239
239
|
if (!r.ok) throw new Error('http ' + r.status);
|