@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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// inlines the five onboarding diagrams at matching sections, builds a TOC from
|
|
6
6
|
// h2s with a scrollspy, drives a reading-progress hairline, and soft-gates the
|
|
7
7
|
// footer ack button until the reader has scrolled past ~90% of the article.
|
|
8
|
-
// Acking POSTs to /api/
|
|
8
|
+
// Acking POSTs to /api/bongos/builders/me/onboarding/primer-read (same endpoint
|
|
9
9
|
// /builder-primer-read uses).
|
|
10
10
|
//
|
|
11
11
|
// ONE PARSER, TWO READERS (task 1003306). This file used to carry its own copy
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
(() => {
|
|
21
21
|
'use strict';
|
|
22
22
|
|
|
23
|
-
const API = '/api/
|
|
23
|
+
const API = '/api/bongos';
|
|
24
24
|
const ACK_SCROLL_THRESHOLD = 0.90; // 90% of article scrolled → button enables
|
|
25
25
|
// The primer markdown lives at docs/onboarding/primer.md, so its relative
|
|
26
26
|
// links resolve against docs/onboarding/.
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
// URLs under these bases so the rendered links go somewhere useful instead of
|
|
31
31
|
// 404-ing under /builders/*. The bases are NOT hardcoded here — that would
|
|
32
32
|
// violate the V3.R12 (#236) authorship-attribution audit. They are fetched
|
|
33
|
-
// from /api/
|
|
33
|
+
// from /api/bongos/public/repo-info at boot; the server derives owner/repo at
|
|
34
34
|
// process start from `git remote get-url origin` (src/bongos/repo-info.js).
|
|
35
35
|
// When they are unavailable the rewriter degrades gracefully: repo-relative
|
|
36
36
|
// hrefs pass through unchanged and 404 instead of crashing the page.
|
|
@@ -372,7 +372,7 @@
|
|
|
372
372
|
// gracefully degrades when the bases are null.
|
|
373
373
|
async function loadRepoInfoBases() {
|
|
374
374
|
try {
|
|
375
|
-
const r = await fetch('/api/
|
|
375
|
+
const r = await fetch('/api/bongos/public/repo-info', { credentials: 'same-origin' });
|
|
376
376
|
if (!r.ok) return;
|
|
377
377
|
const data = await r.json();
|
|
378
378
|
if (data && typeof data.blob_base === 'string') repoBlobBase = data.blob_base;
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
rerender();
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
fetch('/api/
|
|
220
|
+
fetch('/api/bongos/leaderboard', { headers: { Accept: 'application/json' } })
|
|
221
221
|
.then(function (r) {
|
|
222
222
|
if (!r.ok) throw new Error('http ' + r.status);
|
|
223
223
|
return r.json();
|
|
@@ -263,7 +263,7 @@
|
|
|
263
263
|
// modules/hall-ui/CLAUDE.md uses, but Builders is what a reader scans for.
|
|
264
264
|
{ id: 'roster', label: 'Builders', icon: 'leaderboard', href: '/roster', gate: 'signed' },
|
|
265
265
|
// task 1002577: the cross-project builder directory (/scouting graduated
|
|
266
|
-
// into the hall). Gated to MATCH its data — /api/
|
|
266
|
+
// into the hall). Gated to MATCH its data — /api/bongos/scouting checks
|
|
267
267
|
// `project.curate`, whose catalog floor is METIC. This read 'archon' until
|
|
268
268
|
// task 1002710: the nav was stricter than the wall, so a Metic could open
|
|
269
269
|
// /people by URL but never saw the link. Cosmetic either way —
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
(() => {
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const API = '/api/
|
|
18
|
+
const API = '/api/bongos';
|
|
19
19
|
// Generated typed client (task 1996 / R09), served as window.BongosClient by
|
|
20
20
|
// /lib/bongos-client.js. baseUrl:'' passes the full same-origin paths this file
|
|
21
21
|
// builds through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
|
package/modules/ideas/inbox.js
CHANGED
|
@@ -36,7 +36,7 @@ const VALID_KINDS = [
|
|
|
36
36
|
'unclassified',
|
|
37
37
|
// V3.R31 (#252) — Metics propose new version criteria via idea_inbox with
|
|
38
38
|
// structured body_md { target_version, criterion_md, sort_order_hint }.
|
|
39
|
-
// Archon ratifies via POST /api/
|
|
39
|
+
// Archon ratifies via POST /api/bongos/inbox/:id/ratify, which atomically
|
|
40
40
|
// inserts a done_when_criteria row, marks the idea promoted, awards karma,
|
|
41
41
|
// and writes a criterion_proposal_audit row. The kind value gates the
|
|
42
42
|
// ratify endpoint (assert kind='criterion-proposal' before promotion).
|
|
@@ -1222,7 +1222,7 @@ async function getIdeaGrade(ideaId) {
|
|
|
1222
1222
|
// the proposer (delta=+3, reason='proposal_ratified'), and writes an audit
|
|
1223
1223
|
// row capturing who proposed / who ratified / when / what.
|
|
1224
1224
|
//
|
|
1225
|
-
// Called by POST /api/
|
|
1225
|
+
// Called by POST /api/bongos/inbox/:id/ratify (Archon-only, gated by requireRank
|
|
1226
1226
|
// in the router). The body_md of the source idea is parsed as JSON with the
|
|
1227
1227
|
// shape { target_version, criterion_md, sort_order_hint }; malformed bodies
|
|
1228
1228
|
// throw BAD_BODY so the route can return a structured 400.
|
|
@@ -500,7 +500,7 @@ async function ecosystemAnalytics(days = 90) {
|
|
|
500
500
|
// The writing middleware (src/bongos/middleware/audit.js) calls insertAuditLog
|
|
501
501
|
// AFTER the response has been sent, wrapped in try/catch — so the request
|
|
502
502
|
// path never blocks on this insert and a DB failure here can never break a
|
|
503
|
-
// real user request. The reader is the archon-only GET /api/
|
|
503
|
+
// real user request. The reader is the archon-only GET /api/bongos/audit-log
|
|
504
504
|
// endpoint backed by listAuditLog below (paginated, newest-first).
|
|
505
505
|
// -------------------------------------------------------------------------
|
|
506
506
|
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
//
|
|
20
20
|
// Wired in via modules/lifecycle/routes/done-when.js (the loader mounts it,
|
|
21
21
|
// BV1.R86). Three endpoints:
|
|
22
|
-
// GET /api/
|
|
23
|
-
// POST /api/
|
|
24
|
-
// POST /api/
|
|
22
|
+
// GET /api/bongos/versions/:id/done-when — public, 60s cache
|
|
23
|
+
// POST /api/bongos/done-when/:criterionId/satisfy — auth
|
|
24
|
+
// POST /api/bongos/done-when/:criterionId/unsatisfy — auth
|
|
25
25
|
|
|
26
26
|
const { pool } = require('../../src/module-api');
|
|
27
27
|
const { nonTerminalSql } = require('./task-status');
|
|
@@ -165,7 +165,7 @@ async function listPendingReviewCriteria({ versionId = null } = {}, deps = {}) {
|
|
|
165
165
|
|
|
166
166
|
// Roll up every criterion for a version together with the tasks that gate it
|
|
167
167
|
// (via the task_criteria join table, migration 055) and a status breakdown.
|
|
168
|
-
// This is the read behind GET /api/
|
|
168
|
+
// This is the read behind GET /api/bongos/versions/:id/progress and the /status
|
|
169
169
|
// skill — it answers "what's left for criterion Cn?" in one query instead of
|
|
170
170
|
// the ~10-call prose archaeology that motivated #435.
|
|
171
171
|
//
|
|
@@ -88,7 +88,7 @@ async function suggestClose(versionId) {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// Suggestions for every version still in 'building' status.
|
|
91
|
-
// Used by GET /api/
|
|
91
|
+
// Used by GET /api/bongos/versions/close-suggestions and by /builder-start.
|
|
92
92
|
async function getCloseSuggestionsForBuildingVersions() {
|
|
93
93
|
const { rows: versions } = await pool.query(
|
|
94
94
|
`SELECT id FROM versions WHERE status = 'building' ORDER BY started_at, id`
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// indexes: GIN(tags), GIN(related_files),
|
|
10
10
|
// UNIQUE(source, source_ref) WHERE non-null
|
|
11
11
|
//
|
|
12
|
-
// Wired into routes by src/bongos/routes.js (POST + GET /api/
|
|
12
|
+
// Wired into routes by src/bongos/routes.js (POST + GET /api/bongos/learnings).
|
|
13
13
|
|
|
14
14
|
// Carved into modules/memory/ (BV1.R72 / ADR 0091 §2): pool via the doorway.
|
|
15
15
|
const crypto = require('crypto');
|
|
@@ -178,7 +178,7 @@ async function findLearningsByTags(tags, { limit = 50 } = {}) {
|
|
|
178
178
|
//
|
|
179
179
|
// Consumers:
|
|
180
180
|
// - scripts/gds/claim.js calls this indirectly via
|
|
181
|
-
// GET /api/
|
|
181
|
+
// GET /api/bongos/learnings?touches=a,b,c so /builder-claim N can surface
|
|
182
182
|
// up to 3 most-recent relevant learnings right after a successful claim.
|
|
183
183
|
// Query overhead is bounded by the GIN(tags) + GIN(related_files) indexes
|
|
184
184
|
// declared in migration 006_pms_v2.sql (<50ms target).
|
package/modules/memory/memory.js
CHANGED
|
@@ -494,7 +494,7 @@ async function bfgWriteMemoryFile({ targetBuilderId, path, content, audit, karma
|
|
|
494
494
|
RETURNING id, recorded_at`,
|
|
495
495
|
[
|
|
496
496
|
a.principalId ?? null,
|
|
497
|
-
a.route ?? '/api/
|
|
497
|
+
a.route ?? '/api/bongos/memory/builders/:builderId/bfg-write',
|
|
498
498
|
{
|
|
499
499
|
action: a.action || 'bfg_dream_write',
|
|
500
500
|
http_method: 'POST',
|
|
@@ -429,7 +429,7 @@ function shortSession(sessionId) {
|
|
|
429
429
|
return String(sessionId).slice(0, 8);
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
// Build the { title, body_md, kind } payload for POST /api/
|
|
432
|
+
// Build the { title, body_md, kind } payload for POST /api/bongos/inbox. The
|
|
433
433
|
// title carries ONBOARDING_FRICTION_TAG so triage (and the future kind
|
|
434
434
|
// migration) can find these rows; the body holds the subagent summary, each
|
|
435
435
|
// friction moment, and the deterministic signals as an audit trail.
|
|
@@ -177,7 +177,7 @@ module.exports = function myProjectsRoutes() {
|
|
|
177
177
|
// the registry-trusted origin, never the caller's raw input.
|
|
178
178
|
let outcome = null;
|
|
179
179
|
try {
|
|
180
|
-
const r = await fetch(`${client.origin}/api/
|
|
180
|
+
const r = await fetch(`${client.origin}/api/bongos/access-requests`, {
|
|
181
181
|
method: 'POST',
|
|
182
182
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'User-Agent': userAgent() },
|
|
183
183
|
body: JSON.stringify({
|
|
@@ -120,7 +120,7 @@ async function notifyInstanceLogout(membership, githubId) {
|
|
|
120
120
|
const ac = new AbortController();
|
|
121
121
|
const timer = setTimeout(() => ac.abort(), SLO_TIMEOUT_MS);
|
|
122
122
|
try {
|
|
123
|
-
const resp = await fetch(`${origin}/api/
|
|
123
|
+
const resp = await fetch(`${origin}/api/bongos/auth/backchannel-logout`, {
|
|
124
124
|
method: 'POST',
|
|
125
125
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
126
126
|
body: JSON.stringify({ logout_token: token }),
|
|
@@ -260,7 +260,7 @@ module.exports = function ssoRoutes() {
|
|
|
260
260
|
const usable = pi.sessionUsableForFederation(session);
|
|
261
261
|
if (!usable.ok) {
|
|
262
262
|
if (usable.reason === 'no_session') {
|
|
263
|
-
return res.redirect(302, `/api/
|
|
263
|
+
return res.redirect(302, `/api/bongos/auth/web/start?return=${encodeURIComponent(req.originalUrl)}`);
|
|
264
264
|
}
|
|
265
265
|
const msg = usable.reason === 'inactive'
|
|
266
266
|
? 'This account is no longer active. Ask an Archon on the hub if you believe this is an error.'
|
|
@@ -632,7 +632,7 @@ baked apex number sits inside apex-only sentinels so a child instance shows an e
|
|
|
632
632
|
its own feed answers):
|
|
633
633
|
data-ledger="line" = the first progress row whose status is building (else the first row), read
|
|
634
634
|
as "<shipped> of <total> shipped on <V>"; data-ledger="meter" is that ratio.
|
|
635
|
-
data-rows = /api/
|
|
635
|
+
data-rows = /api/bongos/public/recent-shipped?limit=6. Titles are quoted data: an em or en
|
|
636
636
|
dash becomes a comma and a trailing parenthetical is dropped at render time.
|
|
637
637
|
A child instance with no ledger yet answers {tasks:[]}, so the meter, the
|
|
638
638
|
sub-line and the list collapse rather than promise six rows that never
|
|
@@ -910,7 +910,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
910
910
|
<script>
|
|
911
911
|
(function () {
|
|
912
912
|
var API = '/api/bongos';
|
|
913
|
-
var API_ROOT = '/api/
|
|
913
|
+
var API_ROOT = '/api/bongos';
|
|
914
914
|
|
|
915
915
|
/* ── the tagline. It is ONE server-stamped string, but the composition wants it
|
|
916
916
|
on two lines, so the two halves are baked as separate spans. A host whose
|
|
@@ -6181,7 +6181,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6181
6181
|
manifestFor = manageId;
|
|
6182
6182
|
var ctl = typeof AbortController === 'function' ? new AbortController() : null;
|
|
6183
6183
|
var timer = ctl ? setTimeout(function () { ctl.abort(); }, 8000) : null;
|
|
6184
|
-
manifestPromise = fetch('https://' + domain + '/api/
|
|
6184
|
+
manifestPromise = fetch('https://' + domain + '/api/bongos/instance', { mode: 'cors', signal: ctl ? ctl.signal : undefined })
|
|
6185
6185
|
.then(function (res) {
|
|
6186
6186
|
/* a project that ANSWERED but refused (its own pre-launch gate, a proxy
|
|
6187
6187
|
error page) is not an older core — carry the status, don't guess */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"surface": "public-landing",
|
|
5
5
|
"modes": ["dark"],
|
|
6
6
|
"modeQuery": false,
|
|
7
|
-
"ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/provisioning/instances/999", "/provisioning/instances/101$", "/api/
|
|
7
|
+
"ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/provisioning/instances/999", "/provisioning/instances/101$", "/api/bongos/instance$", "/github/repos$", "/provisioning/onboard-plan"],
|
|
8
8
|
"ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "\\b404\\b", "Unauthorized"],
|
|
9
9
|
"ignoreControls": [".skip", ".vh"],
|
|
10
10
|
"states": {
|
package/modules/sessions/db.js
CHANGED
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
|
|
25
25
|
const api = require('../../src/module-api');
|
|
26
26
|
const { pool } = api;
|
|
27
|
+
// The PURE verdict + every clamp and threshold (task 1003865). The module's own
|
|
28
|
+
// file, not the doorway: it touches no kernel surface, and keeping the numbers
|
|
29
|
+
// in one place is what stops the route and the poller disagreeing.
|
|
30
|
+
const uploadHealth = require('./upload-health');
|
|
27
31
|
// `seams` is the doorway's kernel-mediated seam surface (resolveOptional/resolve).
|
|
28
32
|
// upsertSessionRecordWithReward resolves the economy `reward` port through it —
|
|
29
33
|
// the module↔module seam, never a sibling-module require (BV1.R77 / ADR 0093 §2).
|
|
@@ -656,6 +660,118 @@ async function filterTaskIdsClaimedBy(builderId, taskIds) {
|
|
|
656
660
|
return rows.map((r) => Number(r.task_id));
|
|
657
661
|
}
|
|
658
662
|
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
// -------------------------------------------------------------------------
|
|
666
|
+
// Session-upload health (task 1003865) — is the corpus still being written?
|
|
667
|
+
// -------------------------------------------------------------------------
|
|
668
|
+
//
|
|
669
|
+
// THE SIGNAL. The server already knows both halves independently: a task reached
|
|
670
|
+
// status='shipped', and whether that task's builder uploaded any session around
|
|
671
|
+
// then. A ship with no session record for its builder inside a window is the unit
|
|
672
|
+
// of evidence. Before this, the ONLY trace of a skipped upload was a jsonl file on
|
|
673
|
+
// the builder's own laptop, so a weeks-long outage that stopped every cost-plus
|
|
674
|
+
// reward from booking was invisible from the server.
|
|
675
|
+
//
|
|
676
|
+
// GROUPED BY BUILDER, which is the entire design and is measured, not assumed —
|
|
677
|
+
// see ../upload-health for the numbers. The corpus-wide share cannot see the
|
|
678
|
+
// outage (3.4% in its worst month, against 1.7-2.0% in healthy ones); per builder
|
|
679
|
+
// the same data fires on 13 days, all inside the outage window, all one builder.
|
|
680
|
+
//
|
|
681
|
+
// KEYED ON builder_id + TIME, NEVER ON task_ids. session_records.task_ids is
|
|
682
|
+
// EMPTY on 54% of rows (1,771 of 3,873 when task 1002448 measured it), so the
|
|
683
|
+
// obvious predicate — "no session record NAMES this task" — reports the linkage
|
|
684
|
+
// gap as an outage. Neither query below mentions task_ids, and a test asserts
|
|
685
|
+
// that about the SQL these functions actually emit.
|
|
686
|
+
//
|
|
687
|
+
// Every threshold and clamp lives in ../upload-health (PURE, unit-tested), so the
|
|
688
|
+
// route and the poller cannot answer this question two different ways.
|
|
689
|
+
|
|
690
|
+
// sessionUploadCoverage — per-BUILDER ship counts over the lookback, and how many
|
|
691
|
+
// of those ships had NO session record for that builder inside the window. One
|
|
692
|
+
// query, server-side, deliberately: /sessions/search is rate-limited per builder
|
|
693
|
+
// and clamps limit to 100, so paging the corpus from a client to compute this is
|
|
694
|
+
// neither cheap nor reliable (measured while scoping this task — 45 pages, with
|
|
695
|
+
// 429s).
|
|
696
|
+
async function sessionUploadCoverage({ days, windowHours } = {}) {
|
|
697
|
+
const d = uploadHealth.clampLookbackDays(days);
|
|
698
|
+
const w = uploadHealth.clampWindowHours(windowHours);
|
|
699
|
+
const { rows } = await pool.query(
|
|
700
|
+
`SELECT t.shipped_by AS builder_id,
|
|
701
|
+
b.github_login,
|
|
702
|
+
COUNT(*)::int AS ships,
|
|
703
|
+
COUNT(*) FILTER (
|
|
704
|
+
WHERE NOT EXISTS (
|
|
705
|
+
SELECT 1
|
|
706
|
+
FROM session_records sr
|
|
707
|
+
WHERE sr.builder_id = t.shipped_by
|
|
708
|
+
AND sr.uploaded_at >= t.shipped_at - ($2 * interval '1 hour')
|
|
709
|
+
AND sr.uploaded_at <= t.shipped_at + ($2 * interval '1 hour')
|
|
710
|
+
)
|
|
711
|
+
)::int AS uncovered
|
|
712
|
+
FROM tasks t
|
|
713
|
+
JOIN builders b ON b.id = t.shipped_by
|
|
714
|
+
WHERE t.status = 'shipped'
|
|
715
|
+
AND t.shipped_by IS NOT NULL
|
|
716
|
+
AND t.shipped_at IS NOT NULL
|
|
717
|
+
AND t.shipped_at >= now() - ($1 * interval '1 day')
|
|
718
|
+
GROUP BY t.shipped_by, b.github_login
|
|
719
|
+
ORDER BY 4 DESC`,
|
|
720
|
+
[d, w]
|
|
721
|
+
);
|
|
722
|
+
return { days: d, windowHours: w, rows };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// shipsMissingSessionRecord — the offending ships themselves, newest first. The
|
|
726
|
+
// verdict says how bad and whose; this says WHICH, because recovering the unpaid
|
|
727
|
+
// work needs the task ids and a percentage does not carry them. Bounded by
|
|
728
|
+
// clampSampleLimit: evidence, not a report.
|
|
729
|
+
async function shipsMissingSessionRecord({ days, windowHours, limit, builderId } = {}) {
|
|
730
|
+
const d = uploadHealth.clampLookbackDays(days);
|
|
731
|
+
const w = uploadHealth.clampWindowHours(windowHours);
|
|
732
|
+
const n = uploadHealth.clampSampleLimit(limit);
|
|
733
|
+
const b = Number.isFinite(Number(builderId)) ? Number(builderId) : null;
|
|
734
|
+
const { rows } = await pool.query(
|
|
735
|
+
`SELECT t.id, t.title, t.shipped_by, b.github_login, t.shipped_at
|
|
736
|
+
FROM tasks t
|
|
737
|
+
JOIN builders b ON b.id = t.shipped_by
|
|
738
|
+
WHERE t.status = 'shipped'
|
|
739
|
+
AND t.shipped_by IS NOT NULL
|
|
740
|
+
AND t.shipped_at IS NOT NULL
|
|
741
|
+
AND t.shipped_at >= now() - ($1 * interval '1 day')
|
|
742
|
+
AND ($3::bigint IS NULL OR t.shipped_by = $3)
|
|
743
|
+
AND NOT EXISTS (
|
|
744
|
+
SELECT 1
|
|
745
|
+
FROM session_records sr
|
|
746
|
+
WHERE sr.builder_id = t.shipped_by
|
|
747
|
+
AND sr.uploaded_at >= t.shipped_at - ($2 * interval '1 hour')
|
|
748
|
+
AND sr.uploaded_at <= t.shipped_at + ($2 * interval '1 hour')
|
|
749
|
+
)
|
|
750
|
+
ORDER BY t.shipped_at DESC
|
|
751
|
+
LIMIT ${n}`,
|
|
752
|
+
[d, w, b]
|
|
753
|
+
);
|
|
754
|
+
return rows;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// sessionUploadHealth — the whole answer in one call: the verdict, the per-builder
|
|
758
|
+
// counts behind it, and a sample of the ships it is counting. The route serves
|
|
759
|
+
// this and the poller logs from it, so neither owns the arithmetic. The sample is
|
|
760
|
+
// skipped entirely when nothing is uncovered — the healthy path costs one query.
|
|
761
|
+
async function sessionUploadHealth({ days, windowHours, sampleLimit } = {}) {
|
|
762
|
+
const coverage = await sessionUploadCoverage({ days, windowHours });
|
|
763
|
+
const verdict = uploadHealth.uploadHealthVerdict(coverage.rows);
|
|
764
|
+
const sample = verdict.uncovered > 0
|
|
765
|
+
? await shipsMissingSessionRecord({ days, windowHours, limit: sampleLimit })
|
|
766
|
+
: [];
|
|
767
|
+
return {
|
|
768
|
+
days: coverage.days,
|
|
769
|
+
window_hours: coverage.windowHours,
|
|
770
|
+
verdict,
|
|
771
|
+
sample,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
659
775
|
module.exports = {
|
|
660
776
|
// write side (6D.1)
|
|
661
777
|
upsertSessionRecord,
|
|
@@ -667,6 +783,10 @@ module.exports = {
|
|
|
667
783
|
// search + fetch (6D.2)
|
|
668
784
|
searchSessions,
|
|
669
785
|
summarizeSessions,
|
|
786
|
+
// upload health (task 1003865) — is the corpus still being written at all?
|
|
787
|
+
sessionUploadCoverage,
|
|
788
|
+
shipsMissingSessionRecord,
|
|
789
|
+
sessionUploadHealth,
|
|
670
790
|
getSessionById,
|
|
671
791
|
listSessionsByBuilder,
|
|
672
792
|
filterTaskIdsClaimedBy,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"key": "sessions",
|
|
3
3
|
"title": "Sessions & BFG (session corpus · efficiency eval)",
|
|
4
|
-
"description": "The session-records corpus + its data plane (POST /sessions upload, the Archon-gated cross-builder /sessions/search + /sessions/:id deep-dive), the BFG 'read-the-room' cross-builder scorer (ADR 0026 §6B), and the session-pulse / stale-claim heartbeat helpers. A core-domain module carved out of the GDS core in BV1.R82 (ADR 0093 §1); default-on because every instance records its builders' sessions for the BFG. Like the memory module, sessions registers no port — core never calls back into it: the ship-time digest write is an HTTP POST to the module's own /sessions route, and the only outward dependency is the economy `reward` kernel PORT it resolves (session token reward + sumCreditsForTasks) at upload time.",
|
|
4
|
+
"description": "The session-records corpus + its data plane (POST /sessions upload, the Archon-gated cross-builder /sessions/search + /sessions/:id deep-dive + /sessions/upload-health, the server-side signal that a builder's session uploads have silently stopped), the BFG 'read-the-room' cross-builder scorer (ADR 0026 §6B), and the session-pulse / stale-claim heartbeat helpers. A core-domain module carved out of the GDS core in BV1.R82 (ADR 0093 §1); default-on because every instance records its builders' sessions for the BFG. Like the memory module, sessions registers no port — core never calls back into it: the ship-time digest write is an HTTP POST to the module's own /sessions route, and the only outward dependency is the economy `reward` kernel PORT it resolves (session token reward + sumCreditsForTasks) at upload time.",
|
|
5
5
|
"coreVersion": "^1.10.0",
|
|
6
6
|
"default": true,
|
|
7
7
|
"maintenance": { "status": "core-maintained" },
|
|
8
|
-
"contributes": { "routes": ["sessions"], "pollers": ["session-reward-reconcile"] },
|
|
8
|
+
"contributes": { "routes": ["sessions"], "pollers": ["session-reward-reconcile", "session-upload-health"] },
|
|
9
9
|
"provides": [],
|
|
10
10
|
"consumes": []
|
|
11
11
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/sessions/pollers/session-upload-health.js — the half of task 1003865
|
|
4
|
+
// that makes the signal NOTICE rather than merely be answerable.
|
|
5
|
+
//
|
|
6
|
+
// GET /sessions/upload-health makes the outage visible to anyone who asks. Nobody
|
|
7
|
+
// asked for weeks, which is the whole incident: the failure mode is silence, and a
|
|
8
|
+
// number you have to go and look at is silent too. So this sweep asks on a
|
|
9
|
+
// schedule and writes the answer where the server's own operator surface already
|
|
10
|
+
// is — the process log, beside the reward reconciler's lines, which is what a
|
|
11
|
+
// journalctl over this unit is read for.
|
|
12
|
+
//
|
|
13
|
+
// SEPARATE FROM session-reward-reconcile ON PURPOSE. That poller MOVES MONEY; this
|
|
14
|
+
// one only reports. Folding telemetry into the payout sweep would let a bad read
|
|
15
|
+
// here delay or crash a booking there, and the two want different cadences —
|
|
16
|
+
// reward recovery wants minutes, a 14-day coverage ratio does not change in ten.
|
|
17
|
+
//
|
|
18
|
+
// It writes nothing and books nothing. A check that could itself change state
|
|
19
|
+
// would need its own safety argument; this one needs none.
|
|
20
|
+
|
|
21
|
+
const db = require('../db');
|
|
22
|
+
const uploadHealth = require('../upload-health');
|
|
23
|
+
// The doorway's namespaced structured logger (task 1003130), not console: this
|
|
24
|
+
// module reaches the kernel only through module-api, and console.* in a server
|
|
25
|
+
// path is the count the fitness ratchet exists to shrink, not to grow.
|
|
26
|
+
const api = require('../../../src/module-api');
|
|
27
|
+
const log = api.logger('sessions');
|
|
28
|
+
|
|
29
|
+
// Hourly. The measurement behind the thresholds used a 14-day lookback stepped
|
|
30
|
+
// daily, so anything under a day is already far finer than the signal; hourly is
|
|
31
|
+
// chosen so a fresh outage surfaces within the same working session rather than
|
|
32
|
+
// the next morning.
|
|
33
|
+
const INTERVAL_MS = Number(process.env.SESSION_UPLOAD_HEALTH_MS) || 60 * 60_000;
|
|
34
|
+
const FIRST_SWEEP_DELAY_MS = Number(process.env.SESSION_UPLOAD_HEALTH_FIRST_MS) || 120_000;
|
|
35
|
+
|
|
36
|
+
let intervalTimer = null;
|
|
37
|
+
let firstTimer = null;
|
|
38
|
+
|
|
39
|
+
// logLinesFor — PURE, so the wording is pinned by a DB-free test. A log line that
|
|
40
|
+
// says "unhealthy" and no more is the same silence with a timestamp on it: the
|
|
41
|
+
// firing line has to name the builder, the arithmetic, and where to look next,
|
|
42
|
+
// because the person reading it at 2am did not scope this task.
|
|
43
|
+
function logLinesFor(health) {
|
|
44
|
+
const v = (health && health.verdict) || {};
|
|
45
|
+
const where = `GET /api/gds/sessions/upload-health?days=${health && health.days}`;
|
|
46
|
+
if (!v.firing) {
|
|
47
|
+
return [`[session-upload-health] ${v.status || 'unknown'} — ${v.reason || 'no reason given'}`];
|
|
48
|
+
}
|
|
49
|
+
const lines = [
|
|
50
|
+
`[session-upload-health] ALERT — ${v.reason}`,
|
|
51
|
+
`[session-upload-health] a missing session record means no session.token_reward booked (ADR 0146: cost-plus is the only reward stream), so this is unpaid shipped work, not just missing telemetry.`,
|
|
52
|
+
`[session-upload-health] look: ${where} · likely cause: the upload skipping client-side (session-upload-skips.jsonl on that builder's machine).`,
|
|
53
|
+
];
|
|
54
|
+
const sample = Array.isArray(health.sample) ? health.sample.slice(0, 5) : [];
|
|
55
|
+
if (sample.length > 0) {
|
|
56
|
+
lines.push(`[session-upload-health] uncovered ships, newest first: ${sample.map((s) => `#${s.id}`).join(' ')}`);
|
|
57
|
+
}
|
|
58
|
+
return lines;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function runSweep() {
|
|
62
|
+
const health = await db.sessionUploadHealth({
|
|
63
|
+
days: uploadHealth.DEFAULT_LOOKBACK_DAYS,
|
|
64
|
+
windowHours: uploadHealth.DEFAULT_WINDOW_HOURS,
|
|
65
|
+
});
|
|
66
|
+
const lines = logLinesFor(health);
|
|
67
|
+
// A firing sweep goes out at ERROR so it survives a level filter set to hide
|
|
68
|
+
// the hourly all-clear — the whole failure mode here is a signal nobody saw.
|
|
69
|
+
const write = health.verdict && health.verdict.firing ? log.error : log.info;
|
|
70
|
+
for (const line of lines) write(line);
|
|
71
|
+
return health;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Start the periodic sweep. Idempotent (a second call is a no-op). Kill-switched
|
|
75
|
+
// by SESSION_UPLOAD_HEALTH_DISABLED=1. Timers are unref'd so they never hold the
|
|
76
|
+
// process open on a systemd restart (the session-reward-reconcile precedent).
|
|
77
|
+
function start() {
|
|
78
|
+
if (process.env.SESSION_UPLOAD_HEALTH_DISABLED === '1') {
|
|
79
|
+
log.info('[session-upload-health] disabled (SESSION_UPLOAD_HEALTH_DISABLED=1)');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (intervalTimer) return; // already started
|
|
83
|
+
|
|
84
|
+
const tick = () => {
|
|
85
|
+
runSweep().catch((e) =>
|
|
86
|
+
log.error(e, '[session-upload-health] sweep crashed')
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
firstTimer = setTimeout(tick, FIRST_SWEEP_DELAY_MS);
|
|
91
|
+
if (firstTimer.unref) firstTimer.unref();
|
|
92
|
+
intervalTimer = setInterval(tick, INTERVAL_MS);
|
|
93
|
+
if (intervalTimer.unref) intervalTimer.unref();
|
|
94
|
+
|
|
95
|
+
log.info(
|
|
96
|
+
`[session-upload-health] started — checking session-upload coverage every ${Math.round(INTERVAL_MS / 60_000)}m`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stop() {
|
|
101
|
+
if (firstTimer) {
|
|
102
|
+
clearTimeout(firstTimer);
|
|
103
|
+
firstTimer = null;
|
|
104
|
+
}
|
|
105
|
+
if (intervalTimer) {
|
|
106
|
+
clearInterval(intervalTimer);
|
|
107
|
+
intervalTimer = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { start, stop, runSweep, logLinesFor, INTERVAL_MS };
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// rows as data; a follow-up task is expected to add the marker
|
|
22
22
|
// column + UPDATE write + builder surface in /builder-start.
|
|
23
23
|
//
|
|
24
|
-
// 2. POST /api/
|
|
24
|
+
// 2. POST /api/bongos/pulse route is NOT wired here — routes.js is out
|
|
25
25
|
// of touches[]. recordPulse() is exported for whichever future
|
|
26
26
|
// request handler is created. The CLI shell at scripts/gds/pulse.js
|
|
27
27
|
// posts to the intended endpoint and will start working once the
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
//
|
|
30
30
|
// Follow-up work (file new tasks when picked up):
|
|
31
31
|
// a) New migration adding claims.pulse_lost_at timestamptz.
|
|
32
|
-
// b) Route: POST /api/
|
|
32
|
+
// b) Route: POST /api/bongos/pulse → recordPulse() (auth + claim
|
|
33
33
|
// ownership check).
|
|
34
34
|
// c) sweepStaleClaims() side-effect: UPDATE claims
|
|
35
35
|
// SET pulse_lost_at = now() WHERE id = ANY(...).
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
// POST /sessions — upload your OWN session digest (6D.1). Owner is
|
|
7
7
|
// forced to the caller; body builder_id is ignored.
|
|
8
8
|
// GET /sessions/mine — your own sessions (self-serve summary list).
|
|
9
|
+
// GET /sessions/upload-health
|
|
10
|
+
// — is the corpus still being written? Ships whose
|
|
11
|
+
// builder uploaded no session around them, per
|
|
12
|
+
// builder (task 1003865). Same cross-builder gate +
|
|
13
|
+
// audit as /search, because it reads the same corpus.
|
|
9
14
|
// GET /sessions/search — cross-builder search over session + builder facets
|
|
10
15
|
// (rank/karma/drachmae/tenure). Archon-gated +
|
|
11
16
|
// audited (the BFG principal posture until 6C.1).
|
|
@@ -36,6 +41,11 @@ const db = require('../db');
|
|
|
36
41
|
// the interaction profile as data precisely so it does not have to require
|
|
37
42
|
// builder-settings, which ADR 0083 forbids.
|
|
38
43
|
const { scoreAccessibility } = require('../accessibility');
|
|
44
|
+
// The PURE clamps behind /sessions/upload-health. The route audits the values it
|
|
45
|
+
// will actually query with, not the raw query string — an audit row naming
|
|
46
|
+
// `days=99999` for a read that ran at 365 is a record of something that did not
|
|
47
|
+
// happen (task 1003865).
|
|
48
|
+
const uploadHealth = require('../upload-health');
|
|
39
49
|
const seams = api; // resolveOptional('reward' | 'builder-settings') — kernel-mediated ports
|
|
40
50
|
const { validateOrRespond, parseId } = api;
|
|
41
51
|
|
|
@@ -358,6 +368,42 @@ module.exports = function buildSessionsRouter() {
|
|
|
358
368
|
}
|
|
359
369
|
});
|
|
360
370
|
|
|
371
|
+
// GET /sessions/upload-health — the answer to "has anyone's session upload
|
|
372
|
+
// silently stopped?", which before task 1003865 existed ONLY as a jsonl file on
|
|
373
|
+
// the builder's own laptop. A skipped upload means no session_records row, which
|
|
374
|
+
// means the ADR 0146 cost-plus reward never books — a builder earning nothing for
|
|
375
|
+
// work that shipped, invisible from here.
|
|
376
|
+
//
|
|
377
|
+
// Read-only and side-effect-free, so it is DELIBERATELY not a new permission:
|
|
378
|
+
// it reads the same corpus /sessions/search reads, for the same cross-builder
|
|
379
|
+
// audience, so it rides the same `session.search` gate and writes the same
|
|
380
|
+
// fail-closed audit row. A new permission would be a new thing to grant before
|
|
381
|
+
// anyone could look at a number that already exists.
|
|
382
|
+
router.get('/sessions/upload-health', auth.requireBuilder, auth.requirePermission('session.search'), readRateLimit, async (req, res) => {
|
|
383
|
+
const q = req.query || {};
|
|
384
|
+
try {
|
|
385
|
+
// Audit BEFORE the query, like /search: no cross-builder read without a
|
|
386
|
+
// durable record of who asked.
|
|
387
|
+
await recordSessionRead(req, {
|
|
388
|
+
route: '/api/bongos/sessions/upload-health',
|
|
389
|
+
targetBuilderId: null,
|
|
390
|
+
target: `upload-health days=${uploadHealth.clampLookbackDays(q.days)} window_hours=${uploadHealth.clampWindowHours(q.window_hours)}`,
|
|
391
|
+
});
|
|
392
|
+
const health = await db.sessionUploadHealth({
|
|
393
|
+
days: q.days,
|
|
394
|
+
windowHours: q.window_hours,
|
|
395
|
+
sampleLimit: q.sample_limit,
|
|
396
|
+
});
|
|
397
|
+
res.json(health);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
// The namespaced structured logger, not console — see the poller's note.
|
|
400
|
+
// Its siblings above still use console because they predate task 1003130;
|
|
401
|
+
// new call sites do not add to the count the ratchet exists to shrink.
|
|
402
|
+
api.logger('sessions').error(err, 'GET /sessions/upload-health failed');
|
|
403
|
+
res.fail('upload_health_failed', 500);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
361
407
|
// rank: any-builder — your OWN sessions (self-serve; no audit needed).
|
|
362
408
|
router.get('/sessions/mine', auth.requireBuilder, readRateLimit, async (req, res) => {
|
|
363
409
|
try {
|