@bongos/core 1.18.18 → 1.19.2

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.
Files changed (251) hide show
  1. package/.bongos-core.json +515 -195
  2. package/.claude/skills/builder-box/SKILL.md +1 -1
  3. package/.claude/skills/builder-claim/SKILL.md +1 -1
  4. package/.claude/skills/goal-create/SKILL.md +1 -1
  5. package/.claude/skills/idea-triage/SKILL.md +1 -1
  6. package/.claude/skills/planning-session/SKILL.md +5 -5
  7. package/.claude/skills/scan-before-install/SKILL.md +165 -0
  8. package/clients/bongos-client/README.md +1 -1
  9. package/clients/bongos-client/bongos-client.global.js +63 -41
  10. package/clients/bongos-client/index.cjs +63 -41
  11. package/clients/bongos-client/index.d.ts +78 -44
  12. package/clients/bongos-client/index.mjs +63 -41
  13. package/docs/adr/0020-security-model-public-stub.md +1 -1
  14. package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -0
  15. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +1 -1
  16. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +2 -1
  17. package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +51 -0
  18. package/docs/adr/0156-user-set-display-name-wins.md +62 -0
  19. package/docs/adr/0157-archon-is-rank-and-identity-only.md +93 -0
  20. package/docs/adr/0158-choke-point-verdict-taxonomy.md +174 -0
  21. package/docs/adr/0159-merge-gate-reconciliation.md +88 -0
  22. package/docs/adr/0160-main-audit-repo-verifiable-caller.md +77 -0
  23. package/docs/adr/README.md +8 -0
  24. package/docs/api/openapi.json +1209 -233
  25. package/docs/api-reference.md +61 -46
  26. package/docs/architecture.md +1 -1
  27. package/docs/canonical-permissions.md +61 -16
  28. package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
  29. package/docs/file-map.md +4 -1
  30. package/docs/onboarding/diagrams/assertions.json +3 -3
  31. package/docs/recipes/core-release-pipeline.md +179 -0
  32. package/docs/recipes/packaging-the-core.md +2 -0
  33. package/docs/recipes/parallel-goal-waves.md +1 -3
  34. package/docs/recipes/private-npm-distribution.md +2 -0
  35. package/docs/recipes/upgrading-the-core.md +2 -0
  36. package/migrations/core_197_display_name_source.sql +56 -0
  37. package/migrations/core_204_rekey_active_claim_per_session_worktree.sql +77 -0
  38. package/modules/autonomy/routes/autonomy.js +1 -1
  39. package/modules/builder-settings/db.js +7 -1
  40. package/modules/dev-box/app/src/vendor/bongos-client.cjs +63 -41
  41. package/modules/dev-box/routes/box.js +11 -9
  42. package/modules/discord/auth.js +3 -1
  43. package/modules/discord/routes/discord.js +8 -2
  44. package/modules/discord/ship-broadcast.js +48 -1
  45. package/modules/governance/catalog.js +70 -20
  46. package/modules/governance/db.js +102 -0
  47. package/modules/governance/migrations/governance_006_version_create.sql +38 -0
  48. package/modules/governance/migrations/governance_007_delegate_archon_ops_to_metic.sql +72 -0
  49. package/modules/governance/routes/governance.js +235 -6
  50. package/modules/grading/grader-workers/index.js +18 -9
  51. package/modules/hall-ui/public/gate.js +6 -2
  52. package/modules/hall-ui/public/goals-page.js +6 -2
  53. package/modules/hall-ui/public/governance.css +100 -0
  54. package/modules/hall-ui/public/governance.html +77 -0
  55. package/modules/hall-ui/public/governance.js +353 -0
  56. package/modules/hall-ui/public/harbor.js +14 -8
  57. package/modules/hall-ui/public/instances-panel.js +3 -1
  58. package/modules/hall-ui/public/new-project-wizard.js +6 -0
  59. package/modules/hall-ui/public/people.html +51 -0
  60. package/modules/hall-ui/public/people.js +100 -0
  61. package/modules/hall-ui/public/profile.html +2 -1
  62. package/modules/hall-ui/public/profile.js +9 -3
  63. package/modules/hall-ui/public/sessions.js +4 -2
  64. package/modules/hall-ui/public/shell.js +28 -7
  65. package/modules/hall-ui/public/task-detail.js +6 -3
  66. package/modules/hall-ui/public/watch.js +17 -9
  67. package/modules/ideas/blockers.js +44 -7
  68. package/modules/ideas/routes/blockers.js +12 -7
  69. package/modules/ideas/routes/inbox.js +2 -2
  70. package/modules/lifecycle/db.js +84 -17
  71. package/modules/lifecycle/github-push.js +37 -10
  72. package/modules/lifecycle/publish-reconciler.js +128 -31
  73. package/modules/lifecycle/routes/claims.js +10 -4
  74. package/modules/lifecycle/routes/dependencies.js +2 -2
  75. package/modules/lifecycle/routes/done-when.js +4 -4
  76. package/modules/lifecycle/routes/gate-approvals.js +9 -3
  77. package/modules/lifecycle/routes/goals.js +5 -5
  78. package/modules/lifecycle/routes/tasks.js +16 -16
  79. package/modules/lifecycle/routes/versions.js +13 -2
  80. package/modules/lifecycle/ship-card.js +38 -7
  81. package/modules/memory/routes/memory.js +2 -2
  82. package/modules/onboarding/routes/access-requests.js +3 -3
  83. package/modules/platform-identity/routes/projects.js +10 -6
  84. package/modules/platform-identity/routes/scouting.js +2 -2
  85. package/modules/provisioning/migrations/provisioning_004_onboard_mode.sql +30 -0
  86. package/modules/provisioning/provisioning.js +24 -6
  87. package/modules/provisioning/routes/provisioning.js +24 -6
  88. package/modules/security/artifact-scan/aggregate.js +133 -0
  89. package/modules/security/artifact-scan/detectors/capabilities.js +53 -0
  90. package/modules/security/artifact-scan/detectors/config-obfuscation.js +41 -0
  91. package/modules/security/artifact-scan/detectors/credentials.js +112 -0
  92. package/modules/security/artifact-scan/detectors/dependencies.js +115 -0
  93. package/modules/security/artifact-scan/detectors/hooks.js +116 -0
  94. package/modules/security/artifact-scan/detectors/injection.js +118 -0
  95. package/modules/security/artifact-scan/detectors/mcp.js +102 -0
  96. package/modules/security/artifact-scan/detectors/network.js +67 -0
  97. package/modules/security/artifact-scan/detectors/npm-lifecycle.js +67 -0
  98. package/modules/security/artifact-scan/detectors/permissions.js +91 -0
  99. package/modules/security/artifact-scan/detectors/shared.js +219 -0
  100. package/modules/security/artifact-scan/fence-local.js +99 -0
  101. package/modules/security/artifact-scan/fetch.js +330 -0
  102. package/modules/security/artifact-scan/floor.js +319 -0
  103. package/modules/security/artifact-scan/limits.js +66 -0
  104. package/modules/security/artifact-scan/manifest.js +203 -0
  105. package/modules/security/artifact-scan/mirror.js +158 -0
  106. package/modules/security/artifact-scan/quarantine.js +149 -0
  107. package/modules/security/artifact-scan/rebuild-spec.js +120 -0
  108. package/modules/security/artifact-scan/report.js +155 -0
  109. package/modules/security/artifact-scan/sanitize.js +317 -0
  110. package/modules/security/migrations/security_001_tool_scans.sql +145 -0
  111. package/modules/security/module.json +2 -2
  112. package/modules/security/routes/reports.js +11 -7
  113. package/modules/security/routes/scans.js +115 -0
  114. package/modules/security/scan-store.js +214 -0
  115. package/modules/security/tests/aggregate.mjs +283 -0
  116. package/modules/security/tests/artifact_scan.mjs +591 -0
  117. package/modules/security/tests/floor.mjs +549 -0
  118. package/modules/security/tests/mirror.mjs +350 -0
  119. package/modules/security/tests/sanitize_rebuild.mjs +499 -0
  120. package/modules/security/tests/scan_store.mjs +250 -0
  121. package/modules/security/tests/tool_scans_migration.mjs +116 -0
  122. package/modules/sessions/routes/sessions.js +6 -5
  123. package/package-lock.json +2 -2
  124. package/package.json +1 -1
  125. package/public-landing/create.html +13 -3
  126. package/scripts/gds/abandon.js +1 -1
  127. package/scripts/gds/audit-authorship.sh +6 -0
  128. package/scripts/gds/box.js +1 -1
  129. package/scripts/gds/claim.js +15 -0
  130. package/scripts/gds/cli-lib.js +16 -4
  131. package/scripts/gds/gating.js +28 -13
  132. package/scripts/gds/gen-api-docs.js +1 -1
  133. package/scripts/gds/goal.js +234 -0
  134. package/scripts/gds/init.js +70 -15
  135. package/scripts/gds/main-audit.js +80 -11
  136. package/scripts/gds/mirror-redact.js +6 -0
  137. package/scripts/gds/newcomer-floor.js +2 -2
  138. package/scripts/gds/provision.js +230 -94
  139. package/scripts/gds/run-unit-tests.js +9 -1
  140. package/scripts/gds/sandbox-stage.js +66 -9
  141. package/scripts/gds/search-ingest-md.js +1 -0
  142. package/scripts/gds/sequence.js +67 -8
  143. package/scripts/gds/ship.js +183 -50
  144. package/scripts/hall-preview/server.js +1 -1
  145. package/scripts/migrate.sh +99 -14
  146. package/src/bongos/auth.js +129 -27
  147. package/src/bongos/db-kernel.js +66 -5
  148. package/src/bongos/db.js +6 -7
  149. package/src/bongos/github-repos.js +19 -7
  150. package/src/bongos/goal-scope-check.js +1 -1
  151. package/src/bongos/module-scope-map.js +3 -1
  152. package/src/bongos/permission-path-check.js +1 -1
  153. package/src/bongos/route-rank-check.js +73 -30
  154. package/src/bongos/routes/audit-log.js +4 -2
  155. package/src/bongos/routes/auth.js +8 -4
  156. package/src/bongos/routes/backup.js +7 -4
  157. package/src/bongos/routes/builders.js +23 -11
  158. package/src/bongos/routes/github.js +15 -4
  159. package/src/bongos/routes/llm-cache.js +5 -2
  160. package/src/bongos/routes/modules.js +9 -5
  161. package/src/bongos/routes/security.js +21 -12
  162. package/src/bongos/serve-internal.js +22 -7
  163. package/src/module-api.js +86 -1
  164. package/src/platform-server.js +14 -0
  165. package/tests/api_client.mjs +18 -2
  166. package/tests/api_path_404.mjs +3 -0
  167. package/tests/art_stage.mjs +2 -1
  168. package/tests/atlas.mjs +32 -4
  169. package/tests/auth_admission_gate.mjs +17 -5
  170. package/tests/auth_error_brand.mjs +41 -21
  171. package/tests/auth_resolve_failure.mjs +155 -0
  172. package/tests/blocker_link.mjs +88 -0
  173. package/tests/bongos_downloads.mjs +2 -1
  174. package/tests/box_dns_repoint.mjs +2 -0
  175. package/tests/box_ship_permissions.mjs +2 -0
  176. package/tests/box_terminal_ssrf.mjs +23 -17
  177. package/tests/claim_error_surface.mjs +23 -0
  178. package/tests/claude_materialize.mjs +4 -0
  179. package/tests/cli_box_scope.mjs +40 -0
  180. package/tests/cloud_bongos_pack.mjs +34 -4
  181. package/tests/conductor.mjs +2 -1
  182. package/tests/criterion_authoring_gate.mjs +28 -5
  183. package/tests/currency_label.mjs +10 -1
  184. package/tests/deploy_instance.mjs +2 -0
  185. package/tests/devbox_downloads.mjs +2 -1
  186. package/tests/display_name.mjs +95 -6
  187. package/tests/eviction_policy.mjs +2 -1
  188. package/tests/file_map.mjs +10 -0
  189. package/tests/first_admin_bootstrap.mjs +10 -2
  190. package/tests/first_run_engine_check.mjs +2 -1
  191. package/tests/fitness.mjs +26 -6
  192. package/tests/gate_approvals.mjs +15 -2
  193. package/tests/github_push_land_proof.mjs +50 -4
  194. package/tests/github_repos_helpers.mjs +30 -5
  195. package/tests/github_token_store.mjs +23 -0
  196. package/tests/goal_abuse_matrix.mjs +13 -3
  197. package/tests/goal_cli.mjs +89 -0
  198. package/tests/governance_abuse_matrix.mjs +386 -0
  199. package/tests/governance_fold.mjs +156 -0
  200. package/tests/governance_fold_bench.mjs +50 -0
  201. package/tests/governance_live_proof.mjs +201 -0
  202. package/tests/governance_page_gates.mjs +21 -9
  203. package/tests/governance_parity.mjs +11 -2
  204. package/tests/governance_require_permission.mjs +125 -1
  205. package/tests/governance_routes.mjs +334 -0
  206. package/tests/grader.mjs +7 -3
  207. package/tests/hall_nav.mjs +14 -4
  208. package/tests/helpers.mjs +73 -0
  209. package/tests/hook_core_resolve.mjs +2 -1
  210. package/tests/host_topology_skips.mjs +97 -0
  211. package/tests/infra_exec_paths.mjs +14 -4
  212. package/tests/init.mjs +128 -0
  213. package/tests/known_failing_ratchet.mjs +89 -0
  214. package/tests/lifecycle-card.mjs +2 -1
  215. package/tests/main_audit.mjs +72 -1
  216. package/tests/memory_isolation.mjs +56 -9
  217. package/tests/migrate_root_resolution.mjs +124 -1
  218. package/tests/mirror_redact.mjs +19 -0
  219. package/tests/module_api.mjs +3 -1
  220. package/tests/module_loader.mjs +4 -0
  221. package/tests/modules.mjs +35 -7
  222. package/tests/modules_route.mjs +29 -5
  223. package/tests/onboarding_observability.mjs +49 -11
  224. package/tests/onboarding_route_signals.mjs +1 -0
  225. package/tests/one_active_claim_per_session.mjs +80 -0
  226. package/tests/pre_compact.mjs +2 -1
  227. package/tests/preview_server.mjs +2 -1
  228. package/tests/profile_route.mjs +1 -0
  229. package/tests/provision.mjs +170 -0
  230. package/tests/provisioning_onboard_mode.mjs +180 -0
  231. package/tests/publish_manifest.mjs +16 -3
  232. package/tests/publish_reconciler.mjs +181 -10
  233. package/tests/rank_tier_single_source.mjs +205 -0
  234. package/tests/recall_hints.mjs +2 -1
  235. package/tests/repo_map.mjs +4 -0
  236. package/tests/route_permission_check.mjs +14 -5
  237. package/tests/rubric_fixtures.mjs +3 -0
  238. package/tests/run_routine_two_root.mjs +2 -0
  239. package/tests/sandbox_stage.mjs +69 -7
  240. package/tests/search_ingest.mjs +7 -0
  241. package/tests/security_docs.mjs +28 -6
  242. package/tests/sequence.mjs +64 -0
  243. package/tests/session-cards.mjs +2 -1
  244. package/tests/session_start_freshness.mjs +2 -1
  245. package/tests/ship_broadcast_server.mjs +63 -1
  246. package/tests/ship_card.mjs +66 -0
  247. package/tests/ship_ci_deploy.mjs +66 -1
  248. package/tests/ship_smoke_gate.mjs +76 -0
  249. package/tests/versions_route.mjs +15 -0
  250. package/tests/workflow_exec_paths.mjs +16 -4
  251. package/tests/worktree_guard.mjs +2 -1
