@bongos/core 1.21.113 → 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 +603 -183
- 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 +2 -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 +122 -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-verify.js +21 -2
- 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/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/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
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// tests/government_laws_session.mjs — the project's laws are read into every AI
|
|
2
|
+
// session, and a law's lock on a personal setting is ENFORCED (task 1004707,
|
|
3
|
+
// goal 1000111 "government is how a project steers its AI").
|
|
4
|
+
//
|
|
5
|
+
// What this pins (the task's three done-when lines, and the walls behind them):
|
|
6
|
+
// 1. A SESSION ON A PROJECT WITH LAWS SHOWS THE LAWS BLOCK. The government
|
|
7
|
+
// port's lawsForSession turns the laws in force into one numbered block;
|
|
8
|
+
// GET /me carries it (static wiring pin here, the hook run for real in
|
|
9
|
+
// tests/conductor_main_e2e.mjs); the Conductor relays it verbatim, ahead of
|
|
10
|
+
// the builder's own contracts. No law in force → no block, no context spent.
|
|
11
|
+
// 2. CHANGING A LAW CHANGES WHAT THE NEXT SESSION READS. The port reads the
|
|
12
|
+
// book per call (no cache), and the Conductor's advisory id carries a hash
|
|
13
|
+
// of the words, so a changed law re-emits even inside one session.
|
|
14
|
+
// 3. A LOCK IS ENFORCED, NOT JUST SHOWN. On every READ the effective value
|
|
15
|
+
// obeys the lock (a value stored before the law passed included) and the
|
|
16
|
+
// injected contracts say the locked value; on every WRITE a value the lock
|
|
17
|
+
// does not allow is refused 409 and nothing is saved; with the laws
|
|
18
|
+
// unreadable a write is refused 503, never let through on a guess. The rank
|
|
19
|
+
// still caps wandering: a law cannot lift a builder past it.
|
|
20
|
+
//
|
|
21
|
+
// DB-free: db reads/writes are monkeypatched by property.
|
|
22
|
+
//
|
|
23
|
+
// Run: node --test tests/government_laws_session.mjs
|
|
24
|
+
|
|
25
|
+
import { strict as assert } from 'node:assert';
|
|
26
|
+
import { test, after } from 'node:test';
|
|
27
|
+
import http from 'node:http';
|
|
28
|
+
import fs from 'node:fs';
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
import { createRequire } from 'node:module';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
|
+
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
35
|
+
const laws = require(path.join(ROOT, 'modules', 'government', 'laws.js'));
|
|
36
|
+
const govDb = require(path.join(ROOT, 'modules', 'government', 'db.js'));
|
|
37
|
+
const lawLocks = require(path.join(ROOT, 'modules', 'builder-settings', 'law-locks.js'));
|
|
38
|
+
const settingsPort = require(path.join(ROOT, 'modules', 'builder-settings', 'settings.js'));
|
|
39
|
+
const interactionPrefs = require(path.join(ROOT, 'modules', 'builder-settings', 'interaction-prefs.js'));
|
|
40
|
+
const wanderingPrefs = require(path.join(ROOT, 'modules', 'builder-settings', 'wandering-prefs.js'));
|
|
41
|
+
const conductor = require(path.join(ROOT, '.claude', 'hooks', 'conductor.js'));
|
|
42
|
+
|
|
43
|
+
// Law rows as db.listLaws returns them (in force, oldest first).
|
|
44
|
+
const row = (id, text, extra = {}) => ({ id, text, effect_on_ai: null, locks: [], passed_by_item_id: 90 + id, passed_at: '2026-10-09T00:00:00Z', repealed_at: null, ...extra });
|
|
45
|
+
const ASK = row(11, 'Ask like a human', { effect_on_ai: 'Every question to a person is one short card.', locks: [{ setting: 'verbosity', value: 'concise' }, { setting: 'ask_format', value: 'card' }] });
|
|
46
|
+
const LAND = row(12, 'Land only. Ask before going live', { locks: [{ setting: 'wandering', max: 'focused' }, { setting: 'model.main', max: 'sonnet' }] });
|
|
47
|
+
|
|
48
|
+
// ── 1. the block a session reads ────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
test('the laws in force become one numbered block: each law one line, its meaning and its locks beside it', () => {
|
|
51
|
+
const s = laws.forSession([ASK, LAND]);
|
|
52
|
+
assert.equal(s.inject, true);
|
|
53
|
+
assert.deepEqual(s.laws.map((l) => [l.id, l.no, l.text]), [['11', 1, 'Ask like a human'], ['12', 2, 'Land only. Ask before going live']]);
|
|
54
|
+
const lines = s.contract.split('\n');
|
|
55
|
+
assert.match(lines[0], /^Project laws — passed by a vote of this project's government\. Every AI agent on the project obeys them/);
|
|
56
|
+
assert.match(lines[0], /come before this builder's own settings/);
|
|
57
|
+
assert.equal(lines[1], '1. Ask like a human — Every question to a person is one short card. (locks: verbosity is fixed at concise; ask_format is fixed at card)');
|
|
58
|
+
assert.equal(lines[2], '2. Land only. Ask before going live (locks: wandering is at most focused; model.main is at most sonnet)');
|
|
59
|
+
assert.equal(lines.length, 3, 'one line per law, nothing else');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('no law in force → no block at all (a project with no laws spends no context); a repealed law is never read', () => {
|
|
63
|
+
assert.deepEqual(laws.forSession([]), { laws: [], contract: '', inject: false });
|
|
64
|
+
assert.deepEqual(laws.forSession(null), { laws: [], contract: '', inject: false });
|
|
65
|
+
const s = laws.forSession([row(3, 'Gone now', { repealed_at: '2026-10-01T00:00:00Z' }), ASK]);
|
|
66
|
+
assert.deepEqual(s.laws.map((l) => l.id), ['11']);
|
|
67
|
+
assert.doesNotMatch(s.contract, /Gone now/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('the government port reads the book on EVERY call — a law passed or repealed is in the next session', async () => {
|
|
71
|
+
const { governmentPort } = require(path.join(ROOT, 'modules', 'government', 'routes', 'government.js'));
|
|
72
|
+
const orig = govDb.listLaws;
|
|
73
|
+
let book = [ASK];
|
|
74
|
+
let reads = 0;
|
|
75
|
+
govDb.listLaws = async (opts) => { reads += 1; assert.ok(!opts || !opts.includeRepealed, 'in force only'); return book; };
|
|
76
|
+
try {
|
|
77
|
+
const first = await governmentPort.lawsForSession();
|
|
78
|
+
assert.match(first.contract, /Ask like a human/);
|
|
79
|
+
book = [ASK, LAND];
|
|
80
|
+
const second = await governmentPort.lawsForSession();
|
|
81
|
+
assert.match(second.contract, /Land only/);
|
|
82
|
+
book = [];
|
|
83
|
+
const third = await governmentPort.lawsForSession();
|
|
84
|
+
assert.equal(third.inject, false);
|
|
85
|
+
assert.equal(reads, 3, 'no cache between sessions');
|
|
86
|
+
} finally { govDb.listLaws = orig; }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('GET /me carries the block and resolves the settings slices through the locks (wiring pin)', () => {
|
|
90
|
+
const src = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'routes', 'me.js'), 'utf8');
|
|
91
|
+
assert.match(src, /government\.lawsForSession\(\)/, '/me reads the laws through the government port');
|
|
92
|
+
assert.match(src, /settings\.describeWithLaws\(behaviorPrefs, \(sessionLaws && sessionLaws\.laws\) \|\| \[\]/, 'the slices are resolved with the laws applied');
|
|
93
|
+
for (const slice of ['wandering', 'interaction', 'model_allocation']) {
|
|
94
|
+
assert.match(src, new RegExp(`${slice}: lawed \\? lawed\\.${slice} :`), `${slice} reads the locked slice`);
|
|
95
|
+
}
|
|
96
|
+
assert.match(src, /laws: sessionLaws \? \{/, '/me answers a laws block');
|
|
97
|
+
assert.equal(typeof settingsPort.describeWithLaws, 'function');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ── 2. the Conductor relays it, and a change re-emits ──────────────────────
|
|
101
|
+
|
|
102
|
+
test('the Conductor relays the block verbatim, ahead of the builder\'s own contracts', () => {
|
|
103
|
+
const s = laws.forSession([ASK, LAND]);
|
|
104
|
+
const out = conductor.evaluateTriggers({
|
|
105
|
+
laws: { inject: s.inject, contract: s.contract },
|
|
106
|
+
interaction: { inject: true, contract: 'PROFILE' },
|
|
107
|
+
wandering: { inject: true, contract: 'FOCUS' },
|
|
108
|
+
});
|
|
109
|
+
const ids = out.map((a) => a.id);
|
|
110
|
+
const at = ids.findIndex((id) => id.startsWith('laws:'));
|
|
111
|
+
assert.ok(at >= 0, 'the laws advisory is emitted');
|
|
112
|
+
assert.equal(out[at].text, s.contract, 'verbatim');
|
|
113
|
+
assert.ok(at < ids.indexOf('wandering') && at < ids.indexOf('interaction'), 'laws come first');
|
|
114
|
+
assert.match(conductor.render(out), /Project laws/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('no laws, or laws:null (offline) → no laws advisory', () => {
|
|
118
|
+
for (const l of [null, { inject: false, contract: '' }, laws.forSession([])]) {
|
|
119
|
+
assert.ok(!conductor.evaluateTriggers({ laws: l }).some((a) => a.id.startsWith('laws:')));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('a changed law is a new advisory: the dedupe fingerprint moves, so the block re-emits inside one session', () => {
|
|
124
|
+
const a = conductor.evaluateTriggers({ laws: laws.forSession([ASK]) });
|
|
125
|
+
const b = conductor.evaluateTriggers({ laws: laws.forSession([ASK, LAND]) });
|
|
126
|
+
const c = conductor.evaluateTriggers({ laws: laws.forSession([ASK]) });
|
|
127
|
+
assert.notEqual(conductor.fingerprint(a), conductor.fingerprint(b));
|
|
128
|
+
assert.equal(conductor.fingerprint(a), conductor.fingerprint(c), 'the same words, the same id: no re-emit noise');
|
|
129
|
+
assert.match(conductor.wordsHash('Ask like a human'), /^[0-9a-f]{8}$/);
|
|
130
|
+
assert.equal(conductor.wordsHash('Ask like a human'), conductor.wordsHash('Ask like a human'));
|
|
131
|
+
assert.notEqual(conductor.wordsHash('Ask like a human'), conductor.wordsHash('Ask like a human.'), 'one character is a different law');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ── 3. the locks, enforced on the read ─────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
const BOOK = laws.forSession([ASK, LAND]).laws;
|
|
137
|
+
|
|
138
|
+
test('a FIXED value wins over the builder\'s own, and the injected contract says the law\'s value', () => {
|
|
139
|
+
const mine = { interaction: { verbosity: 'detailed', technical_level: 'engineer' } };
|
|
140
|
+
const d = lawLocks.describe(mine, BOOK, { rank: 'archon', instanceModels: {} });
|
|
141
|
+
assert.equal(d.interaction.effective.verbosity, 'concise', 'the law');
|
|
142
|
+
assert.equal(d.interaction.effective.technical_level, 'engineer', 'an unlocked setting stays the builder\'s');
|
|
143
|
+
assert.deepEqual(d.interaction.stored, mine.interaction, 'what they chose is still reported as theirs');
|
|
144
|
+
assert.match(d.interaction.contract, /Verbosity: concise/);
|
|
145
|
+
assert.doesNotMatch(d.interaction.contract, /Verbosity: detailed/, 'the session never reads the overridden value');
|
|
146
|
+
const v = d.locks.find((l) => l.setting === 'verbosity');
|
|
147
|
+
assert.deepEqual([v.enforced, v.from, v.to, v.law_id, v.law_no], [true, 'detailed', 'concise', '11', 1]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('a CAP pulls a value above it down and leaves one under it alone — wandering and the model tiers', () => {
|
|
151
|
+
const high = lawLocks.describe({ wandering: 3, model_allocation: { main: 'fable' } }, BOOK, { rank: 'archon', instanceModels: {} });
|
|
152
|
+
assert.equal(high.wandering.effective_key, 'focused');
|
|
153
|
+
assert.equal(high.wandering.override_level, 3, 'their own choice is still on record');
|
|
154
|
+
assert.equal(high.wandering.inject, true, 'the focus contract is now sent');
|
|
155
|
+
assert.equal(high.model_allocation.effective.main, 'sonnet');
|
|
156
|
+
assert.equal(high.model_allocation.override.main, 'fable');
|
|
157
|
+
const low = lawLocks.describe({ wandering: 0, model_allocation: { main: 'haiku' } }, BOOK, { rank: 'archon', instanceModels: {} });
|
|
158
|
+
assert.equal(low.wandering.effective_key, 'locked', 'under the cap: theirs');
|
|
159
|
+
assert.equal(low.model_allocation.effective.main, 'haiku');
|
|
160
|
+
// nothing stored: the default is capped too (a law binds the builder who never opened Settings)
|
|
161
|
+
const none = lawLocks.describe({}, BOOK, { rank: 'archon', instanceModels: { main: 'opus' } });
|
|
162
|
+
assert.equal(none.model_allocation.effective.main, 'sonnet');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('each enforced lock names every value it still allows — a cap the values under it, a fixed value just that one (task 1004714)', () => {
|
|
166
|
+
const d = lawLocks.describe({ wandering: 3, model_allocation: { main: 'fable' }, interaction: { verbosity: 'detailed' } }, BOOK, { rank: 'archon', instanceModels: {} });
|
|
167
|
+
const of = (k) => d.locks.find((l) => l.setting === k).allowed;
|
|
168
|
+
assert.deepEqual(of('wandering'), ['locked', 'focused'], 'wandering up to the cap, in its own order');
|
|
169
|
+
assert.deepEqual(of('model.main'), ['haiku', 'sonnet'], 'the model tiers cheap to capable, up to the cap');
|
|
170
|
+
assert.deepEqual(of('verbosity'), ['concise'], 'a fixed value allows only itself');
|
|
171
|
+
// the write check and the read report agree: what a PATCH may set is what allowed names
|
|
172
|
+
assert.deepEqual(lawLocks.checkPatch({ wandering: 'balanced' }, BOOK).allowed, of('wandering'));
|
|
173
|
+
assert.equal(lawLocks.checkPatch({ wandering: 'focused' }, BOOK), null);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('a law cannot lift a builder past their rank: wandering stays inside the rank ceiling', () => {
|
|
177
|
+
const lift = laws.forSession([row(20, 'Roam freely', { locks: [{ setting: 'wandering', value: 'exploratory' }] })]).laws;
|
|
178
|
+
const xenos = lawLocks.describe({}, lift, { rank: 'xenos', instanceModels: {} });
|
|
179
|
+
assert.equal(xenos.wandering.effective_level, wanderingPrefs.maxLevelForRank('xenos'), 'the rank has the last word');
|
|
180
|
+
const archon = lawLocks.describe({}, lift, { rank: 'archon', instanceModels: {} });
|
|
181
|
+
assert.equal(archon.wandering.effective_key, 'exploratory');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('a lock on a setting no table holds yet is reported, not invented; a value the setting does not have is not applied', () => {
|
|
185
|
+
const d = lawLocks.describe({}, laws.forSession([ASK, row(21, 'Odd words', { locks: [{ setting: 'machinery', value: 'loud' }] })]).laws, { rank: 'archon' });
|
|
186
|
+
const askFormat = d.locks.find((l) => l.setting === 'ask_format');
|
|
187
|
+
assert.deepEqual([askFormat.enforced, askFormat.reason], [false, 'not_a_personal_setting']);
|
|
188
|
+
const odd = d.locks.find((l) => l.setting === 'machinery');
|
|
189
|
+
assert.deepEqual([odd.enforced, odd.reason], [false, 'value_not_allowed']);
|
|
190
|
+
assert.equal(d.interaction.effective.machinery, interactionPrefs.defaultValue('machinery'));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('no laws → every slice exactly as before', () => {
|
|
194
|
+
const prefs = { interaction: { verbosity: 'detailed' }, wandering: 2, model_allocation: { main: 'fable' } };
|
|
195
|
+
const d = lawLocks.describe(prefs, [], { rank: 'metic', instanceModels: {} });
|
|
196
|
+
assert.deepEqual(d.interaction, interactionPrefs.describeForApi(prefs));
|
|
197
|
+
assert.deepEqual(d.wandering, wanderingPrefs.describeForApi(prefs, 'metic'));
|
|
198
|
+
assert.deepEqual(d.locks, []);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ── 4. the locks, enforced on the write — through the real router ──────────
|
|
202
|
+
|
|
203
|
+
const api = require(path.join(ROOT, 'src', 'module-api.js'));
|
|
204
|
+
const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
|
|
205
|
+
const { attachFail } = require(path.join(ROOT, 'src', 'bongos', 'middleware', 'error-envelope.js'));
|
|
206
|
+
const settingsDb = require(path.join(ROOT, 'modules', 'builder-settings', 'db.js'));
|
|
207
|
+
const express = require('express');
|
|
208
|
+
|
|
209
|
+
const realRequireBuilder = api.requireBuilder;
|
|
210
|
+
const realGetBuilder = api.getBuilderById;
|
|
211
|
+
const realGet = settingsDb.getBuilderBehaviorPrefs;
|
|
212
|
+
const realSet = settingsDb.setBuilderBehaviorPrefs;
|
|
213
|
+
api.requireBuilder = (req, res, next) => { req.builder = { id: 42, rank: 'archon' }; next(); };
|
|
214
|
+
api.getBuilderById = async () => ({ id: 42, rank: 'archon' });
|
|
215
|
+
let stored = {};
|
|
216
|
+
let saves = 0;
|
|
217
|
+
settingsDb.getBuilderBehaviorPrefs = async () => JSON.parse(JSON.stringify(stored));
|
|
218
|
+
settingsDb.setBuilderBehaviorPrefs = async (_id, next) => { saves += 1; stored = next; return next; };
|
|
219
|
+
seams._reset();
|
|
220
|
+
let lawRows = [ASK, LAND];
|
|
221
|
+
let lawsDown = false;
|
|
222
|
+
let lawReads = 0;
|
|
223
|
+
seams.registerProvider('government', {
|
|
224
|
+
lawsForSession: async () => { lawReads += 1; if (lawsDown) throw new Error('db down'); return laws.forSession(lawRows); },
|
|
225
|
+
});
|
|
226
|
+
const buildSettingsRouter = require(path.join(ROOT, 'modules', 'builder-settings', 'routes', 'settings.js'));
|
|
227
|
+
const app = express();
|
|
228
|
+
app.use(express.json());
|
|
229
|
+
app.use(attachFail);
|
|
230
|
+
app.use(buildSettingsRouter());
|
|
231
|
+
const server = http.createServer(app);
|
|
232
|
+
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
|
233
|
+
const BASE = `http://127.0.0.1:${server.address().port}`;
|
|
234
|
+
after(() => {
|
|
235
|
+
server.close();
|
|
236
|
+
api.requireBuilder = realRequireBuilder;
|
|
237
|
+
api.getBuilderById = realGetBuilder;
|
|
238
|
+
settingsDb.getBuilderBehaviorPrefs = realGet;
|
|
239
|
+
settingsDb.setBuilderBehaviorPrefs = realSet;
|
|
240
|
+
seams._reset();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
async function call(method, p, body) {
|
|
244
|
+
const res = await fetch(`${BASE}${p}`, { method, headers: body === undefined ? {} : { 'content-type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body) });
|
|
245
|
+
let json = null; try { json = await res.json(); } catch { /* none */ }
|
|
246
|
+
return { status: res.status, json };
|
|
247
|
+
}
|
|
248
|
+
function reset(prefs = {}) { stored = prefs; saves = 0; lawReads = 0; lawRows = [ASK, LAND]; lawsDown = false; }
|
|
249
|
+
|
|
250
|
+
test('GET reads the locked value and names the law; a value stored before the law passed still resolves to the law', async () => {
|
|
251
|
+
reset({ interaction: { verbosity: 'detailed' }, wandering: 3 });
|
|
252
|
+
const i = await call('GET', '/me/interaction');
|
|
253
|
+
assert.equal(i.status, 200);
|
|
254
|
+
assert.equal(i.json.effective.verbosity, 'concise');
|
|
255
|
+
assert.equal(i.json.stored.verbosity, 'detailed');
|
|
256
|
+
assert.deepEqual(i.json.law_locks.map((l) => [l.setting, l.law_id, l.enforced]), [['verbosity', '11', true]]);
|
|
257
|
+
assert.equal(i.json.laws_read, true);
|
|
258
|
+
const w = await call('GET', '/me/wandering');
|
|
259
|
+
assert.equal(w.json.effective_key, 'focused');
|
|
260
|
+
assert.deepEqual(w.json.law_locks.map((l) => l.setting), ['wandering']);
|
|
261
|
+
const m = await call('GET', '/me/model-allocation');
|
|
262
|
+
assert.equal(m.json.effective.main, 'sonnet');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test('a PATCH a law forbids is refused 409 naming the law, and NOTHING is saved', async () => {
|
|
266
|
+
for (const [p, body, setting] of [
|
|
267
|
+
['/me/interaction', { verbosity: 'detailed' }, 'verbosity'],
|
|
268
|
+
['/me/wandering', { wandering: 2 }, 'wandering'],
|
|
269
|
+
['/me/model-allocation', { main: 'opus' }, 'model.main'],
|
|
270
|
+
]) {
|
|
271
|
+
reset({});
|
|
272
|
+
const r = await call('PATCH', p, body);
|
|
273
|
+
assert.equal(r.status, 409, `${p} ${JSON.stringify(r.json)}`);
|
|
274
|
+
assert.equal(r.json.error.code || r.json.error, 'locked_by_law');
|
|
275
|
+
const det = r.json.error.details || r.json.details;
|
|
276
|
+
assert.equal(det.setting, setting);
|
|
277
|
+
assert.ok(['11', '12'].includes(det.law_id));
|
|
278
|
+
assert.equal(saves, 0, `${p}: nothing saved`);
|
|
279
|
+
const msg = r.json.error.message || r.json.message;
|
|
280
|
+
assert.ok(msg.includes(setting) && det.allowed.every((a) => msg.includes(a)), `the refusal names the setting and what is allowed: ${msg}`);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('a PATCH the law allows goes through: the locked value itself, a value under a cap, an unlocked field, a clear', async () => {
|
|
285
|
+
for (const [p, body] of [
|
|
286
|
+
['/me/interaction', { verbosity: 'concise' }],
|
|
287
|
+
['/me/interaction', { technical_level: 'engineer' }],
|
|
288
|
+
['/me/interaction', { verbosity: null }],
|
|
289
|
+
['/me/wandering', { wandering: 1 }],
|
|
290
|
+
['/me/wandering', { wandering: null }],
|
|
291
|
+
['/me/model-allocation', { main: 'haiku' }],
|
|
292
|
+
['/me/model-allocation', { subagentDefault: 'fable' }],
|
|
293
|
+
]) {
|
|
294
|
+
reset({});
|
|
295
|
+
const r = await call('PATCH', p, body);
|
|
296
|
+
assert.equal(r.status, 200, `${p} ${JSON.stringify(body)} → ${JSON.stringify(r.json)}`);
|
|
297
|
+
assert.equal(saves, 1);
|
|
298
|
+
assert.equal(lawReads, 1, `${p}: the book is read once per request`);
|
|
299
|
+
assert.ok(Array.isArray(r.json.law_locks), 'the answer reports the locks');
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('repeal the law and the same write goes through — the next read follows the book', async () => {
|
|
304
|
+
reset({});
|
|
305
|
+
lawRows = [LAND];
|
|
306
|
+
const r = await call('PATCH', '/me/interaction', { verbosity: 'detailed' });
|
|
307
|
+
assert.equal(r.status, 200);
|
|
308
|
+
assert.equal(r.json.effective.verbosity, 'detailed');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('laws unreadable: a write is refused 503 and saves nothing; a read falls back to the builder\'s own and says so', async () => {
|
|
312
|
+
reset({ interaction: { verbosity: 'detailed' } });
|
|
313
|
+
lawsDown = true;
|
|
314
|
+
const w = await call('PATCH', '/me/interaction', { verbosity: 'detailed' });
|
|
315
|
+
assert.equal(w.status, 503);
|
|
316
|
+
assert.equal(saves, 0);
|
|
317
|
+
const r = await call('GET', '/me/interaction');
|
|
318
|
+
assert.equal(r.status, 200);
|
|
319
|
+
assert.equal(r.json.laws_read, false);
|
|
320
|
+
assert.equal(r.json.effective.verbosity, 'detailed');
|
|
321
|
+
});
|