@bongos/core 1.21.176 → 1.21.178
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 +186 -496
- package/.claude/skills/planning-session/SKILL.md +2 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +0 -6
- package/clients/bongos-client/index.cjs +0 -6
- package/clients/bongos-client/index.d.ts +0 -6
- package/clients/bongos-client/index.mjs +0 -6
- package/config/scheduled-routines.json +3 -8
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +3 -2
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +1 -1
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +1 -1
- package/docs/api/openapi.json +1 -87
- package/docs/api-reference.md +1 -8
- package/docs/canonical-permissions.md +4 -4
- package/docs/copy-inventory.md +25 -39
- package/docs/copy-registry.json +73 -200
- package/docs/file-map.md +6 -11
- package/docs/handoff-template.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +2 -2
- package/docs/onboarding/diagrams/README.md +3 -7
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/page-inventory.json +4 -7
- package/docs/page-readings.json +1520 -1569
- package/docs/recipes/ops-gotchas.md +3 -1
- package/modules/autonomy/db.js +9 -0
- package/modules/autonomy/module.json +2 -2
- package/modules/autonomy/pollers/daily-checks.js +262 -0
- package/modules/autonomy/pollers/routine-timers.js +7 -5
- package/modules/autonomy/rank-roles.js +64 -0
- package/modules/autonomy/server-schedule.js +37 -0
- package/modules/government/catalog.js +10 -7
- package/modules/government/decision-board.js +0 -1
- package/modules/government/decision-rules.js +0 -1
- package/modules/government/docket.js +0 -1
- package/modules/government/protected-surfaces.js +1 -1
- package/modules/government/protected-surfaces.json +2 -3
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.css +4 -26
- package/modules/hall-ui/public/gate.html +12 -27
- package/modules/hall-ui/public/gate.js +26 -176
- package/modules/hall-ui/public/gate.states.json +4 -22
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/street-card.js +5 -9
- package/modules/hall-ui/public/street-desk.js +2 -2
- package/modules/hall-ui/public/street-inside.js +1 -1
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/lifecycle/matter-acts.js +0 -6
- package/modules/lifecycle/module.json +0 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +36 -0
- package/scripts/bongos/adopt-preflight.js +3 -3
- package/scripts/bongos/api-alias-caller-scan.js +2 -2
- package/scripts/bongos/audit-authorship.sh +1 -8
- package/scripts/bongos/audit-rank-roles.js +15 -56
- package/scripts/bongos/bump-version.js +46 -9
- package/scripts/bongos/codemod-rename-scripts-gds.js +0 -1
- package/scripts/bongos/diagram-facts.js +1 -1
- package/scripts/bongos/do-api.js +1 -1
- package/scripts/bongos/doc-cli-guard.js +2 -0
- package/scripts/bongos/doc-control-claims.js +2 -2
- package/scripts/bongos/fitness-checks-identity.js +4 -15
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/government-walk.js +3 -9
- package/scripts/bongos/hard-floor.js +52 -0
- package/scripts/bongos/publish-manifest.js +3 -3
- package/scripts/bongos/pulse.js +5 -3
- package/scripts/bongos/routine-schedule.js +38 -3
- package/scripts/bongos/run-unit-tests.js +17 -5
- package/scripts/bongos/ship-regen.js +2 -3
- package/scripts/gds/{goal-names.js → hard-floor.js} +3 -4
- package/scripts/security/dep-audit.js +50 -124
- package/src/bongos/module-scope-map.js +2 -2
- package/src/bongos/permission-path-check.js +1 -1
- package/src/bongos/route-rank-check.js +1 -8
- package/src/bongos/routes.js +1 -2
- package/src/module-api.js +1 -1
- package/src/modules.js +1 -1
- package/tests/acclaim_feed_evidence_db.mjs +42 -23
- package/tests/adr_0089_scope.mjs +7 -5
- package/tests/autonomy_daily_checks.mjs +201 -0
- package/tests/bump_version.mjs +52 -18
- package/tests/claude_materialize.mjs +12 -12
- package/tests/copy_desk_flags_db.mjs +22 -14
- package/tests/effective_visibility_predicate.mjs +0 -18
- package/tests/gating_review_retired.mjs +1 -1
- package/tests/government_act_alone_buildings.mjs +1 -3
- package/tests/government_board_membership_db.mjs +35 -22
- package/tests/government_protected_surfaces.mjs +29 -13
- package/tests/governor_docket.mjs +0 -2
- package/tests/governor_matter_acts.mjs +6 -8
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_matter_card.mjs +2 -2
- package/tests/hall_oversight_world.mjs +16 -12
- package/tests/hall_street_desk.mjs +4 -3
- package/tests/hall_street_inside.mjs +1 -1
- package/tests/helpers.mjs +34 -0
- package/tests/howto_state.mjs +33 -56
- package/tests/howto_state_db.mjs +68 -0
- package/tests/idea_credit_streams_db.mjs +11 -3
- package/tests/integration_parked_ratchet.mjs +7 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/page_notes_db.mjs +12 -8
- package/tests/page_tweak_no_rearm.mjs +50 -0
- package/tests/publish_manifest.mjs +64 -0
- package/tests/routine_capture_kind.mjs +18 -7
- package/tests/routine_schedule.mjs +42 -0
- package/tests/search_isolation.mjs +5 -103
- package/tests/search_isolation_db.mjs +140 -0
- package/tests/session_earnings_mirror_db.mjs +2 -0
- package/tests/workflow_exec_paths.mjs +9 -11
- package/modules/lifecycle/routes/gate-approvals.js +0 -299
- package/scripts/bongos/artifact-staleness.js +0 -267
- package/scripts/bongos/ci-grade.js +0 -82
- package/scripts/bongos/classify-discipline.js +0 -153
- package/scripts/bongos/codemod-rename-src-gds.js +0 -145
- package/scripts/bongos/coreB-carve-proof.sh +0 -725
- package/scripts/bongos/gate-review.js +0 -330
- package/scripts/bongos/goal-names.js +0 -102
- package/scripts/bongos/rename-goals.js +0 -109
- package/scripts/bongos/seed-adopt-tasks.js +0 -75
- package/scripts/bongos/seed-api-contract-goal-tasks.js +0 -141
- package/scripts/bongos/seed-board-room-tasks.js +0 -196
- package/scripts/bongos/seed-bongos-app-tasks.js +0 -294
- package/scripts/bongos/seed-bongos-coreB-tranche1-tasks.js +0 -81
- package/scripts/bongos/seed-bongos-coreB-tranche2-tasks.js +0 -100
- package/scripts/bongos/seed-bongos-goal-hierarchy-tasks.js +0 -307
- package/scripts/bongos/seed-bongos-module-system-tasks.js +0 -346
- package/scripts/bongos/seed-bongos-tasks.js +0 -214
- package/scripts/bongos/seed-bongos-v1-seo-tasks.js +0 -104
- package/scripts/bongos/seed-bv1-community-tasks.js +0 -131
- package/scripts/bongos/seed-bv1-privacy-invites-tasks.js +0 -133
- package/scripts/bongos/seed-bv1-profile-page-tasks.js +0 -161
- package/scripts/bongos/seed-bv1-r01-delta-tasks.js +0 -154
- package/scripts/bongos/seed-bv2-tweak-mode-tasks.js +0 -277
- package/scripts/bongos/seed-gds-v4-tasks.js +0 -624
- package/scripts/bongos/seed-governance-tasks.js +0 -340
- package/scripts/bongos/seed-medusa-tasks.js +0 -440
- package/scripts/bongos/seed-module-upstreaming.js +0 -219
- package/scripts/bongos/seed-productization-tasks.js +0 -235
- package/scripts/bongos/seed-project-admin-console-tasks.js +0 -88
- package/scripts/bongos/seed-provisioning-tasks.js +0 -83
- package/scripts/bongos/seed-strict-versioning-tasks.js +0 -275
- package/scripts/bongos/seed-v2-world-tasks.js +0 -283
- package/scripts/gds/artifact-staleness.js +0 -7
- package/scripts/gds/ci-grade.js +0 -7
- package/scripts/gds/classify-discipline.js +0 -7
- package/scripts/gds/codemod-rename-src-gds.js +0 -7
- package/scripts/gds/coreB-carve-proof.sh +0 -8
- package/scripts/gds/gate-review.js +0 -7
- package/scripts/gds/rename-goals.js +0 -7
- package/scripts/gds/seed-adopt-tasks.js +0 -7
- package/scripts/gds/seed-api-contract-goal-tasks.js +0 -7
- package/scripts/gds/seed-board-room-tasks.js +0 -6
- package/scripts/gds/seed-bongos-app-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +0 -7
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +0 -7
- package/scripts/gds/seed-bongos-module-system-tasks.js +0 -7
- package/scripts/gds/seed-bongos-tasks.js +0 -7
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +0 -6
- package/scripts/gds/seed-bv1-community-tasks.js +0 -6
- package/scripts/gds/seed-bv1-privacy-invites-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profile-page-tasks.js +0 -6
- package/scripts/gds/seed-bv1-r01-delta-tasks.js +0 -6
- package/scripts/gds/seed-bv2-tweak-mode-tasks.js +0 -6
- package/scripts/gds/seed-gds-v4-tasks.js +0 -7
- package/scripts/gds/seed-governance-tasks.js +0 -7
- package/scripts/gds/seed-medusa-tasks.js +0 -7
- package/scripts/gds/seed-module-upstreaming.js +0 -7
- package/scripts/gds/seed-productization-tasks.js +0 -7
- package/scripts/gds/seed-project-admin-console-tasks.js +0 -6
- package/scripts/gds/seed-provisioning-tasks.js +0 -7
- package/scripts/gds/seed-strict-versioning-tasks.js +0 -7
- package/scripts/gds/seed-v2-world-tasks.js +0 -7
- package/scripts/migrate_cost_ledger.sh +0 -178
- package/tests/artifact_staleness.mjs +0 -207
- package/tests/gate_approvals.mjs +0 -611
- package/tests/gate_review.mjs +0 -152
- package/tests/goal_names.mjs +0 -120
- package/tests/newcomer_generator.mjs +0 -48
package/tests/page_notes_db.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { strict as assert } from 'node:assert';
|
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import url from 'node:url';
|
|
13
|
+
import { insertTestBuilders } from './helpers.mjs';
|
|
13
14
|
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
const { Pool } = require('pg');
|
|
@@ -42,15 +43,15 @@ async function refuses(sql, params, constraint) {
|
|
|
42
43
|
throw new Error(`expected ${constraint || 'a refusal'}, but the write landed`);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
CREATE TABLE IF NOT EXISTS idea_inbox (id bigserial PRIMARY KEY, title text, status text);
|
|
49
|
-
`);
|
|
46
|
+
// Needs a MIGRATED database (the integration lane's). The stand-in builders /
|
|
47
|
+
// idea_inbox tables this once created drifted from the schema (builders.github_id
|
|
48
|
+
// became NOT NULL) and are gone (task 1004863).
|
|
50
49
|
const A = 910000001;
|
|
51
50
|
const B = 910000002;
|
|
52
|
-
await pool
|
|
53
|
-
|
|
51
|
+
await insertTestBuilders(pool, [
|
|
52
|
+
{ id: A, login: 'pntest-a', display_name: 'Page Notes Test A' },
|
|
53
|
+
{ id: B, login: 'pntest-b', display_name: '' },
|
|
54
|
+
]);
|
|
54
55
|
|
|
55
56
|
const MIGRATION = fs.readFileSync(path.join(ROOT, 'modules/page-notes/migrations/page_notes_001_notes.sql'), 'utf8');
|
|
56
57
|
await pool.query(MIGRATION);
|
|
@@ -137,7 +138,6 @@ await test('deleting a doc takes its images with it', async () => {
|
|
|
137
138
|
});
|
|
138
139
|
|
|
139
140
|
await clean();
|
|
140
|
-
await pool.query('DELETE FROM builders WHERE id = ANY($1::bigint[])', [[A, B]]).catch(() => {});
|
|
141
141
|
await test('THE SITE LIST (task 1004499): one surface, submitted only, newest first, read through its own index', async () => {
|
|
142
142
|
await pool.query(MIGRATION_2);
|
|
143
143
|
const mk = async (author, page, at) => {
|
|
@@ -162,6 +162,10 @@ await test('THE SITE LIST (task 1004499): one surface, submitted only, newest fi
|
|
|
162
162
|
await clean();
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
+
// The builders go LAST (task 1004863): the site-list case above writes notes authored
|
|
166
|
+
// by A and B, and page_notes_docs.author_id is a foreign key into builders.
|
|
167
|
+
await clean();
|
|
168
|
+
await pool.query('DELETE FROM builders WHERE id = ANY($1::bigint[])', [[A, B]]).catch(() => {});
|
|
165
169
|
await pool.end();
|
|
166
170
|
console.log(`page_notes_db: ${passed} passed, ${failed} failed`);
|
|
167
171
|
process.exit(failed ? 1 : 0);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// tests/page_tweak_no_rearm.mjs — a page tweak held for its artist is never
|
|
2
|
+
// re-armed for auto-merge from outside github-push (task 1004859).
|
|
3
|
+
//
|
|
4
|
+
// WHY. ADR 0341 D7 holds a page tweak until its artist signs it off. The one
|
|
5
|
+
// caller that could re-arm auto-merge on an open PR was the hall's gate-approval
|
|
6
|
+
// route (POST /gate-approvals/:pr/approve → recheckPullRequest), and
|
|
7
|
+
// tests/gate_approvals.mjs "layer 3" pinned that it passed { arm: false } for a
|
|
8
|
+
// held tweak. Task 1004859 deleted that route with the dead held-PR list. The
|
|
9
|
+
// guarantee now rests on there being NO other caller, so this pins that: a
|
|
10
|
+
// new caller of recheckPullRequest must come with its own held-tweak check
|
|
11
|
+
// and its own test, and this file is where it gets noticed.
|
|
12
|
+
//
|
|
13
|
+
// DB-free, no network. Run: node --test tests/page_tweak_no_rearm.mjs
|
|
14
|
+
|
|
15
|
+
import { strict as assert } from 'node:assert';
|
|
16
|
+
import { test } from 'node:test';
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
const OWNER = 'modules/lifecycle/github-push.js';
|
|
24
|
+
|
|
25
|
+
// Code only: a comment naming the function is not a call.
|
|
26
|
+
const stripComments = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
|
|
27
|
+
|
|
28
|
+
function callers(files, read) {
|
|
29
|
+
return files.filter((f) => f !== OWNER && /recheckPullRequest\s*\(/.test(stripComments(read(f))));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('nothing outside github-push calls recheckPullRequest (the only re-arm path was the removed gate route)', () => {
|
|
33
|
+
const files = execFileSync('git', ['ls-files', 'src', 'modules', 'scripts'], { cwd: ROOT, encoding: 'utf8' })
|
|
34
|
+
.split('\n').filter((f) => /\.(c|m)?js$/.test(f) && !/\/tests?\//.test(f));
|
|
35
|
+
assert.ok(files.length > 500, `only ${files.length} files scanned — the sweep is not reading the tree`);
|
|
36
|
+
const read = (f) => fs.readFileSync(path.join(ROOT, f), 'utf8');
|
|
37
|
+
assert.deepEqual(callers(files, read), [],
|
|
38
|
+
'a new caller re-arms auto-merge on an open PR: it must check heldForArtistFailClosed for the PR\'s task '
|
|
39
|
+
+ 'and pass { arm: false } when held (ADR 0341 D7), with a test, then be listed here');
|
|
40
|
+
assert.ok(!fs.existsSync(path.join(ROOT, 'modules/lifecycle/routes/gate-approvals.js')), 'the gate route stays removed');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('the sweep would catch a caller (counterfactual), and ignores one in a comment', () => {
|
|
44
|
+
const fake = {
|
|
45
|
+
'modules/x/a.js': 'await githubPush.recheckPullRequest({ number: 1 });',
|
|
46
|
+
'modules/x/b.js': '// recheckPullRequest(...) is described here only',
|
|
47
|
+
[OWNER]: 'async function recheckPullRequest() {}',
|
|
48
|
+
};
|
|
49
|
+
assert.deepEqual(callers(Object.keys(fake), (f) => fake[f]), ['modules/x/a.js']);
|
|
50
|
+
});
|
|
@@ -22,6 +22,7 @@ import { createRequire } from 'node:module';
|
|
|
22
22
|
import fs from 'node:fs';
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import url from 'node:url';
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
25
26
|
|
|
26
27
|
const ROOT = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..');
|
|
27
28
|
const require = createRequire(import.meta.url);
|
|
@@ -324,5 +325,68 @@ t('no publishable test statically imports an excluded module (R89 boundary)', ()
|
|
|
324
325
|
assert.equal(offenders.length, 0, '\n ' + offenders.join('\n '));
|
|
325
326
|
});
|
|
326
327
|
|
|
328
|
+
// ── hall-preview fixtures stay out of the core; the harness stays in (task 1004888) ──
|
|
329
|
+
// scripts/hall-preview/fixtures/ is ~2.4 MB of captured hall data from THIS instance
|
|
330
|
+
// (owner login, world name, prior instance name). INSTANCE_EXCLUDE keeps it out of the
|
|
331
|
+
// published core (task 1002219, audit H5); this pins that, so an edit to the exclude
|
|
332
|
+
// list cannot silently start shipping the fixtures to every hosted project again.
|
|
333
|
+
// The selection is built the way package-core builds it: enumerate the git tree
|
|
334
|
+
// (`git ls-tree -r` on a ref) and keep what isPublishable() admits.
|
|
335
|
+
const HALL_PREVIEW = 'scripts/hall-preview/';
|
|
336
|
+
const HALL_FIXTURES = `${HALL_PREVIEW}fixtures/`;
|
|
337
|
+
// The harness files the preview cannot boot without: the server and its two local
|
|
338
|
+
// requires (./live-identity, ./pref-writes). A named floor, so the check below can
|
|
339
|
+
// never pass by finding no harness at all.
|
|
340
|
+
const HALL_HARNESS_FLOOR = ['server.js', 'live-identity.js', 'pref-writes.js'].map((f) => HALL_PREVIEW + f);
|
|
341
|
+
|
|
342
|
+
function treeFiles() {
|
|
343
|
+
// package-core's ref path: every blob on HEAD. A core checkout with no git history
|
|
344
|
+
// (a consumer's vendored core) falls back to walking the disk, as --from-worktree does.
|
|
345
|
+
try {
|
|
346
|
+
const out = execFileSync('git', ['ls-tree', '-r', '--name-only', 'HEAD'], { cwd: ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 });
|
|
347
|
+
return out.split('\n').filter(Boolean);
|
|
348
|
+
} catch (_) {
|
|
349
|
+
const files = [];
|
|
350
|
+
const walk = (rel) => {
|
|
351
|
+
for (const ent of fs.readdirSync(path.join(ROOT, rel), { withFileTypes: true })) {
|
|
352
|
+
const r = rel ? `${rel}/${ent.name}` : ent.name;
|
|
353
|
+
if (ent.isDirectory()) walk(r); else files.push(r);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
if (fs.existsSync(path.join(ROOT, HALL_PREVIEW))) walk(HALL_PREVIEW.slice(0, -1));
|
|
357
|
+
return files;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
t('hall-preview fixtures are excluded from the core (path rule, task 1004888)', () => {
|
|
362
|
+
// Holds even where the fixtures are not on disk (a consumer core never has them).
|
|
363
|
+
for (const p of [`${HALL_FIXTURES}me.json`, `${HALL_FIXTURES}nested/deep.json`, `${HALL_FIXTURES}tasks.json`]) {
|
|
364
|
+
assert.equal(isPublishable(p), false, `${p} would ship in the published core — keep '${HALL_FIXTURES}' in INSTANCE_EXCLUDE`);
|
|
365
|
+
}
|
|
366
|
+
for (const p of HALL_HARNESS_FLOOR) {
|
|
367
|
+
assert.equal(isPublishable(p), true, `${p} is the preview harness and must still publish`);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
t('the package-core selection carries no hall-preview fixture, but keeps the harness (task 1004888)', () => {
|
|
372
|
+
const hall = treeFiles().filter((f) => f.startsWith(HALL_PREVIEW));
|
|
373
|
+
const fixtures = hall.filter((f) => f.startsWith(HALL_FIXTURES));
|
|
374
|
+
const harness = hall.filter((f) => !f.startsWith(HALL_FIXTURES));
|
|
375
|
+
const { publish } = partitionPaths(hall);
|
|
376
|
+
const leaked = publish.filter((f) => f.startsWith(HALL_FIXTURES));
|
|
377
|
+
assert.equal(leaked.length, 0, `${leaked.length} hall-preview fixture(s) would ship in the core, e.g. ${leaked.slice(0, 3).join(', ')} — keep '${HALL_FIXTURES}' in INSTANCE_EXCLUDE`);
|
|
378
|
+
// Every harness file on the tree publishes, and the named floor is among them
|
|
379
|
+
// whenever the harness exists here at all (the source repo always has it).
|
|
380
|
+
const dropped = harness.filter((f) => !publish.includes(f));
|
|
381
|
+
assert.equal(dropped.length, 0, `hall-preview harness file(s) dropped from the core: ${dropped.join(', ')}`);
|
|
382
|
+
if (harness.length) {
|
|
383
|
+
for (const p of HALL_HARNESS_FLOOR) assert.ok(publish.includes(p), `${p} missing from the core selection`);
|
|
384
|
+
}
|
|
385
|
+
// Non-vacuous in the source repo: the fixtures are there to be excluded.
|
|
386
|
+
if (fs.existsSync(path.join(ROOT, HALL_FIXTURES))) {
|
|
387
|
+
assert.ok(fixtures.length > 0, `found ${HALL_FIXTURES} on disk but no fixture in the enumerated tree — the enumeration is broken, so this check proves nothing`);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
327
391
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
328
392
|
process.exit(failed ? 1 : 0);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// kind the server accepts (task 1003694).
|
|
3
3
|
//
|
|
4
4
|
// THE FAILURE THIS PINS. .claude/scheduled-tasks/dep-audit-nightly/SKILL.md filed
|
|
5
|
+
// (before task 1004878 moved its filing into the server's daily-checks poller)
|
|
5
6
|
// with `--kind security`. That is not one of the kinds — captureIdea throws BAD_KIND
|
|
6
7
|
// and the route 400s — so the capture step of that nightly routine had NEVER filed
|
|
7
8
|
// anything.
|
|
@@ -137,11 +138,21 @@ test('a capture command wrapped over lines is still read', () => {
|
|
|
137
138
|
'dep-audit-nightly writes exactly this shape — a line-at-a-time matcher misses it');
|
|
138
139
|
});
|
|
139
140
|
|
|
140
|
-
test('dep-audit-nightly specifically now files with an accepted kind (task 1003694)', () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
test('dep-audit-nightly specifically now files with an accepted kind (task 1003694)', async () => {
|
|
142
|
+
// Since task 1004878 the routine files from the server's daily-checks poller, not
|
|
143
|
+
// from a capture command in its SKILL.md — so the kind to check is the one the
|
|
144
|
+
// poller hands ideas.capture. Driven, not grepped: a stub npm reports one finding.
|
|
145
|
+
const { runCheck } = require('../modules/autonomy/pollers/daily-checks.js');
|
|
146
|
+
const filed = [];
|
|
147
|
+
const out = await runCheck('dep-audit-nightly', {
|
|
148
|
+
db: { readAutonomyRuns: async () => [], recordAutonomyRun: async () => ({}) },
|
|
149
|
+
cwd: '/instance',
|
|
150
|
+
npmAudit: async () => ({ stdout: JSON.stringify({ vulnerabilities: { x: { severity: 'high', via: [], fixAvailable: true } } }), stderr: '' }),
|
|
151
|
+
captureIdea: async (idea) => { filed.push(idea); return { id: 1 }; },
|
|
152
|
+
log: () => {}, error: () => {},
|
|
153
|
+
});
|
|
154
|
+
assert.equal(out.outcome, 'filed');
|
|
155
|
+
assert.equal(filed.length, 1);
|
|
156
|
+
assert.notEqual(filed[0].kind, 'security', 'security is not a kind and never was');
|
|
157
|
+
assert.ok(VALID_KINDS.includes(filed[0].kind), `${filed[0].kind} is not a valid kind`);
|
|
147
158
|
});
|
|
@@ -234,3 +234,45 @@ test('LIVE: the patrol workflow installs gitleaks, which the history scan cannot
|
|
|
234
234
|
assert.match(wf, /sha256sum -c -/, 'the binary must be checksum-verified (the tj-actions lesson)');
|
|
235
235
|
assert.match(wf, /fetch-depth: 0/, 'a shallow clone makes the history scan a confident no-op');
|
|
236
236
|
});
|
|
237
|
+
|
|
238
|
+
// ------------------------------------------------- the server scheduler (task 1004878)
|
|
239
|
+
|
|
240
|
+
const SRV = [{ routine: 'nightly-thing', cadence: 'daily' }];
|
|
241
|
+
|
|
242
|
+
test('a server row fires its routine: declared daily + server daily is green with NO workflow', () => {
|
|
243
|
+
const r = rs.evaluate(routines([{ name: 'nightly-thing', cadence: 'daily', steps: [] }]), [], SRV);
|
|
244
|
+
assert.deepEqual(r.violations, []);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('without the server row the same routine is dormant — the row is what makes it green', () => {
|
|
248
|
+
const v = rs.evaluate(routines([{ name: 'nightly-thing', cadence: 'daily', steps: [] }]), [], []).violations;
|
|
249
|
+
assert.match(v[0], /NOTHING fires it/);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('a server row at a different period than the routine declares is red', () => {
|
|
253
|
+
const v = rs.evaluate(routines([{ name: 'nightly-thing', cadence: 'weekly', steps: [] }]), [], SRV).violations;
|
|
254
|
+
assert.match(v[0], /declares cadence: weekly but the server fires it daily/);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('the server firing a routine whose SKILL.md declares no cadence is red, not a quiet warning', () => {
|
|
258
|
+
const r = rs.evaluate(routines([], [{ name: 'nightly-thing' }]), [], SRV);
|
|
259
|
+
assert.match(r.violations[0], /fired daily by the server but its SKILL\.md declares no cadence/);
|
|
260
|
+
assert.equal(r.warnings.length, 0, 'the same routine must not ALSO read as manual-only');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('a server row naming a routine that is gone is red (rename rot)', () => {
|
|
264
|
+
const v = rs.evaluate(routines(WEEKLY), [WEEKLY_WF], SRV).violations;
|
|
265
|
+
assert.ok(v.some((x) => /fires routine 'nightly-thing', which has no/.test(x)), v.join(' | '));
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('with no routine tree at all (an instance before materialize) server rows are not dangling', () => {
|
|
269
|
+
assert.deepEqual(rs.evaluate(routines([]), null, SRV).violations, []);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('LIVE: every server-fired routine declares the cadence the server realizes', () => {
|
|
273
|
+
const { SERVER_ROUTINES } = require(path.join(ROOT, 'modules', 'autonomy', 'server-schedule.js'));
|
|
274
|
+
const declared = new Map(rs.readRoutines().withCadence.map((r) => [r.name, r.cadence]));
|
|
275
|
+
for (const s of SERVER_ROUTINES) {
|
|
276
|
+
assert.equal(declared.get(s.routine), s.cadence, `${s.routine}: SKILL.md cadence vs server-schedule.js`);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
@@ -14,15 +14,13 @@
|
|
|
14
14
|
// to searchChunks and IGNORES any attempt to widen scope via the body
|
|
15
15
|
// (builder_id / rank / min_rank / owner_builder_id). This is the
|
|
16
16
|
// "can't be tricked" proof.
|
|
17
|
-
// C. DB-backed
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
17
|
+
// C. DB-backed: lives in tests/search_isolation_db.mjs (task 1004863). It was
|
|
18
|
+
// the third layer of this file, whose comment said it ran in the integration
|
|
19
|
+
// gate — but this file was never in that set, so in CI Layer C always skipped
|
|
20
|
+
// and no lane executed it. Split out, it is in the INTEGRATION set, and A + B
|
|
21
|
+
// stay here in the DB-free unit lane.
|
|
22
22
|
//
|
|
23
23
|
// Run: node tests/search_isolation.mjs
|
|
24
|
-
// (Layer C runs only where a Postgres is reachable — the integration gate / any
|
|
25
|
-
// machine with one; it skips cleanly otherwise so the DB-free unit gate stays green.)
|
|
26
24
|
|
|
27
25
|
import { strict as assert } from 'node:assert';
|
|
28
26
|
import { createRequire } from 'node:module';
|
|
@@ -136,102 +134,6 @@ try {
|
|
|
136
134
|
search.searchChunks = orig.searchChunks;
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
// Layer C — DB-backed real-query scoping (auto-skips without Postgres)
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
console.log('\nLayer C — real searchChunks against a live DB (skips if unreachable):');
|
|
143
|
-
|
|
144
|
-
let pool = null;
|
|
145
|
-
try {
|
|
146
|
-
// task 1003207: this said `../src/db`, which has not existed since the kernel
|
|
147
|
-
// carve — so the require ALWAYS threw, pool stayed null, and Layer C skipped
|
|
148
|
-
// even against a live Postgres. It was a dead layer reporting as a skip.
|
|
149
|
-
({ pool } = require('../src/bongos/pool'));
|
|
150
|
-
} catch { pool = null; }
|
|
151
|
-
|
|
152
|
-
async function dbReachable() {
|
|
153
|
-
if (!pool) return null;
|
|
154
|
-
try {
|
|
155
|
-
const c = await pool.connect();
|
|
156
|
-
try { await c.query('SELECT 1'); return c; } catch { c.release(); return null; }
|
|
157
|
-
} catch { return null; }
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const client = await dbReachable();
|
|
161
|
-
if (!client) {
|
|
162
|
-
console.log(' SKIP no reachable Postgres — Layer C runs in the integration gate / wherever a Postgres is reachable.');
|
|
163
|
-
console.log(' (The same scoping cases were proven on the prod DB in a rolled-back transaction at ship time.)');
|
|
164
|
-
// END THE POOL ON THE SKIP PATH TOO (task 1003848). Only the else-branch below
|
|
165
|
-
// ended it, so when Postgres was unreachable the Pool survived with libuv handles
|
|
166
|
-
// still closing from its failed connect — and the process.exit() at the bottom of
|
|
167
|
-
// this file then tore down the loop underneath them. On Windows that surfaces as
|
|
168
|
-
// `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), src\win\async.c`, exit
|
|
169
|
-
// 0xC0000409: a process-teardown crash AFTER '5 passed, 0 failed', which reads as
|
|
170
|
-
// a failing suite in the unit gate while every assertion actually passed.
|
|
171
|
-
if (pool) await pool.end().catch(() => {});
|
|
172
|
-
} else {
|
|
173
|
-
try {
|
|
174
|
-
await client.query('BEGIN');
|
|
175
|
-
await client.query(`SET LOCAL pg_trgm.word_similarity_threshold = ${search.TRGM_THRESHOLD}`);
|
|
176
|
-
// builder 1 exists (example-owner); seed a protected (metic) chunk, a public one,
|
|
177
|
-
// and a chunk owned by builder 1 — all matching q='enforcement'.
|
|
178
|
-
await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
|
|
179
|
-
('doc','__iso__/protected.md',0,'p','PROT','trust boundary permission core enforcement','metic',NULL,'i1'),
|
|
180
|
-
('doc','__iso__/public.md',0,'pub','PUB','trust boundary public enforcement overview','xenos',NULL,'i2'),
|
|
181
|
-
('db','__iso__/memory:1',0,'m','MEM1','builder one private enforcement memo','xenos',1,'i3')`);
|
|
182
|
-
|
|
183
|
-
const titles = (rows) => rows.map((r) => r.title).sort().join(',');
|
|
184
|
-
|
|
185
|
-
await t('xenos caller sees the public chunk but NOT the metic-protected one (xenos-vs-protected)', async () => {
|
|
186
|
-
const rows = await search.searchChunks({ q: 'enforcement', builderId: 2, builderRank: 'xenos', limit: 50 }, { client });
|
|
187
|
-
const ts = titles(rows);
|
|
188
|
-
assert.ok(ts.includes('PUB'), `expected PUB, got [${ts}]`);
|
|
189
|
-
assert.ok(!ts.includes('PROT'), `xenos must NOT see the metic chunk, got [${ts}]`);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
await t('metic caller sees the protected chunk too', async () => {
|
|
193
|
-
const rows = await search.searchChunks({ q: 'enforcement', builderId: 2, builderRank: 'metic', limit: 50 }, { client });
|
|
194
|
-
const ts = titles(rows);
|
|
195
|
-
assert.ok(ts.includes('PROT') && ts.includes('PUB'), `metic should see PROT+PUB, got [${ts}]`);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
await t('builder 2 does NOT see builder 1\'s owned chunk (cross-builder memory)', async () => {
|
|
199
|
-
const rows = await search.searchChunks({ q: 'enforcement', builderId: 2, builderRank: 'archon', limit: 50 }, { client });
|
|
200
|
-
assert.ok(!rows.some((r) => r.title === 'MEM1'), 'an Archon STILL must not see another builder\'s private chunk');
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
await t('builder 1 DOES see their own owned chunk (owner-sees-own)', async () => {
|
|
204
|
-
const rows = await search.searchChunks({ q: 'enforcement', builderId: 1, builderRank: 'xenos', limit: 50 }, { client });
|
|
205
|
-
assert.ok(rows.some((r) => r.title === 'MEM1'), 'a builder must see their own private chunk');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// The public port (task 1002534, ADR 0367): the Discord #support answerer
|
|
209
|
-
// has no session, so its scope is fixed — shared xenos docs on the
|
|
210
|
-
// allowlist, nothing else, whatever matches the query.
|
|
211
|
-
await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
|
|
212
|
-
('doc','__pub__/allowed/guide.md',0,'g','PUBOK','support enforcement guide','xenos',NULL,'p1'),
|
|
213
|
-
('doc','__pub__/allowed/locked.md',0,'l','PUBMETIC','support enforcement locked','metic',NULL,'p2'),
|
|
214
|
-
('doc','__pub__/allowed/mine.md',0,'m','PUBOWNED','support enforcement owned','xenos',1,'p3'),
|
|
215
|
-
('db','__pub__/allowed/task:1',0,'d','PUBDB','support enforcement task prose','xenos',NULL,'p4'),
|
|
216
|
-
('doc','__pub__/elsewhere.md',0,'e','PUBOFFLIST','support enforcement off the list','xenos',NULL,'p5')`);
|
|
217
|
-
|
|
218
|
-
await t('searchPublicDocs returns ONLY a shared xenos doc on the allowlist (no metic, owned, db, or off-list row)', async () => {
|
|
219
|
-
const rows = await search.searchPublicDocs({ q: 'support enforcement', refs: ['__pub__/allowed/'], limit: 10 }, { client });
|
|
220
|
-
const ts = titles(rows);
|
|
221
|
-
assert.equal(ts, 'PUBOK', `expected only PUBOK, got [${ts}]`);
|
|
222
|
-
assert.ok(rows[0].body.includes('support enforcement guide'), 'the body comes back with the hit');
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
await t('searchPublicDocs with no allowlist returns nothing at all', async () => {
|
|
226
|
-
assert.deepEqual(await search.searchPublicDocs({ q: 'support enforcement', refs: [] }, { client }), []);
|
|
227
|
-
});
|
|
228
|
-
} finally {
|
|
229
|
-
await client.query('ROLLBACK').catch(() => {});
|
|
230
|
-
client.release();
|
|
231
|
-
await pool.end().catch(() => {});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
137
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
236
138
|
// EXIT CODE, NOT process.exit() (task 1003848). A hard exit tears the event loop down
|
|
237
139
|
// underneath any handle that is still closing — and pg leaves one behind after a
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// tests/search_isolation_db.mjs
|
|
2
|
+
//
|
|
3
|
+
// Layer C of the GDS search isolation suite (task 950, V4.R12 / ADR 0060 §6 / ADR 0016),
|
|
4
|
+
// against a REAL Postgres. Layers A (pure + SQL structure) and B (the HTTP route derives
|
|
5
|
+
// scope from the session only) are DB-free and stay in tests/search_isolation.mjs.
|
|
6
|
+
//
|
|
7
|
+
// Seeds rank-gated + owner-scoped chunks and runs the REAL searchChunks and
|
|
8
|
+
// searchPublicDocs in a ROLLED-BACK transaction for the done-when cases
|
|
9
|
+
// (xenos-vs-protected, cross-builder memory, owner-sees-own, the public port's fixed
|
|
10
|
+
// scope). The search index must never be a read-side bypass of the trust boundary.
|
|
11
|
+
//
|
|
12
|
+
// SPLIT OUT by task 1004863. As the third layer of search_isolation.mjs it said it ran in
|
|
13
|
+
// the integration gate, but that file was never in the INTEGRATION set — so in CI it
|
|
14
|
+
// always skipped and no lane executed it. This file is in the set; it needs a MIGRATED
|
|
15
|
+
// database (builders 1 and 2 are the seeded system rows the owned chunk points at).
|
|
16
|
+
//
|
|
17
|
+
// Run: DATABASE_URL=postgres://... node tests/search_isolation_db.mjs
|
|
18
|
+
|
|
19
|
+
import { strict as assert } from 'node:assert';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const search = require('../modules/memory/search.js');
|
|
24
|
+
|
|
25
|
+
let passed = 0;
|
|
26
|
+
let failed = 0;
|
|
27
|
+
async function t(name, fn) {
|
|
28
|
+
try { await fn(); passed++; console.log(` ok ${name}`); }
|
|
29
|
+
catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Layer C — DB-backed real-query scoping (auto-skips without Postgres)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
console.log('Layer C — real searchChunks against a live DB (skips if unreachable):');
|
|
36
|
+
|
|
37
|
+
let pool = null;
|
|
38
|
+
try {
|
|
39
|
+
// task 1003207: this said `../src/db`, which has not existed since the kernel
|
|
40
|
+
// carve — so the require ALWAYS threw, pool stayed null, and Layer C skipped
|
|
41
|
+
// even against a live Postgres. It was a dead layer reporting as a skip.
|
|
42
|
+
({ pool } = require('../src/bongos/pool'));
|
|
43
|
+
} catch { pool = null; }
|
|
44
|
+
|
|
45
|
+
async function dbReachable() {
|
|
46
|
+
if (!pool) return null;
|
|
47
|
+
try {
|
|
48
|
+
const c = await pool.connect();
|
|
49
|
+
try { await c.query('SELECT 1'); return c; } catch { c.release(); return null; }
|
|
50
|
+
} catch { return null; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const client = await dbReachable();
|
|
54
|
+
if (!client) {
|
|
55
|
+
console.log(' SKIP no reachable Postgres — this runs in the integration lane (run-unit-tests.js INTEGRATION) / wherever a Postgres is reachable.');
|
|
56
|
+
console.log(' (The same scoping cases were proven on the prod DB in a rolled-back transaction at ship time.)');
|
|
57
|
+
// END THE POOL ON THE SKIP PATH TOO (task 1003848). Only the else-branch below
|
|
58
|
+
// ended it, so when Postgres was unreachable the Pool survived with libuv handles
|
|
59
|
+
// still closing from its failed connect — and the process.exit() at the bottom of
|
|
60
|
+
// the old combined file then tore down the loop underneath them. On Windows that surfaces as
|
|
61
|
+
// `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), src\win\async.c`, exit
|
|
62
|
+
// 0xC0000409: a process-teardown crash AFTER '5 passed, 0 failed', which reads as
|
|
63
|
+
// a failing suite in the unit gate while every assertion actually passed.
|
|
64
|
+
if (pool) await pool.end().catch(() => {});
|
|
65
|
+
} else {
|
|
66
|
+
try {
|
|
67
|
+
await client.query('BEGIN');
|
|
68
|
+
await client.query(`SET LOCAL pg_trgm.word_similarity_threshold = ${search.TRGM_THRESHOLD}`);
|
|
69
|
+
// builder 1 exists (example-owner); seed a protected (metic) chunk, a public one,
|
|
70
|
+
// and a chunk owned by builder 1 — all matching q=ISO_TERM.
|
|
71
|
+
//
|
|
72
|
+
// ISO_TERM is a word no real document contains (task 1004889). It used to be
|
|
73
|
+
// 'enforcement', and in the integration lane the booted server's search-index
|
|
74
|
+
// poller fills search_chunks with the repo's docs first: 149 real chunks matched,
|
|
75
|
+
// the three seeded rows fell out of the top 50, and every "sees" case failed.
|
|
76
|
+
// Worse, the "does NOT see" cases were then passing VACUOUSLY — a hidden row and a
|
|
77
|
+
// crowded-out row look the same. With a unique term the seeded rows are the only
|
|
78
|
+
// candidates, so a row missing from the results can only mean the scope removed it.
|
|
79
|
+
const ISO_TERM = 'zqxisoprobe';
|
|
80
|
+
await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
|
|
81
|
+
('doc','__iso__/protected.md',0,'p','PROT','trust boundary permission core ${ISO_TERM}','metic',NULL,'i1'),
|
|
82
|
+
('doc','__iso__/public.md',0,'pub','PUB','trust boundary public ${ISO_TERM} overview','xenos',NULL,'i2'),
|
|
83
|
+
('db','__iso__/memory:1',0,'m','MEM1','builder one private ${ISO_TERM} memo','xenos',1,'i3')`);
|
|
84
|
+
|
|
85
|
+
const titles = (rows) => rows.map((r) => r.title).sort().join(',');
|
|
86
|
+
|
|
87
|
+
await t('xenos caller sees the public chunk but NOT the metic-protected one (xenos-vs-protected)', async () => {
|
|
88
|
+
const rows = await search.searchChunks({ q: ISO_TERM, builderId: 2, builderRank: 'xenos', limit: 50 }, { client });
|
|
89
|
+
const ts = titles(rows);
|
|
90
|
+
assert.ok(ts.includes('PUB'), `expected PUB, got [${ts}]`);
|
|
91
|
+
assert.ok(!ts.includes('PROT'), `xenos must NOT see the metic chunk, got [${ts}]`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await t('metic caller sees the protected chunk too', async () => {
|
|
95
|
+
const rows = await search.searchChunks({ q: ISO_TERM, builderId: 2, builderRank: 'metic', limit: 50 }, { client });
|
|
96
|
+
const ts = titles(rows);
|
|
97
|
+
assert.ok(ts.includes('PROT') && ts.includes('PUB'), `metic should see PROT+PUB, got [${ts}]`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await t('builder 2 does NOT see builder 1\'s owned chunk (cross-builder memory)', async () => {
|
|
101
|
+
const rows = await search.searchChunks({ q: ISO_TERM, builderId: 2, builderRank: 'archon', limit: 50 }, { client });
|
|
102
|
+
assert.ok(!rows.some((r) => r.title === 'MEM1'), 'an Archon STILL must not see another builder\'s private chunk');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await t('builder 1 DOES see their own owned chunk (owner-sees-own)', async () => {
|
|
106
|
+
const rows = await search.searchChunks({ q: ISO_TERM, builderId: 1, builderRank: 'xenos', limit: 50 }, { client });
|
|
107
|
+
assert.ok(rows.some((r) => r.title === 'MEM1'), 'a builder must see their own private chunk');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// The public port (task 1002534, ADR 0367): the Discord #support answerer
|
|
111
|
+
// has no session, so its scope is fixed — shared xenos docs on the
|
|
112
|
+
// allowlist, nothing else, whatever matches the query.
|
|
113
|
+
await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
|
|
114
|
+
('doc','__pub__/allowed/guide.md',0,'g','PUBOK','support enforcement guide','xenos',NULL,'p1'),
|
|
115
|
+
('doc','__pub__/allowed/locked.md',0,'l','PUBMETIC','support enforcement locked','metic',NULL,'p2'),
|
|
116
|
+
('doc','__pub__/allowed/mine.md',0,'m','PUBOWNED','support enforcement owned','xenos',1,'p3'),
|
|
117
|
+
('db','__pub__/allowed/task:1',0,'d','PUBDB','support enforcement task prose','xenos',NULL,'p4'),
|
|
118
|
+
('doc','__pub__/elsewhere.md',0,'e','PUBOFFLIST','support enforcement off the list','xenos',NULL,'p5')`);
|
|
119
|
+
|
|
120
|
+
await t('searchPublicDocs returns ONLY a shared xenos doc on the allowlist (no metic, owned, db, or off-list row)', async () => {
|
|
121
|
+
const rows = await search.searchPublicDocs({ q: 'support enforcement', refs: ['__pub__/allowed/'], limit: 10 }, { client });
|
|
122
|
+
const ts = titles(rows);
|
|
123
|
+
assert.equal(ts, 'PUBOK', `expected only PUBOK, got [${ts}]`);
|
|
124
|
+
assert.ok(rows[0].body.includes('support enforcement guide'), 'the body comes back with the hit');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await t('searchPublicDocs with no allowlist returns nothing at all', async () => {
|
|
128
|
+
assert.deepEqual(await search.searchPublicDocs({ q: 'support enforcement', refs: [] }, { client }), []);
|
|
129
|
+
});
|
|
130
|
+
} finally {
|
|
131
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
132
|
+
client.release();
|
|
133
|
+
await pool.end().catch(() => {});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
138
|
+
// exitCode, not process.exit() (task 1003848): a hard exit can tear the loop down under a
|
|
139
|
+
// pg handle still closing after a failed connect — the skip path.
|
|
140
|
+
process.exitCode = failed === 0 ? 0 : 1;
|
|
@@ -125,6 +125,8 @@ async function fixture(client) {
|
|
|
125
125
|
title: `${tag} task`,
|
|
126
126
|
status: 'shipped',
|
|
127
127
|
version_id: version.id,
|
|
128
|
+
// Named, not placeholder-filled: tasks_discipline_check admits only the live crafts.
|
|
129
|
+
discipline: 'engineer',
|
|
128
130
|
});
|
|
129
131
|
const sessionId = `${tag}-session`;
|
|
130
132
|
await insertRow(client, 'session_records', {
|
|
@@ -23,11 +23,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
23
23
|
const REPO = path.resolve(__dirname, '..');
|
|
24
24
|
const CORE_PATH_SH = path.join(REPO, 'infra', 'core-path.sh');
|
|
25
25
|
|
|
26
|
-
// infra/
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
26
|
+
// infra/ is HOST topology (publish-manifest.js PROTECTED_TOPOLOGY_EXCLUDE) — absent
|
|
27
|
+
// from a neutral core checkout (task 1002470). The real-tree checks only mean
|
|
28
|
+
// something where that tree exists; the synthetic counterfactual below always runs.
|
|
29
|
+
// Task 1004859: this used to key on .github/workflows/gate-review.yml, a workflow that
|
|
30
|
+
// was only ever planned; it now keys on the resolver every routed invocation needs.
|
|
31
|
+
const HAS_HOST_TOPOLOGY = fs.existsSync(path.join(REPO, 'infra', 'core-node.sh'));
|
|
31
32
|
const HAS_CORE_PATH = fs.existsSync(CORE_PATH_SH);
|
|
32
33
|
|
|
33
34
|
let passed = 0, failed = 0;
|
|
@@ -42,14 +43,11 @@ function hostTest(name, present, fn) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// ---- A. the workflow lint is green on the real tree ---------------------------------------------
|
|
45
|
-
hostTest('lint-workflow-exec: every core invocation is routed + publishable (no bare repo-relative form)',
|
|
46
|
+
hostTest('lint-workflow-exec: every core invocation is routed + publishable (no bare repo-relative form)', HAS_HOST_TOPOLOGY, () => {
|
|
46
47
|
const r = lintWorkflowExec({ instanceRoot: REPO, coreRoot: REPO });
|
|
47
48
|
assert.deepEqual(r.violations, [], `expected no violations, got: ${JSON.stringify(r.violations, null, 2)}`);
|
|
48
49
|
assert.ok(r.ok, 'lint should pass');
|
|
49
|
-
// The security-critical merge gates + the functional gates must all be covered.
|
|
50
50
|
const rels = r.checked.map((c) => `${c.file}:${c.rel}`);
|
|
51
|
-
assert.ok(rels.some((s) => s.startsWith('gate-review.yml:') && s.endsWith('scripts/bongos/gate-review.js')), 'gate-review routed');
|
|
52
|
-
assert.ok(rels.some((s) => s.startsWith('grade-gate.yml:') && s.endsWith('scripts/bongos/ci-grade.js')), 'grade-gate routed');
|
|
53
51
|
assert.ok(rels.some((s) => s.endsWith('scripts/migrate.sh')), 'selfhost migrate routed via core-path.sh');
|
|
54
52
|
});
|
|
55
53
|
|
|
@@ -58,9 +56,9 @@ test('lint-workflow-exec: a bare core invocation in a workflow is flagged (count
|
|
|
58
56
|
try {
|
|
59
57
|
fs.mkdirSync(path.join(tmp, '.github', 'workflows'), { recursive: true });
|
|
60
58
|
fs.mkdirSync(path.join(tmp, 'scripts', 'bongos'), { recursive: true });
|
|
61
|
-
fs.writeFileSync(path.join(tmp, 'scripts', 'bongos', '
|
|
59
|
+
fs.writeFileSync(path.join(tmp, 'scripts', 'bongos', 'main-audit.js'), '// probe\n');
|
|
62
60
|
fs.writeFileSync(path.join(tmp, '.github', 'workflows', 'legacy.yml'),
|
|
63
|
-
'jobs:\n x:\n steps:\n - run: node scripts/bongos/
|
|
61
|
+
'jobs:\n x:\n steps:\n - run: node scripts/bongos/main-audit.js\n');
|
|
64
62
|
const r = lintWorkflowExec({ instanceRoot: tmp, coreRoot: tmp });
|
|
65
63
|
assert.ok(!r.ok, 'a bare core invocation must fail the lint');
|
|
66
64
|
assert.equal(r.violations.length, 1);
|