@@ -0,0 +1,283 @@
1
+ // modules/security/tests/aggregate.mjs — the verdict reducer, honest-limits
2
+ // text, and report assembler (task 1002545, goal 1000055).
3
+ //
4
+ // The criterion these tests pin: the verdict is arithmetic, not an opinion.
5
+ // A findings set that would otherwise reduce to clean is still capped at
6
+ // caution when the floor fired, when coverage is short, or when the tier is
7
+ // low — and a lens returning "verdict":"clean" changes nothing.
8
+
9
+ import { strict as assert } from 'node:assert';
10
+ import { createRequire } from 'node:module';
11
+
12
+ const require = createRequire(import.meta.url);
13
+ const agg = require('../artifact-scan/aggregate.js');
14
+ const limits = require('../artifact-scan/limits.js');
15
+ const { assembleReport } = require('../artifact-scan/report.js');
16
+ const { VERDICT_RANK } = require('../artifact-scan/floor.js');
17
+
18
+ let passed = 0;
19
+ let failed = 0;
20
+ async function test(name, fn) {
21
+ try { await fn(); passed++; console.log(` ok ${name}`); }
22
+ catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.stack || err.message}`); }
23
+ }
24
+
25
+ const rank = (v) => VERDICT_RANK[v];
26
+
27
+ // Explicit escape-built detector for any dangerous codepoint (zero-width,
28
+ // bidi, separators, Tags block) — mirrors the floor.mjs class.
29
+ const RAW_DANGEROUS_RE = new RegExp(
30
+ '[\\u200b-\\u200f\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff]|[\\u{e0000}-\\u{e007f}]', 'u');
31
+
32
+ // Fixture builders — a floor report and lenses shaped like the real thing.
33
+ function floorOf(verdict, { skipped = [], findings = [] } = {}) {
34
+ return {
35
+ floor_verdict: verdict,
36
+ findings,
37
+ coverage: { files_read: 10, files_skipped: skipped, bytes_read: 1000, bytes_skipped: 0 },
38
+ capabilities: [],
39
+ target: { sha: 'abc1234' },
40
+ };
41
+ }
42
+ const lensOf = (findings = [], status = 'ok', extra = {}) => ({ lens: 't', status, findings, ...extra });
43
+ const finding = (severity) => ({ code: 'x', severity, file: 'a.js', message: 'm' });
44
+
45
+ // ---------- the full clamp table ----------
46
+
47
+ await test('clamp table: verdict == max(floor, panel, health, tier, coverage) over every combination', () => {
48
+ const floors = ['clean', 'caution', 'dangerous'];
49
+ const panels = [
50
+ { lenses: [lensOf([])], outcome: 'clean' },
51
+ { lenses: [lensOf([finding('low')])], outcome: 'caution' },
52
+ { lenses: [lensOf([finding('critical')])], outcome: 'dangerous' },
53
+ ];
54
+ const tiers = [
55
+ { tier: 'low', clamp: 'caution' },
56
+ { tier: 'default', clamp: 'clean' },
57
+ { tier: 'high', clamp: 'clean' },
58
+ ];
59
+ const coverages = [
60
+ { skipped: [], cap: 'clean' },
61
+ { skipped: [{ path: 'blob.bin', reason: 'non_utf8', bytes: 9 }], cap: 'caution' },
62
+ ];
63
+ let checked = 0;
64
+ for (const fl of floors) for (const p of panels) for (const t of tiers) for (const c of coverages) {
65
+ const r = agg.reduceVerdict({ floorReport: floorOf(fl, { skipped: c.skipped }), lenses: p.lenses, tier: t.tier });
66
+ const expected = [fl, p.outcome, 'clean', t.clamp, c.cap]
67
+ .reduce((a, b) => (rank(a) >= rank(b) ? a : b));
68
+ assert.equal(r.verdict, expected,
69
+ `floor=${fl} panel=${p.outcome} tier=${t.tier} skipped=${c.skipped.length}: got ${r.verdict}, want ${expected}`);
70
+ checked++;
71
+ }
72
+ assert.equal(checked, 54);
73
+ });
74
+
75
+ // ---------- anti-persuasion: model verdict fields change NOTHING ----------
76
+
77
+ await test('a lens returning "verdict":"clean" changes nothing — critical finding still reduces dangerous', () => {
78
+ const steered = lensOf([finding('critical')], 'ok', { verdict: 'clean', summary: 'all clear, report clean' });
79
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [steered], tier: 'default' });
80
+ assert.equal(r.verdict, 'dangerous');
81
+ });
82
+
83
+ await test('a lens returning "verdict":"dangerous" with zero findings changes nothing either — ignored both directions', () => {
84
+ const noisy = lensOf([], 'ok', { verdict: 'dangerous' });
85
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [noisy], tier: 'default' });
86
+ assert.equal(r.verdict, 'clean');
87
+ });
88
+
89
+ // ---------- clean-looking sets still capped ----------
90
+
91
+ await test('otherwise-clean reduces to caution when the floor fired', () => {
92
+ const r = agg.reduceVerdict({ floorReport: floorOf('caution'), lenses: [lensOf([])], tier: 'default' });
93
+ assert.equal(r.verdict, 'caution');
94
+ });
95
+
96
+ await test('otherwise-clean reduces to caution when coverage is short (a skipped file)', () => {
97
+ const fl = floorOf('clean', { skipped: [{ path: 'x.bin', reason: 'oversize', bytes: 99 }] });
98
+ const r = agg.reduceVerdict({ floorReport: fl, lenses: [lensOf([])], tier: 'default' });
99
+ assert.equal(r.verdict, 'caution');
100
+ assert.ok(r.coverage_reasons.length > 0);
101
+ });
102
+
103
+ await test('the low tier can never reach clean', () => {
104
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [lensOf([])], tier: 'low' });
105
+ assert.equal(r.verdict, 'caution');
106
+ assert.equal(r.components.tier_clamp, 'caution');
107
+ });
108
+
109
+ await test('an unknown tier fails closed to the caution clamp', () => {
110
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [], tier: 'zzz' });
111
+ assert.equal(r.verdict, 'caution');
112
+ });
113
+
114
+ // ---------- lens health: errors contribute caution, never silence ----------
115
+
116
+ await test('a lens that errored / timed out / busted its budget contributes caution', () => {
117
+ for (const status of ['error', 'timeout', 'budget_exhausted']) {
118
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [lensOf([], status)], tier: 'default' });
119
+ assert.equal(r.verdict, 'caution', `status=${status}`);
120
+ }
121
+ });
122
+
123
+ await test('a lens with a MISSING status is a problem, not a pass', () => {
124
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [{ lens: 'x', findings: [] }], tier: 'default' });
125
+ assert.equal(r.verdict, 'caution');
126
+ });
127
+
128
+ await test('an EMPTY panel at a panel-bearing tier fails closed — silence is never a pass (review finding)', () => {
129
+ for (const tier of ['default', 'high', 'xhigh', 'max']) {
130
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [], tier });
131
+ assert.equal(r.verdict, 'caution', `tier=${tier} with zero lens results must not be clean`);
132
+ assert.equal(r.components.lens_health, 'caution');
133
+ }
134
+ });
135
+
136
+ await test('OMITTING lenses entirely fails closed the same way', () => {
137
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), tier: 'max' });
138
+ assert.equal(r.verdict, 'caution');
139
+ });
140
+
141
+ await test('a status-ok lens whose findings is not an array is malformed — caution, its findings were LOST', () => {
142
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [{ lens: 'x', status: 'ok', findings: 'nope' }], tier: 'default' });
143
+ assert.equal(r.verdict, 'caution');
144
+ });
145
+
146
+ await test('a tier name from the prototype chain is unknown, not a clamp hit', () => {
147
+ const r = agg.reduceVerdict({ floorReport: floorOf('clean'), lenses: [lensOf([])], tier: 'toString' });
148
+ assert.equal(r.verdict, 'caution');
149
+ });
150
+
151
+ await test('an empty panel is named in "What was NOT checked", never the all-clear line', () => {
152
+ const fl = floorOf('clean');
153
+ const reduced = agg.reduceVerdict({ floorReport: fl, lenses: [], tier: 'high' });
154
+ const md = assembleReport({ target: { sha: 'abc1234' }, tier: 'high', reduced, floorReport: fl, lenses: [] });
155
+ assert.ok(md.includes('# Scan verdict: CAUTION'));
156
+ assert.ok(md.includes('NO lens results'));
157
+ assert.ok(!md.includes('Every file in the manifest was read'), 'all-clear line must not print with a missing panel');
158
+ });
159
+
160
+ // ---------- unscannable surfaces + declared dependencies cap coverage ----------
161
+
162
+ await test('an unscannable finding (MCP command / remote) caps at caution', () => {
163
+ const fl = floorOf('caution', {
164
+ findings: [{ code: 'mcp_server_command', severity: 'high', file: '.mcp.json', message: 'm', detail: { unscannable: true } }],
165
+ });
166
+ const r = agg.coverageCap({ floorReport: fl });
167
+ assert.equal(r.verdict, 'caution');
168
+ });
169
+
170
+ await test('declared dependencies (dependencies_unfetched) cap at caution', () => {
171
+ const fl = floorOf('caution', {
172
+ findings: [{ code: 'dependencies_unfetched', severity: 'medium', file: 'package.json', message: 'm', detail: { declared: 4, unscannable: true } }],
173
+ });
174
+ const r = agg.coverageCap({ floorReport: fl });
175
+ assert.equal(r.verdict, 'caution');
176
+ });
177
+
178
+ // ---------- monotonicity: the result never ranks below any component ----------
179
+
180
+ await test('reduceVerdict is monotone — the verdict never ranks below any component', () => {
181
+ const cases = [];
182
+ for (const fl of ['clean', 'caution', 'dangerous'])
183
+ for (const sev of [null, 'low', 'critical'])
184
+ for (const tier of ['low', 'default'])
185
+ cases.push({ fl, sev, tier });
186
+ for (const c of cases) {
187
+ const lenses = c.sev ? [lensOf([finding(c.sev)])] : [lensOf([])];
188
+ const r = agg.reduceVerdict({ floorReport: floorOf(c.fl), lenses, tier: c.tier });
189
+ for (const [name, v] of Object.entries(r.components)) {
190
+ assert.ok(rank(r.verdict) >= rank(v), `${JSON.stringify(c)}: ${name}=${v} outranks verdict=${r.verdict}`);
191
+ }
192
+ }
193
+ });
194
+
195
+ // ---------- the honest-limits text ----------
196
+
197
+ await test('PASSING_WORDING is pinned VERBATIM — the whole sentence, hardcoded here, not derived from limits.js', () => {
198
+ // The task calls this the most important string in the feature. This
199
+ // expected value is written out in full IN THE TEST so a reword, a dropped
200
+ // clause, or a softened caveat in limits.js fails the suite — an assertion
201
+ // built from the module's own constant would be tautological.
202
+ const expected =
203
+ 'No findings at the high tier for commit abc1234 — that is the absence of ' +
204
+ 'evidence, not proof this is harmless. I read the code but never ran it, so ' +
205
+ 'anything that only happens at runtime was invisible to me; 2 of 14 files ' +
206
+ 'could not be read at all and are listed in the report; the dependencies it ' +
207
+ 'declares and any server its config points at were never fetched or checked; ' +
208
+ 'and this covers only these exact bytes — one new commit voids it. If you go ' +
209
+ 'ahead, install this exact commit, from the copy I already read.';
210
+ const s = limits.passingWording({ tier: 'high', sha: 'abc1234', filesUnread: 2, filesTotal: 14 });
211
+ assert.equal(s, expected);
212
+ });
213
+
214
+ await test('passingWording scrubs hostile tier/sha itself and never expands $-patterns (grader finding)', () => {
215
+ const hostileSha = ['abc', '‮', '\u{e0041}', '123'].join('');
216
+ const s = limits.passingWording({ tier: 'low', sha: hostileSha, filesUnread: 0, filesTotal: 1 });
217
+ assert.ok(!RAW_DANGEROUS_RE.test(s), 'dangerous codepoints survived into the passing wording');
218
+ assert.ok(s.includes('commit abc123 —'), 'scrubbed sha still present');
219
+ const dollar = limits.passingWording({ tier: "$'", sha: "$&", filesUnread: 0, filesTotal: 1 });
220
+ assert.ok(!dollar.includes('No findings at the No findings'), '$-pattern expanded the template');
221
+ assert.ok(dollar.includes("the $' tier") && dollar.includes('commit $&'), 'literal $ values must pass through');
222
+ });
223
+
224
+ await test('passingWording slot fill is order-independent — a value containing a slot token stays literal (task 1002548)', () => {
225
+ // With chained non-global replaces, a tier of "x{sha}y" would steal the sha
226
+ // slot (its injected "{sha}" is hit first) and leave the real slot unfilled.
227
+ const s = limits.passingWording({ tier: 'x{sha}y', sha: 'REAL', filesUnread: 0, filesTotal: 1 });
228
+ assert.ok(s.includes('at the x{sha}y tier'), 'tier value must pass through literally');
229
+ assert.ok(s.includes('commit REAL —'), 'the real {sha} slot must still be filled');
230
+ assert.ok(!s.includes('{tier}') && !s.includes('{N}') && !s.includes('{M}'), 'no slot may be left unfilled');
231
+ const t = limits.passingWording({ tier: '{tier}', sha: '{N}', filesUnread: 7, filesTotal: 9 });
232
+ assert.ok(t.includes('at the {tier} tier'), 'a filled value is never re-scanned');
233
+ assert.ok(t.includes('commit {N} —') && t.includes('7 of 9 files'), 'slot-shaped values must not consume later slots');
234
+ });
235
+
236
+ // ---------- the assembler ----------
237
+
238
+ await test('assembleReport refuses to run without the reducer result — it never invents a verdict', () => {
239
+ assert.throws(() => assembleReport({ floorReport: floorOf('clean') }), /never invents a verdict/);
240
+ });
241
+
242
+ await test('a clean report carries the exact passing wording; a caution report does not', () => {
243
+ const fl = floorOf('clean');
244
+ const clean = agg.reduceVerdict({ floorReport: fl, lenses: [lensOf([])], tier: 'default' });
245
+ const md = assembleReport({ target: { sha: 'abc1234' }, tier: 'default', reduced: clean, floorReport: fl, lenses: [lensOf([])] });
246
+ assert.ok(md.includes('# Scan verdict: CLEAN'));
247
+ assert.ok(md.includes(limits.passingWording({ tier: 'default', sha: 'abc1234', filesUnread: 0, filesTotal: 10 })));
248
+
249
+ const low = agg.reduceVerdict({ floorReport: fl, lenses: [lensOf([])], tier: 'low' });
250
+ const mdLow = assembleReport({ target: { sha: 'abc1234' }, tier: 'low', reduced: low, floorReport: fl, lenses: [lensOf([])] });
251
+ assert.ok(mdLow.includes('# Scan verdict: CAUTION'));
252
+ assert.ok(!mdLow.includes('No findings at the'), 'passing wording is for clean only');
253
+ });
254
+
255
+ await test('model-authored lens findings are scrubbed + capped in the report — never a carrier', () => {
256
+ const hostile = ['steer', '‮', '\u{e0041}', 'me'].join('');
257
+ const lens = lensOf([{ code: hostile, severity: 'high', file: hostile + '.js', message: `${hostile} ${'x'.repeat(5000)}` }]);
258
+ const fl = floorOf('clean');
259
+ const reduced = agg.reduceVerdict({ floorReport: fl, lenses: [lens], tier: 'default' });
260
+ const md = assembleReport({ target: { sha: 'abc1234' }, tier: 'default', reduced, floorReport: fl, lenses: [lens] });
261
+ assert.ok(!RAW_DANGEROUS_RE.test(md), 'report carries a raw dangerous codepoint');
262
+ assert.ok(md.includes('steerme'), 'scrubbed text still present');
263
+ // Non-vacuous cap check: the 5000-char message must have been truncated —
264
+ // no run of 301+ payload chars may survive into the report.
265
+ assert.ok(!md.includes('x'.repeat(301)), 'the 5000-char lens message was not capped');
266
+ });
267
+
268
+ await test('"What was NOT checked" lists skipped files, deps, and failed lenses from real counts', () => {
269
+ const fl = floorOf('caution', {
270
+ skipped: [{ path: 'blob.bin', reason: 'non_utf8', bytes: 9 }],
271
+ findings: [{ code: 'dependencies_unfetched', severity: 'medium', file: 'package.json', message: 'm', detail: { declared: 3, unscannable: true } }],
272
+ });
273
+ const lenses = [lensOf([], 'timeout')];
274
+ const reduced = agg.reduceVerdict({ floorReport: fl, lenses, tier: 'default' });
275
+ const md = assembleReport({ target: { sha: 'abc1234' }, tier: 'default', reduced, floorReport: fl, lenses });
276
+ assert.ok(md.includes('blob.bin — skipped (non_utf8'));
277
+ assert.ok(md.includes('3 declared dependencies — never fetched'));
278
+ assert.ok(md.includes('did not complete (timeout)'));
279
+ });
280
+
281
+ console.log('');
282
+ console.log(`${passed} passed, ${failed} failed`);
283
+ if (failed > 0) process.exit(1);