@bongos/core 1.21.112 → 1.21.114
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 +616 -186
- package/.claude/skills/ask-for-help/SKILL.md +1 -1
- package/.claude/skills/bongos-feedback/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -0
- package/GOVERNMENT.md +128 -11
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +28 -0
- package/clients/bongos-client/index.cjs +28 -0
- package/clients/bongos-client/index.d.ts +45 -2
- package/clients/bongos-client/index.mjs +28 -0
- package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
- package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
- package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
- package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
- package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
- package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
- package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
- package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
- package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
- package/docs/adr/README.md +10 -1
- package/docs/api/openapi.json +989 -87
- package/docs/api-reference.md +18 -4
- package/docs/copy-inventory.md +343 -262
- package/docs/copy-registry.json +1223 -426
- package/docs/file-map.md +2 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/packs/engineer.md +2 -1
- package/docs/page-inventory.json +91 -6
- package/docs/page-readings.json +5017 -4458
- package/modules/builder-settings/law-locks.js +202 -0
- package/modules/builder-settings/routes/settings.js +66 -6
- package/modules/builder-settings/settings.js +7 -0
- package/modules/discord/ship-broadcast.js +22 -13
- package/modules/government/asks-db.js +54 -0
- package/modules/government/asks.js +269 -0
- package/modules/government/board-idea-reads.js +68 -0
- package/modules/government/board.js +21 -57
- package/modules/government/build-rights.js +148 -0
- package/modules/government/buildouts.js +160 -0
- package/modules/government/counts.js +81 -0
- package/modules/government/db.js +242 -0
- package/modules/government/decision-board.js +234 -8
- package/modules/government/decision-rules.js +2 -0
- package/modules/government/docket.js +114 -6
- package/modules/government/law-board.js +131 -0
- package/modules/government/laws.js +241 -0
- package/modules/government/leads.js +170 -0
- package/modules/government/migrations/government_027_laws.sql +143 -0
- package/modules/government/migrations/government_028_asks.sql +57 -0
- package/modules/government/migrations/government_029_leads.sql +135 -0
- package/modules/government/migrations/government_030_buildouts.sql +111 -0
- package/modules/government/migrations/government_031_treasury.sql +107 -0
- package/modules/government/module.json +2 -1
- package/modules/government/pollers/treasury-borrow.js +62 -0
- package/modules/government/positions.js +107 -0
- package/modules/government/presets.js +149 -0
- package/modules/government/routes/government.js +425 -2
- package/modules/government/routing.js +154 -0
- package/modules/government/treasury-db.js +166 -0
- package/modules/government/treasury.js +241 -0
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/board-room.js +2 -0
- package/modules/hall-ui/public/city-draw.js +604 -11
- package/modules/hall-ui/public/city.css +157 -1
- package/modules/hall-ui/public/city.html +37 -7
- package/modules/hall-ui/public/city.js +441 -21
- package/modules/hall-ui/public/city.states.json +110 -5
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/craft-mode.js +112 -29
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/diagram-viewer.css +12 -0
- package/modules/hall-ui/public/diagram-viewer.js +4 -0
- package/modules/hall-ui/public/diagrams.html +2 -2
- package/modules/hall-ui/public/drachmae.html +2 -2
- package/modules/hall-ui/public/fleet.html +2 -2
- package/modules/hall-ui/public/gate.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/gov-feeds.js +141 -0
- package/modules/hall-ui/public/gov-living.js +1002 -0
- package/modules/hall-ui/public/gov-positions.js +97 -0
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/hall-render.js +3 -0
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +3 -3
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/my-agents.css +334 -0
- package/modules/hall-ui/public/my-agents.html +72 -0
- package/modules/hall-ui/public/my-agents.js +603 -0
- package/modules/hall-ui/public/my-agents.states.json +37 -0
- package/modules/hall-ui/public/primer.html +2 -2
- package/modules/hall-ui/public/profile-connections.js +40 -28
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/ranks.html +2 -2
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/room.css +8 -0
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/sessions.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +116 -16
- package/modules/hall-ui/public/street-card.js +18 -1
- package/modules/hall-ui/public/street-deck.css +299 -0
- package/modules/hall-ui/public/street-deck.js +1145 -0
- package/modules/hall-ui/public/street-depth.js +712 -0
- package/modules/hall-ui/public/street-desk.js +5 -2
- package/modules/hall-ui/public/street-draw.js +109 -16
- package/modules/hall-ui/public/street-inside.js +137 -22
- package/modules/hall-ui/public/street-office.js +705 -0
- package/modules/hall-ui/public/street.css +161 -11
- package/modules/hall-ui/public/street.html +41 -11
- package/modules/hall-ui/public/street.js +78 -66
- package/modules/hall-ui/public/street.states.json +129 -21
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/style.css +5 -1
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.html +2 -2
- package/modules/hall-ui/public/watch.js +14 -1
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/hall-ui/records/living-city.md +57 -0
- package/modules/hall-ui/records/my-agents.md +21 -0
- package/modules/hall-ui/records/oversight-family.md +1 -0
- package/modules/hall-ui/records/profile-connections.md +10 -0
- package/modules/hall-ui/records/reading-rooms.md +2 -0
- package/modules/ideas/routes/blockers.js +92 -3
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/moving.js +17 -1
- package/modules/lifecycle/routes/moving.js +6 -0
- package/modules/lifecycle/routes/project-settings.js +23 -0
- package/modules/onboarding/approval-broadcast.js +8 -0
- package/modules/onboarding/routes/access-requests.js +16 -6
- package/modules/platform-identity/connections.js +13 -2
- package/modules/platform-identity/routes/connections.js +7 -1
- package/modules/public-landing/public/account-private.states.json +2 -2
- package/modules/public-landing/public/account.html +65 -5
- package/modules/public-landing/public/account.states.json +3 -3
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
- package/modules/public-landing/public/assets/world.css +4 -4
- package/modules/public-landing/public/connections-empty.states.json +14 -0
- package/modules/public-landing/public/connections-inactive.states.json +14 -0
- package/modules/public-landing/public/connections.html +450 -0
- package/modules/public-landing/public/connections.probes.json +414 -0
- package/modules/public-landing/public/connections.states.json +17 -0
- package/modules/public-landing/public/contact.html +2 -2
- package/modules/public-landing/public/guild.html +2 -2
- package/modules/public-landing/public/index.html +2 -2
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/privacy.html +2 -2
- package/modules/public-landing/public/projects.html +2 -2
- package/modules/public-landing/public/projects.probes.json +6 -2
- package/modules/public-landing/public/terms.html +2 -2
- package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
- package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
- package/modules/ui-design/kit/fixtures/connections.json +22 -0
- package/modules/ui-design/kit/serve.js +95 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +128 -0
- package/scripts/discord/README.md +8 -5
- package/scripts/discord/post.js +12 -1
- package/scripts/gds/artifact-audit.js +20 -3
- package/scripts/gds/ask.js +164 -0
- package/scripts/gds/autobongos-run.js +20 -5
- package/scripts/gds/autobongos-tweak-lane.js +262 -0
- package/scripts/gds/autobongos-verify.js +21 -2
- package/scripts/gds/claim.js +2 -0
- package/scripts/gds/government-walk.js +638 -4
- package/scripts/gds/pulse.js +9 -6
- package/scripts/gds/ship-escalation.js +22 -3
- package/scripts/gds/ship-finish.js +5 -4
- package/scripts/gds/ship-merge-gate.js +18 -6
- package/scripts/gds/tweak-halt.js +1 -0
- package/scripts/hall-preview/README.md +41 -1
- package/scripts/hall-preview/pref-writes.js +140 -0
- package/scripts/hall-preview/refresh-fixtures.js +101 -7
- package/scripts/hall-preview/scrub-identity.js +366 -7
- package/scripts/hall-preview/server.js +27 -1
- package/scripts/render-diagrams.sh +4 -2
- package/src/bongos/auth-idp.js +6 -1
- package/src/bongos/project-settings.js +41 -0
- package/src/bongos/route-rank-check.js +27 -0
- package/src/bongos/routes/me.js +68 -7
- package/src/bongos/serve-internal.js +5 -1
- package/src/module-api.js +7 -1
- package/src/platform-server.js +10 -0
- package/tests/application_lifecycle.mjs +47 -5
- package/tests/autobongos_tweak_lane.mjs +208 -0
- package/tests/conductor_main_e2e.mjs +21 -0
- package/tests/connections_api.mjs +18 -0
- package/tests/craft_mode_nav.mjs +5 -3
- package/tests/craft_mode_tallies.mjs +52 -16
- package/tests/discord_approvals.mjs +11 -0
- package/tests/discord_channels.mjs +20 -0
- package/tests/fixtures/gov-seated.mjs +64 -0
- package/tests/fixtures/government-ask-cards.json +1284 -0
- package/tests/government_act_alone_buildings.mjs +1 -0
- package/tests/government_agent_asks.mjs +322 -0
- package/tests/government_asks.mjs +408 -0
- package/tests/government_build_rights.mjs +253 -0
- package/tests/government_buildouts.mjs +382 -0
- package/tests/government_counts.mjs +143 -0
- package/tests/government_decision_rules.mjs +5 -1
- package/tests/government_laws.mjs +488 -0
- package/tests/government_laws_session.mjs +321 -0
- package/tests/government_leads.mjs +375 -0
- package/tests/government_positions.mjs +127 -0
- package/tests/government_presets.mjs +215 -0
- package/tests/government_routes.mjs +8 -1
- package/tests/government_routing.mjs +118 -0
- package/tests/government_seed.mjs +5 -0
- package/tests/government_treasury.mjs +394 -0
- package/tests/governor_docket.mjs +26 -2
- package/tests/hall_buildouts.mjs +208 -0
- package/tests/hall_city.mjs +14 -6
- package/tests/hall_city_living.mjs +617 -0
- package/tests/hall_gov_counts.mjs +336 -0
- package/tests/hall_gov_living.mjs +636 -0
- package/tests/hall_gov_real_site.mjs +655 -0
- package/tests/hall_matter_card.mjs +6 -1
- package/tests/hall_my_agents.mjs +481 -0
- package/tests/hall_nav.mjs +107 -4
- package/tests/hall_page_gate_map.mjs +4 -0
- package/tests/hall_preview_governor_fixtures.mjs +30 -3
- package/tests/hall_preview_scrub.mjs +59 -1
- package/tests/hall_preview_security.mjs +169 -0
- package/tests/hall_reading_world.mjs +44 -0
- package/tests/hall_roster_world.mjs +6 -2
- package/tests/hall_street.mjs +18 -2
- package/tests/hall_street_amend.mjs +1 -1
- package/tests/hall_street_deck.mjs +609 -0
- package/tests/hall_street_depth.mjs +559 -0
- package/tests/hall_street_desk.mjs +1 -1
- package/tests/hall_street_inside.mjs +29 -5
- package/tests/hall_tweak_editor.mjs +4 -1
- package/tests/hub_account_page.mjs +94 -3
- package/tests/hub_connections_page.mjs +507 -0
- package/tests/hub_invite_badge.mjs +1 -1
- package/tests/idp_device_exchange.mjs +18 -0
- package/tests/landing_legal_pages.mjs +8 -5
- package/tests/landing_page.mjs +23 -4
- package/tests/nav_permission_atoms.mjs +4 -1
- package/tests/profile_connections_ui.mjs +88 -25
- package/tests/projects_hub.mjs +15 -0
- package/tests/render_check.mjs +2 -2
- package/tests/ship_broadcast_routing.mjs +57 -16
- package/tests/ship_broadcast_server.mjs +40 -7
- package/tests/ship_merge_gate.mjs +19 -0
- package/tests/ui_design_kit.mjs +2 -2
- package/tests/watch_applications_queue.mjs +23 -0
|
@@ -567,6 +567,19 @@ function ownedRequests() {
|
|
|
567
567
|
return Object.values(guildsHeld()).filter((g) => g.members && g.members.you.membership_kind === 'owner')
|
|
568
568
|
.flatMap((g) => ((g.requests && g.requests.incoming) || []).map((r) => ({ ...r, guild: { slug: g.members.guild.slug, name: g.members.guild.name } })));
|
|
569
569
|
}
|
|
570
|
+
// GET /me/guilds (the account page's guild notice, task 1004260; ADR 0337 D4.4): the guilds
|
|
571
|
+
// the builder is in, each with their own two switches, derived from the SAME held set the
|
|
572
|
+
// guild page reads and writes — so a switch flipped there reads back here, as on the server.
|
|
573
|
+
// The route's own row shape (guilds.listMyGuilds): slug, name, visibility, membership_kind,
|
|
574
|
+
// shown_publicly, counted_in_totals, joined_at; count is the array's length.
|
|
575
|
+
function myGuilds(res) {
|
|
576
|
+
const mine = Object.values(guildsHeld()).filter((g) => g.members && g.members.you).map((g) => ({
|
|
577
|
+
slug: g.members.guild.slug, name: g.members.guild.name, visibility: g.members.guild.visibility,
|
|
578
|
+
membership_kind: g.members.you.membership_kind, shown_publicly: g.members.you.shown_publicly,
|
|
579
|
+
counted_in_totals: g.members.you.counted_in_totals, joined_at: g.members.you.joined_at || null,
|
|
580
|
+
}));
|
|
581
|
+
return json(res, 200, { guilds: mine, count: mine.length });
|
|
582
|
+
}
|
|
570
583
|
function myGuildRequests(res) {
|
|
571
584
|
const q = heldFixture('me__guild-requests');
|
|
572
585
|
const owner = ownedRequests();
|
|
@@ -598,6 +611,79 @@ function guildEngagementLive(slug, origin) {
|
|
|
598
611
|
return (heldFixture('me__guild-engagements').engagements || []).some((e) => e.guild && e.guild.slug === slug.toLowerCase() && e.project && sameOrigin(e.project.origin, origin));
|
|
599
612
|
}
|
|
600
613
|
|
|
614
|
+
// The connections page (task 1004278): GET /connections, /connections/requests and
|
|
615
|
+
// /connections/suggestions answered from ONE graph held for the run (connections.json, or
|
|
616
|
+
// the file a states file's stub.fixtures names), so an accept, decline, withdraw, remove or
|
|
617
|
+
// ask the page re-reads comes back changed. The routes' answers are mirrored
|
|
618
|
+
// (modules/platform-identity/routes/connections.js): a sent ask is the bare
|
|
619
|
+
// 201 { status: 'pending' } with no id, a decline a silent 204, a row the caller may not
|
|
620
|
+
// act on one 404, an open pair 409, your own handle 400, and an unknown handle the same 404
|
|
621
|
+
// a private or inactive one gets. "inactive": true in the graph answers every route 403,
|
|
622
|
+
// the provisional-profile refusal. The caps are copied (a kit module may not import one).
|
|
623
|
+
// FAIL=connections makes the two load-bearing reads 500; FAIL=connection-suggestions only
|
|
624
|
+
// the suggestions read, which the page treats as additive.
|
|
625
|
+
let GRAPH = null;
|
|
626
|
+
function graph() {
|
|
627
|
+
if (!GRAPH) GRAPH = stamp(heldFixture('connections'));
|
|
628
|
+
return GRAPH;
|
|
629
|
+
}
|
|
630
|
+
const STUB_CONN_HANDLE_MAX = 39;
|
|
631
|
+
const STUB_CONN_NOTE_MAX = 280;
|
|
632
|
+
let STUB_CONN_NEXT_ID = 900;
|
|
633
|
+
function withAvatar(row, origin) {
|
|
634
|
+
return typeof row.avatar_url === 'string' && row.avatar_url.startsWith('/') ? { ...row, avatar_url: origin + row.avatar_url } : row;
|
|
635
|
+
}
|
|
636
|
+
function connInactive(res) { return refuse(res, 403, 'profile_provisional', 'sign in to the platform once to activate your profile before connecting'); }
|
|
637
|
+
function connRead(res, which, origin) {
|
|
638
|
+
const g = graph();
|
|
639
|
+
if (g.inactive) return connInactive(res);
|
|
640
|
+
if (which !== 'suggestions' && FAIL.has('connections')) return json(res, 500, { error: { code: 'stub_failure', message: 'FAIL=connections' } });
|
|
641
|
+
if (which === 'suggestions' && FAIL.has('connection-suggestions')) return json(res, 500, { error: { code: 'stub_failure', message: 'FAIL=connection-suggestions' } });
|
|
642
|
+
const rows = (k) => (g[k] || []).map((r) => withAvatar(r, origin));
|
|
643
|
+
if (which === 'list') { const connections = rows('connections'); return json(res, 200, { connections, count: connections.length }); }
|
|
644
|
+
if (which === 'requests') {
|
|
645
|
+
const incoming = rows('incoming');
|
|
646
|
+
const outgoing = rows('outgoing').map(({ seen_at, ...r }) => r);
|
|
647
|
+
return json(res, 200, { incoming, outgoing, incoming_count: incoming.length, outgoing_count: outgoing.length });
|
|
648
|
+
}
|
|
649
|
+
const suggestions = rows('suggestions');
|
|
650
|
+
return json(res, 200, { suggestions, count: suggestions.length });
|
|
651
|
+
}
|
|
652
|
+
function connAsk(res, body) {
|
|
653
|
+
const g = graph();
|
|
654
|
+
if (g.inactive) return connInactive(res);
|
|
655
|
+
if (typeof body.handle !== 'string' || body.handle.length < 1 || body.handle.length > STUB_CONN_HANDLE_MAX) return refuse(res, 400, 'validation_failed', 'handle must be 1-39 characters');
|
|
656
|
+
if (body.note !== undefined && (typeof body.note !== 'string' || body.note.length > STUB_CONN_NOTE_MAX)) return refuse(res, 400, 'validation_failed', 'note must be at most 280 characters');
|
|
657
|
+
const h = body.handle.toLowerCase();
|
|
658
|
+
if (h === String(account().handle).toLowerCase()) return refuse(res, 400, 'cannot_connect_to_self', 'that is your own account');
|
|
659
|
+
const same = (r) => String(r.handle).toLowerCase() === h;
|
|
660
|
+
if (['connections', 'incoming', 'outgoing'].some((k) => (g[k] || []).some(same))) return refuse(res, 409, 'connection_already_open', 'there is already a request or a connection between you');
|
|
661
|
+
const who = (g.suggestions || []).find(same) || (g.directory || []).find(same);
|
|
662
|
+
if (!who) return refuse(res, 404, 'connection_target_not_found', 'connection_target_not_found');
|
|
663
|
+
g.outgoing = (g.outgoing || []).concat([{ id: String(STUB_CONN_NEXT_ID++), note: body.note || null, created_at: new Date().toISOString(), handle: who.handle, display_name: who.display_name || null, avatar_url: who.avatar_url || null }]);
|
|
664
|
+
g.suggestions = (g.suggestions || []).filter((r) => !same(r));
|
|
665
|
+
return json(res, 201, { status: 'pending' });
|
|
666
|
+
}
|
|
667
|
+
function connAnswer(res, id, verb) {
|
|
668
|
+
const g = graph();
|
|
669
|
+
if (g.inactive) return connInactive(res);
|
|
670
|
+
const r = /^\d{1,19}$/.test(id) && (g.incoming || []).find((x) => x.id === id);
|
|
671
|
+
if (!r) return refuse(res, 404, 'connection_request_not_found', 'connection_request_not_found');
|
|
672
|
+
g.incoming = g.incoming.filter((x) => x !== r);
|
|
673
|
+
if (verb === 'decline') { res.writeHead(204, { 'cache-control': 'no-store' }); return res.end(); }
|
|
674
|
+
g.connections = [{ id: r.id, connected_at: new Date().toISOString(), handle: r.handle, display_name: r.display_name, avatar_url: r.avatar_url }].concat(g.connections || []);
|
|
675
|
+
return json(res, 200, { status: 'accepted' });
|
|
676
|
+
}
|
|
677
|
+
function connRemove(res, id) {
|
|
678
|
+
const g = graph();
|
|
679
|
+
if (g.inactive) return connInactive(res);
|
|
680
|
+
for (const [k, outcome] of [['connections', 'removed'], ['outgoing', 'cancelled']]) {
|
|
681
|
+
const r = /^\d{1,19}$/.test(id) && (g[k] || []).find((x) => x.id === id);
|
|
682
|
+
if (r) { g[k] = g[k].filter((x) => x !== r); return json(res, 200, { outcome }); }
|
|
683
|
+
}
|
|
684
|
+
return refuse(res, 404, 'connection_not_found', 'connection_not_found');
|
|
685
|
+
}
|
|
686
|
+
|
|
601
687
|
const server = http.createServer((req, res) => {
|
|
602
688
|
const u = new URL(req.url, 'http://x');
|
|
603
689
|
const p = u.pathname;
|
|
@@ -624,7 +710,16 @@ const server = http.createServer((req, res) => {
|
|
|
624
710
|
if (guild && guild[2] === '/members' && req.method === 'GET') return AUTH ? guildRead(res, guild[1], 'members', origin) : json(res, 401, UNAUTH);
|
|
625
711
|
if (guild && guild[2] === '/membership' && req.method === 'PATCH') return AUTH ? readBody(req, (body) => guildMembershipPatch(res, guild[1], body)) : json(res, 401, UNAUTH);
|
|
626
712
|
if (guild && guild[2] === '/engagements' && req.method === 'POST') return AUTH ? readBody(req, (body) => guildEngagementPost(res, guild[1], body)) : json(res, 401, UNAUTH);
|
|
713
|
+
if (rest === '/me/guilds' && req.method === 'GET') return AUTH ? myGuilds(res) : json(res, 401, UNAUTH);
|
|
627
714
|
if (rest === '/me/guild-requests' && req.method === 'GET') return AUTH ? myGuildRequests(res) : json(res, 401, UNAUTH);
|
|
715
|
+
if (rest === '/connections' && req.method === 'GET') return AUTH ? connRead(res, 'list', origin) : json(res, 401, UNAUTH);
|
|
716
|
+
if (rest === '/connections/requests' && req.method === 'GET') return AUTH ? connRead(res, 'requests', origin) : json(res, 401, UNAUTH);
|
|
717
|
+
if (rest === '/connections/suggestions' && req.method === 'GET') return AUTH ? connRead(res, 'suggestions', origin) : json(res, 401, UNAUTH);
|
|
718
|
+
if (rest === '/connections' && req.method === 'POST') return AUTH ? readBody(req, (body) => connAsk(res, body)) : json(res, 401, UNAUTH);
|
|
719
|
+
const connAct = rest.match(/^\/connections\/([^/]+)\/(accept|decline)$/);
|
|
720
|
+
if (connAct && req.method === 'POST') return AUTH ? connAnswer(res, connAct[1], connAct[2]) : json(res, 401, UNAUTH);
|
|
721
|
+
const connDel = rest.match(/^\/connections\/([^/]+)$/);
|
|
722
|
+
if (connDel && req.method === 'DELETE') return AUTH ? connRemove(res, connDel[1]) : json(res, 401, UNAUTH);
|
|
628
723
|
const answer = rest.match(/^\/guild-requests\/([^/]+)\/(accept|decline)$/);
|
|
629
724
|
if (answer && req.method === 'POST') return AUTH ? readBody(req, (body) => guildRequestAnswer(res, answer[1], answer[2], body)) : json(res, 401, UNAUTH);
|
|
630
725
|
if (rest === '/provisioning/instances' && req.method === 'GET') return AUTH ? instances(res, null, false) : json(res, 401, UNAUTH);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.114",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.114",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.114",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -9530,5 +9530,133 @@
|
|
|
9530
9530
|
"id": "1004669",
|
|
9531
9531
|
"text": "The security record for the main branch now says what is actually true. It used to say anyone with access could push straight to the main code line. That stopped being true in August: now every change from a regular collaborat"
|
|
9532
9532
|
}
|
|
9533
|
+
],
|
|
9534
|
+
"1.21.113": [
|
|
9535
|
+
{
|
|
9536
|
+
"id": "1004679",
|
|
9537
|
+
"text": "When a project turns automatic pickup on, the background runner now picks up the oldest page an artist submitted and applies it, before its other work, then leaves it waiting for the artist. It stops at the monthly limit, leav"
|
|
9538
|
+
}
|
|
9539
|
+
],
|
|
9540
|
+
"1.21.114": [
|
|
9541
|
+
{
|
|
9542
|
+
"id": "1004722",
|
|
9543
|
+
"text": "New core releases can be cut again. Since the living-city prototype merged, every merge to main failed its release check, so no new core version went out and cloudbongos.com stayed on an old build. The cause was one security t"
|
|
9544
|
+
},
|
|
9545
|
+
{
|
|
9546
|
+
"id": "1004728",
|
|
9547
|
+
"text": "Shipping work from a project that keeps its Bongos core in a downloaded package no longer gets stuck at the final merge step."
|
|
9548
|
+
},
|
|
9549
|
+
{
|
|
9550
|
+
"id": "1004702",
|
|
9551
|
+
"text": "Decisions now open as light glass panels right where they belong, built to the mocks you approved. The deck no longer hides the street: each question sits beside the building that asks, with a thin line to its pin, the rest of"
|
|
9552
|
+
},
|
|
9553
|
+
{
|
|
9554
|
+
"id": "1002846",
|
|
9555
|
+
"text": "The ship-news channel is back on: it now gets one plain headline for each new feature or bug fix, while the builders' ship-feed keeps logging every ship."
|
|
9556
|
+
},
|
|
9557
|
+
{
|
|
9558
|
+
"id": "1004260",
|
|
9559
|
+
"text": "When you make your account private or hide your numbers, your account page now tells you that your guilds still count your numbers in their rounded totals, and links to each guild's page where you can switch that off."
|
|
9560
|
+
},
|
|
9561
|
+
{
|
|
9562
|
+
"id": "1004673",
|
|
9563
|
+
"text": "When a project's owner reviews people asking to join, a confirmed applicant's platform name now shows even if the platform is down, and the Discord approval prompt is re-posted with the real applicant's words when they take ov"
|
|
9564
|
+
},
|
|
9565
|
+
{
|
|
9566
|
+
"id": "1003649",
|
|
9567
|
+
"text": "The landing's dark mode now loads its chrome artwork about 200 KB lighter (447 KB down to 247 KB on first paint) with no visible change, so the page appears faster on slow connections."
|
|
9568
|
+
},
|
|
9569
|
+
{
|
|
9570
|
+
"id": "1003639",
|
|
9571
|
+
"text": "The onboarding diagrams now match the builders' hall in dark mode instead of sitting there as a bright white box, and they still look the same in light mode."
|
|
9572
|
+
},
|
|
9573
|
+
{
|
|
9574
|
+
"id": "1002536",
|
|
9575
|
+
"text": "Builders get a new #ai-news Discord channel for AI-tooling news from a short, curated list of feeds, at no running cost to us. An Archon switches it on by following the new setup guide."
|
|
9576
|
+
},
|
|
9577
|
+
{
|
|
9578
|
+
"id": "1003645",
|
|
9579
|
+
"text": "A check in the project page's test kit now really proves that \"Let the platform choose again\" resets a project's planet, instead of passing either way."
|
|
9580
|
+
},
|
|
9581
|
+
{
|
|
9582
|
+
"id": "1004279",
|
|
9583
|
+
"text": "On a project's hall, your profile's Connections tab now shows only the people you're connected with who also build on that project. Your requests and everyone else are one click away on your Connections page on the hub."
|
|
9584
|
+
},
|
|
9585
|
+
{
|
|
9586
|
+
"id": "1004278",
|
|
9587
|
+
"text": "You can now manage your connections on cloudbongos.com: a new Connections page (in your profile menu) shows who wants to connect, the builders you know across every project, and builders you may know, and lets you ask anyone b"
|
|
9588
|
+
},
|
|
9589
|
+
{
|
|
9590
|
+
"id": "1004590",
|
|
9591
|
+
"text": "The Projects menu now says \"Your projects: the ones you own\". The projects you help build moved to your Account page, so the old \"own or build on\" was only half true."
|
|
9592
|
+
},
|
|
9593
|
+
{
|
|
9594
|
+
"id": "1004720",
|
|
9595
|
+
"text": "The guide to your project's government now tells the whole story in plain words. It opens with the idea behind it: government is how a project steers its AI. A one-minute overview names the five parts: laws (the standing order"
|
|
9596
|
+
},
|
|
9597
|
+
{
|
|
9598
|
+
"id": "1004719",
|
|
9599
|
+
"text": "The government walk now proves the new government end to end: 169 checks, all passing, up from 123. It goes through the real routes for laws (passed by a vote, handed to every AI session, a locked setting refused and read back"
|
|
9600
|
+
},
|
|
9601
|
+
{
|
|
9602
|
+
"id": "1004718",
|
|
9603
|
+
"text": "The street and the city got their polish pass. On a wide screen the street now sits right on its six readings with no empty black sky above the skyline. Inside a building, the floor you pick is drawn in ink, not salmon, so sal"
|
|
9604
|
+
},
|
|
9605
|
+
{
|
|
9606
|
+
"id": "1004717",
|
|
9607
|
+
"text": "The city and the street now run on your project's real government only. The sample data the prototype leaned on is deleted: no invented Leads, laws, pots, building work or reworded questions, and no \"prototype\" tags or keys an"
|
|
9608
|
+
},
|
|
9609
|
+
{
|
|
9610
|
+
"id": "1004716",
|
|
9611
|
+
"text": "The government numbers now come from one place on the server. \"To do\", \"wait on others\", \"to decide\", \"agents working\" and \"moving\" are counted once by a new server read, and the city, the street header, the decision deck and"
|
|
9612
|
+
},
|
|
9613
|
+
{
|
|
9614
|
+
"id": "1004715",
|
|
9615
|
+
"text": "Strictness presets are real. A project can now pick how strict its AI agents run: Open workshop (one law: land only, going live waits for a person), Guild (the standard set: also \"stuck? note it and move on\" and agents stay on"
|
|
9616
|
+
},
|
|
9617
|
+
{
|
|
9618
|
+
"id": "1004714",
|
|
9619
|
+
"text": "When an AI agent needs a person to decide something, it now has to ask like a person: one short question, two or three answers to tap, its pick and why, who it waits on, and what happens if nobody answers. The server refuses a"
|
|
9620
|
+
},
|
|
9621
|
+
{
|
|
9622
|
+
"id": "1004712",
|
|
9623
|
+
"text": "The treasury is real. Whoever runs the Treasury (its Lead, or you while it has none) can give a goal a monthly pot. Each pot fills from the project's real AI spend on that goal's work this month. The reserve is last month's sp"
|
|
9624
|
+
},
|
|
9625
|
+
{
|
|
9626
|
+
"id": "1004711",
|
|
9627
|
+
"text": "Whoever holds a building (its Lead, or the owner when it has none) can now really grow it: a committee room, a new floor, an annex, or a district. If the form of government allows it, it goes up at once and the city shows scaf"
|
|
9628
|
+
},
|
|
9629
|
+
{
|
|
9630
|
+
"id": "1004713",
|
|
9631
|
+
"text": "Governing is now a seat you hold, not a craft you pick. The server works out which government seats each builder holds: the owner seat, a building Lead, a seat on a building's own sitting, or a board seat. It sends that list w"
|
|
9632
|
+
},
|
|
9633
|
+
{
|
|
9634
|
+
"id": "1004705",
|
|
9635
|
+
"text": "Every question an agent puts to a person now waits on a real seat. The server decides who each open matter is for: the building's Lead if it has one, the owner when it has none, the voters for a board vote, or whoever the agen"
|
|
9636
|
+
},
|
|
9637
|
+
{
|
|
9638
|
+
"id": "1004710",
|
|
9639
|
+
"text": "Whether a building can be built out without a vote now comes from your form of government, not from anything a Lead's charter says. Under a Monarchy whoever holds a building builds a room, floor or annex at once; under a Timoc"
|
|
9640
|
+
},
|
|
9641
|
+
{
|
|
9642
|
+
"id": "1004709",
|
|
9643
|
+
"text": "Each of the nine buildings in your project's city can now have a real Lead: one person in charge, with a one-line charter saying what they answer for. A Lead is appointed, replaced or removed only by a vote (a People matter)."
|
|
9644
|
+
},
|
|
9645
|
+
{
|
|
9646
|
+
"id": "1004704",
|
|
9647
|
+
"text": "AI agents now have to ask you things the way a person would. When an agent files something that is stuck on you, it must send a short card: one question of twelve words or fewer, two or three answers to tap, the answer it reco"
|
|
9648
|
+
},
|
|
9649
|
+
{
|
|
9650
|
+
"id": "1004707",
|
|
9651
|
+
"text": "Every AI session on your project now reads the project's laws first, before anyone's own settings. The server writes the words, so nobody's local files can change or drop them, and passing or repealing a law changes what the n"
|
|
9652
|
+
},
|
|
9653
|
+
{
|
|
9654
|
+
"id": "1004706",
|
|
9655
|
+
"text": "Your project can now have laws: short one-line rules every AI agent follows, like \"Ask like a human\". A law can also lock a personal setting it overrides. Laws change only the way the constitution does: someone proposes one (o"
|
|
9656
|
+
},
|
|
9657
|
+
{
|
|
9658
|
+
"id": "1004701",
|
|
9659
|
+
"text": "The living city is now the hall's base: the city, the street with depth, the decision deck, the government inside each building, and My agents. On a real project it shows only what the real data supports: no invented Leads, la"
|
|
9660
|
+
}
|
|
9533
9661
|
]
|
|
9534
9662
|
}
|
|
@@ -45,8 +45,11 @@ node scripts/discord/post.js --channel ship_feed "shipped #607" # -> #shi
|
|
|
45
45
|
echo "hello team" | node scripts/discord/post.js
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
Ship announcements do **not** come through this CLI any more (task 1749): the
|
|
49
|
+
server posts them once the deploy lands, from
|
|
50
|
+
`modules/discord/ship-broadcast.js` — every ship to `#ship-feed`, and a plain
|
|
51
|
+
headline to `#ship-news` only for a feature or a bug fix (task 1002846). This
|
|
52
|
+
CLI carries the project's other news, which defaults to `#ship-news` (a builder
|
|
53
|
+
graduating, a rank change, an outage heads-up); builder housekeeping passes
|
|
54
|
+
`--channel ship_feed`. Every call is best-effort: a Discord failure logs a
|
|
55
|
+
warning and never blocks anything.
|
package/scripts/discord/post.js
CHANGED
|
@@ -9,7 +9,18 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Default channel: ship_news — the plain-language, business-facing feed that
|
|
11
11
|
// replaced the Google Chat broadcast. Exits non-zero on failure so callers can
|
|
12
|
-
// treat the post as best-effort
|
|
12
|
+
// treat the post as best-effort.
|
|
13
|
+
//
|
|
14
|
+
// The default stays ship_news on purpose (task 1002846). SHIP announcements
|
|
15
|
+
// never pass through here: the server posts them from
|
|
16
|
+
// modules/discord/ship-broadcast.js, whose kind filter decides which ships are
|
|
17
|
+
// news. What does pass through here is the project's own news in plain words,
|
|
18
|
+
// which is what #ship-news promises: a builder graduating
|
|
19
|
+
// (modules/onboarding/onboarding-state.js), a rank change
|
|
20
|
+
// (src/bongos/routes/builders.js), an outage heads-up
|
|
21
|
+
// (modules/provisioning/pollers/liveness-notify.js) and the style-guide audit
|
|
22
|
+
// (scripts/cron/style_guide_audit.sh). Builder housekeeping names its channel:
|
|
23
|
+
// scripts/gds/pulse.js's reclaim nudge passes --channel ship_feed.
|
|
13
24
|
|
|
14
25
|
const fs = require('node:fs');
|
|
15
26
|
const { postWebhook } = require('./lib');
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
const { execFileSync } = require('node:child_process');
|
|
56
56
|
const { apiCall, arg, hasFlag } = require('./cli-lib');
|
|
57
57
|
const { TASK_LIST_MAX_LIMIT, buildTaskPageQuery } = require('../../modules/lifecycle/task-list-limits.js');
|
|
58
|
+
const { postWithAsk } = require('./ask.js');
|
|
58
59
|
|
|
59
60
|
// The statuses where a claimed artifact is supposed to already exist. `shipped` is the one the
|
|
60
61
|
// reconciler never revisits, and the reason this file exists.
|
|
@@ -172,7 +173,7 @@ module.exports = {
|
|
|
172
173
|
auditArtifacts, declaresArtifact, taskIdsIn,
|
|
173
174
|
AUDITED_STATUSES, ASSURANCE_GATE_DATE, TASK_REF,
|
|
174
175
|
messageEvidence, contentEvidence, fetchAuditedTasks,
|
|
175
|
-
blockerSourceRef, blockerBody, fileBlocker,
|
|
176
|
+
blockerSourceRef, blockerBody, blockerAsk, fileBlocker,
|
|
176
177
|
};
|
|
177
178
|
|
|
178
179
|
// ---- the human surface ------------------------------------------------------------------
|
|
@@ -206,14 +207,30 @@ function blockerBody(stranded) {
|
|
|
206
207
|
return lines.join('\n');
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
// The card a person answers (task 1004704): the finding as an ask, short enough
|
|
211
|
+
// to decide at a glance; the body above carries the list.
|
|
212
|
+
function blockerAsk(stranded) {
|
|
213
|
+
const n = stranded.length;
|
|
214
|
+
return {
|
|
215
|
+
question: `Check by hand the ${n} shipped task${n === 1 ? '' : 's'} nothing on main names?`,
|
|
216
|
+
options: [
|
|
217
|
+
{ key: 'check', label: 'Check each', result: 'Someone reads each by hand before anything is reopened.', act: 'go ahead' },
|
|
218
|
+
{ key: 'accept', label: 'Accept them', result: 'They stay shipped and credited as they are.', act: 'not now' },
|
|
219
|
+
],
|
|
220
|
+
recommend: { key: 'check', why: 'A credited task with no code may be paid work that does not exist.' },
|
|
221
|
+
waits_on: 'lead:review',
|
|
222
|
+
if_nobody_answers: 'They stay shipped and credited, unchecked.',
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
209
226
|
async function fileBlocker(stranded, { call = apiCall } = {}) {
|
|
210
227
|
if (!stranded.length) return { filed: false, reason: 'nothing to report' };
|
|
211
|
-
const r = await call('POST', '/api/bongos/blockers', {
|
|
228
|
+
const r = await postWithAsk((body) => call('POST', '/api/bongos/blockers', body), {
|
|
212
229
|
title: `${stranded.length} shipped task(s) claim an artifact nothing on main names`,
|
|
213
230
|
body_md: blockerBody(stranded),
|
|
214
231
|
source: 'artifact-audit',
|
|
215
232
|
source_ref: blockerSourceRef(stranded),
|
|
216
|
-
});
|
|
233
|
+
}, blockerAsk(stranded));
|
|
217
234
|
if (!r || !r.ok) return { filed: false, reason: `POST /blockers → ${r && r.status}` };
|
|
218
235
|
if (r.data && r.data.skipped) return { filed: false, reason: 'already filed for this exact finding set' };
|
|
219
236
|
return { filed: true, id: r.data && r.data.blocker && r.data.blocker.id };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
//
|
|
4
|
+
// scripts/gds/ask.js — the rewrite helper an agent runs BEFORE it asks a person
|
|
5
|
+
// anything (task 1004704, goal 1000111: "agents must ask like humans").
|
|
6
|
+
//
|
|
7
|
+
// WHY. The owner's ruling: an AI agent asks a person the way a person would —
|
|
8
|
+
// short, decidable, no slop. The shape is the ask (modules/government/asks.js,
|
|
9
|
+
// task 1004703): one question of at most twelve words, two or three tap answers
|
|
10
|
+
// each with a one-line result, a pick and why, the seat it waits on, what
|
|
11
|
+
// happens if nobody answers; the long text rides along as `detail`. The server
|
|
12
|
+
// refuses anything else, and a blocker an AI session files by hand without one
|
|
13
|
+
// is refused too (`ask_required`). This helper is the loop that turns prose into
|
|
14
|
+
// a card: write the card, check it, read every fix the server names, rewrite,
|
|
15
|
+
// check again — and only then file it, or print it.
|
|
16
|
+
//
|
|
17
|
+
// node scripts/gds/ask.js template a blank card to fill in
|
|
18
|
+
// node scripts/gds/ask.js check <file|-> the server's verdict; on a pass,
|
|
19
|
+
// the card as a person reads it
|
|
20
|
+
//
|
|
21
|
+
// `check` asks the server (POST /government/asks/check, the dry run: nothing is
|
|
22
|
+
// stored). With no session, or a server that cannot answer, it checks with the
|
|
23
|
+
// same rules locally and says so. The file holds the ask bare or as { ask }, so
|
|
24
|
+
// the body you will POST to /blockers can be checked as it is.
|
|
25
|
+
//
|
|
26
|
+
// The printed card is also what an agent puts in CHAT for an ask that is not a
|
|
27
|
+
// stored matter: the "please confirm" list at the end of a run, a review
|
|
28
|
+
// request, a help request. Same shape everywhere a person reads it.
|
|
29
|
+
//
|
|
30
|
+
// For scripts that file templated blockers: `checkedAsk` and `postWithAsk`
|
|
31
|
+
// below. A templated card is checked here before it is sent, and a filing whose
|
|
32
|
+
// card is refused is re-sent without it — a script's blocker always files.
|
|
33
|
+
|
|
34
|
+
const fs = require('node:fs');
|
|
35
|
+
const path = require('node:path');
|
|
36
|
+
|
|
37
|
+
const asks = require(path.join(__dirname, '..', '..', 'modules', 'government', 'asks.js'));
|
|
38
|
+
|
|
39
|
+
const TEMPLATE = Object.freeze({
|
|
40
|
+
question: 'One question, twelve words at most?',
|
|
41
|
+
options: [
|
|
42
|
+
{ key: 'yes', label: 'Do it', result: 'One line: what happens if they pick this.' },
|
|
43
|
+
{ key: 'no', label: 'Leave it', result: 'One line: what happens if they pick this.' },
|
|
44
|
+
],
|
|
45
|
+
recommend: { key: 'yes', why: 'One line: why you would pick it.' },
|
|
46
|
+
waits_on: 'owner',
|
|
47
|
+
if_nobody_answers: 'One line: what happens if nobody answers.',
|
|
48
|
+
detail: 'Optional. The long text rides along here; it is never the ask.',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// The ask in a body: bare, or as { ask } (the shape POST /blockers takes).
|
|
52
|
+
function askOf(body) {
|
|
53
|
+
return body && typeof body === 'object' && !Array.isArray(body) && body.ask !== undefined ? body.ask : body;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A templated card, checked with the server's own rules. → the stored form, or
|
|
57
|
+
// null when it breaks a rule (the caller files without it rather than not at all).
|
|
58
|
+
function checkedAsk(ask) {
|
|
59
|
+
const r = asks.checkAsk(ask);
|
|
60
|
+
return r.ok ? r.ask : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// File a blocker with its card. `post(body)` → { ok, status, data } (the shape
|
|
64
|
+
// cli-lib's apiCall and the generated client both answer). A card that breaks a
|
|
65
|
+
// rule is never sent; a server that refuses the card or cannot check it gets the
|
|
66
|
+
// same blocker again without it, so the filing never depends on the card.
|
|
67
|
+
const CARD_REFUSALS = new Set(['ask_refused', 'asks_unavailable', 'validation_failed']);
|
|
68
|
+
async function postWithAsk(post, body, ask) {
|
|
69
|
+
const card = ask ? checkedAsk(ask) : null;
|
|
70
|
+
if (!card) return post(body);
|
|
71
|
+
const r = await post({ ...body, ask: card });
|
|
72
|
+
const code = r && r.data && r.data.error && r.data.error.code;
|
|
73
|
+
if (r && !r.ok && (r.status === 400 || r.status === 503) && CARD_REFUSALS.has(code)) return post(body);
|
|
74
|
+
return r;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The verdict as plain lines. ok → the card; refused → the refusal and each fix.
|
|
78
|
+
function verdictLines({ ok, ask, message, errors, where }) {
|
|
79
|
+
const out = [];
|
|
80
|
+
if (ok) {
|
|
81
|
+
out.push(`✓ This ask has the shape${where === 'local' ? ' (checked here; the server could not be asked)' : ''}. As a person reads it:`);
|
|
82
|
+
out.push('');
|
|
83
|
+
out.push(asks.cardText(ask));
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
out.push(`✗ ${message || asks.refusalLine(errors)}`);
|
|
87
|
+
if (where === 'local') out.push(' (checked here; the server could not be asked)');
|
|
88
|
+
const list = Array.isArray(errors) ? errors : [];
|
|
89
|
+
if (list.length) {
|
|
90
|
+
out.push('');
|
|
91
|
+
out.push('Fix each, then check again:');
|
|
92
|
+
for (const e of list) out.push(` - ${e.field}: ${e.message}`);
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readJson(file) {
|
|
98
|
+
const raw = file === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(file, 'utf8');
|
|
99
|
+
return JSON.parse(raw.replace(/^/, ''));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The server's verdict, or null when it could not give one (no session, offline,
|
|
103
|
+
// no government module, not allowed) — the caller then checks locally.
|
|
104
|
+
async function serverVerdict(ask, { client } = {}) {
|
|
105
|
+
let api = client;
|
|
106
|
+
try {
|
|
107
|
+
if (!api) {
|
|
108
|
+
const lib = require('./cli-lib');
|
|
109
|
+
if (!lib.readSessionToken()) return null;
|
|
110
|
+
api = await lib.cliClient({ allowUnauthenticated: true });
|
|
111
|
+
}
|
|
112
|
+
const r = await api.request('POST', '/government/asks/check', { body: { ask } });
|
|
113
|
+
if (r && r.ok && r.data && r.data.ok) return { ok: true, ask: r.data.ask, where: 'server' };
|
|
114
|
+
const err = r && r.data && r.data.error;
|
|
115
|
+
if (r && r.status === 400 && err && err.code === 'ask_refused') {
|
|
116
|
+
return { ok: false, message: err.message, errors: (err.details && err.details.errors) || [], where: 'server' };
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
} catch (_) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function check(file, deps = {}) {
|
|
125
|
+
let body;
|
|
126
|
+
try { body = readJson(file); } catch (e) {
|
|
127
|
+
return { code: 2, lines: [`ask: could not read the card as JSON (${e.message}). Start from: node scripts/gds/ask.js template`] };
|
|
128
|
+
}
|
|
129
|
+
const ask = askOf(body);
|
|
130
|
+
let v = deps.local ? null : await serverVerdict(ask, deps);
|
|
131
|
+
if (!v) {
|
|
132
|
+
const r = asks.checkAsk(ask);
|
|
133
|
+
v = { ok: r.ok, ask: r.ask, errors: r.errors, where: 'local' };
|
|
134
|
+
}
|
|
135
|
+
return { code: v.ok ? 0 : 1, lines: verdictLines(v) };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const USAGE = [
|
|
139
|
+
'usage:',
|
|
140
|
+
' node scripts/gds/ask.js template print a blank card to fill in',
|
|
141
|
+
' node scripts/gds/ask.js check <file|-> check a card; on a pass, print it as a person reads it',
|
|
142
|
+
' --local check with the rules here, without the server',
|
|
143
|
+
].join('\n');
|
|
144
|
+
|
|
145
|
+
async function main(argv = process.argv.slice(2)) {
|
|
146
|
+
const cmd = argv[0];
|
|
147
|
+
if (cmd === 'template') {
|
|
148
|
+
console.log(JSON.stringify(TEMPLATE, null, 2));
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
if (cmd === 'check' && argv[1]) {
|
|
152
|
+
const r = await check(argv[1], { local: argv.includes('--local') });
|
|
153
|
+
console.log(r.lines.join('\n'));
|
|
154
|
+
return r.code;
|
|
155
|
+
}
|
|
156
|
+
console.error(USAGE);
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (require.main === module) {
|
|
161
|
+
main().then((code) => { process.exitCode = code; }, (e) => { console.error(`ask: ${e && e.message}`); process.exitCode = 1; });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { TEMPLATE, askOf, checkedAsk, postWithAsk, verdictLines, serverVerdict, check, main };
|
|
@@ -45,6 +45,7 @@ const {
|
|
|
45
45
|
const { verifyShip, failureReason, recheckLands, pendingLandIds } = require('./autobongos-verify.js');
|
|
46
46
|
const gradeCap = require('./autobongos-grade-cap.js');
|
|
47
47
|
const fence = require('../../modules/autonomy/fence.js');
|
|
48
|
+
const tweakLane = require('./autobongos-tweak-lane.js');
|
|
48
49
|
// The ADR 0043 protected-path registry, reused rather than re-derived. scripts/
|
|
49
50
|
// sits outside the kernel boundary, so this direct core require is allowed here
|
|
50
51
|
// where it would not be inside modules/ — which is exactly why fence.js takes the
|
|
@@ -346,13 +347,13 @@ async function gradeCapReader({ api, taskId, ledger }) {
|
|
|
346
347
|
|
|
347
348
|
// spawnWorker — one headless session, bounded by WALL CLOCK, in the task's own
|
|
348
349
|
// working tree.
|
|
349
|
-
async function spawnWorker({ task, model, timeoutMs, cwd, gradeLedger = null, capCheck = null, deps = {} }) {
|
|
350
|
+
async function spawnWorker({ task, model, timeoutMs, cwd, gradeLedger = null, capCheck = null, prompt: givenPrompt = null, deps = {} }) {
|
|
350
351
|
const spawnFn = deps.spawn || spawn;
|
|
351
352
|
const sessionId = deps.sessionId || crypto.randomUUID(); // pinned, so the session can be resumed
|
|
352
353
|
const { bin, shell: workerShell } = deps.resolveBin ? deps.resolveBin() : resolveWorkerBin();
|
|
353
354
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
354
355
|
const args = buildWorkerArgs({ sessionId, model, isRoot });
|
|
355
|
-
const prompt = buildWorkerPrompt({ task });
|
|
356
|
+
const prompt = givenPrompt || buildWorkerPrompt({ task }); // the tweak lane names its own (task 1004679)
|
|
356
357
|
|
|
357
358
|
return new Promise((resolve) => {
|
|
358
359
|
let child;
|
|
@@ -547,9 +548,13 @@ async function iteration(opts, deps = {}) {
|
|
|
547
548
|
// always injected deps.run. Caught by the grader on task 1003908; the test that
|
|
548
549
|
// now drives iteration() with no deps is the actual fix — this rename is half.
|
|
549
550
|
const gate = fence.decideRun({ fence: raw, requestedGoals: opts.goals, graderBypassed });
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
551
|
+
// ADR 0371 D3 (task 1004679): the tweak lane's own setting replaces the GOAL
|
|
552
|
+
// ALLOWLIST for that lane only, so a fence that refused on goals alone still lets
|
|
553
|
+
// the lane run below; the kill switch and the grader stop refuse it here.
|
|
554
|
+
const fencedRow = () => log({ event: 'fenced', code: gate.code, reason: gate.reason, refused_goals: gate.refusedGoals || [] });
|
|
555
|
+
const goalsOnly = !gate.go && tweakLane.GOAL_ONLY_CODES.has(gate.code)
|
|
556
|
+
&& await (deps.laneSwitchOn || tweakLane.laneSwitchOn)(deps.api);
|
|
557
|
+
if (!gate.go && !goalsOnly) return fencedRow();
|
|
553
558
|
|
|
554
559
|
// 1. The gauge. Never start a task we cannot pay for out of the window.
|
|
555
560
|
const g = readGauge();
|
|
@@ -588,6 +593,16 @@ async function iteration(opts, deps = {}) {
|
|
|
588
593
|
}
|
|
589
594
|
for (const [id, until] of state.setAside) if (until <= now) state.setAside.delete(id);
|
|
590
595
|
|
|
596
|
+
// 1d. THE TWEAK LANE (task 1004679, ADR 0371 D1): before ordinary work, the
|
|
597
|
+
// oldest submitted page round the lane may take. Null means it took none.
|
|
598
|
+
const laneRow = await (deps.tweakLaneStep || tweakLane.tweakLaneStep)({ opts, state }, {
|
|
599
|
+
api: deps.api || await (require('./cli-lib').cliClient)(), log, matchProtected, runCmd, spawnWork,
|
|
600
|
+
worktreeName: deps.worktreeName || worktreeName, spawnDeps: deps, gradeCapReader,
|
|
601
|
+
gradeLedgerPath: deps.gradeLedgerPath || gradeLedgerPath, ...(deps.laneDeps || {}),
|
|
602
|
+
});
|
|
603
|
+
if (laneRow) return laneRow;
|
|
604
|
+
if (goalsOnly) return fencedRow();
|
|
605
|
+
|
|
591
606
|
// 2–4, up to MAX_CLAIM_ATTEMPTS times: pick, fence, tree, claim. A task refused
|
|
592
607
|
// on its own is set aside and the NEXT one is tried in the same pass.
|
|
593
608
|
let task = null; let picked = null; let wtName = null; let wtPath = null;
|