@bongos/core 1.20.27 → 1.20.28

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 (32) hide show
  1. package/.bongos-core.json +59 -34
  2. package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +1 -1
  3. package/docs/adr/0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md +1 -1
  4. package/docs/adr/0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md +46 -0
  5. package/docs/adr/README.md +1 -0
  6. package/docs/api/openapi.json +2 -2
  7. package/docs/copy-inventory.md +20 -19
  8. package/docs/copy-registry.json +34 -25
  9. package/docs/module-api-changelog.md +3 -1
  10. package/docs/page-readings.json +9 -9
  11. package/migrations/core_266_access_requests_invited_login_idx.sql +31 -0
  12. package/modules/hall-ui/public/watch.js +9 -3
  13. package/modules/onboarding/routes/access-requests.js +43 -20
  14. package/modules/platform-identity/platform-identity.js +12 -8
  15. package/modules/platform-identity/tests/platform-identity.mjs +4 -2
  16. package/package-lock.json +2 -2
  17. package/package.json +1 -1
  18. package/release-notes.json +10 -0
  19. package/scripts/gds/fitness.js +4 -0
  20. package/scripts/gds/login.js +6 -0
  21. package/scripts/gds/run-unit-tests.js +4 -0
  22. package/src/bongos/db-kernel.js +6 -3
  23. package/src/bongos/invite-expiry.js +47 -0
  24. package/src/module-api.js +10 -1
  25. package/tests/access_requests_invite.mjs +2 -1
  26. package/tests/application_lifecycle.mjs +164 -5
  27. package/tests/bongos_login.mjs +2 -0
  28. package/tests/hub_invite_notices.mjs +5 -2
  29. package/tests/invite_expiry.mjs +37 -0
  30. package/tests/invite_expiry_db.mjs +196 -0
  31. package/tests/module_api.mjs +1 -0
  32. package/tests/project_admin_console_model.mjs +12 -5
@@ -49,6 +49,7 @@ const PUBLISHED_SURFACE = [
49
49
  'artistGate', // added by task 1003575 (ADR 0241): the artist gate — off | advisory | strict, plus the pure verdict helpers over it. Two modules ask it two different questions ("file this review?" and "does an open one hold the deploy?") and both must resolve the knob through this one reader, never branding().project
50
50
  'readApplicantProfileFromHub', // added by task 1002972 (privacy spec D7): the reviewer queue's LIVE per-render read of one applicant's hub profile view. NARROW by design — the underlying call is authenticated with this instance's hub client secret, and loadIdpConfig stays OFF the doorway so no module ever holds the credential that speaks for the whole project
51
51
  'notifyHubOfInvite', // added by task 1002818 (ADR 0353): tell the hub about one of THIS project's invites (or its rescind) so the invitee sees it in My Projects. The same NARROW-port reason as readApplicantProfileFromHub: the call carries this instance's hub client secret, so a module passes one login and one direction and can neither widen nor re-address it
52
+ 'inviteExpiry', // added by task 1002969 (ADR 0356): the ONE read-time definition of when a recruit invite's door closes. The sign-in gate reads it in core; the onboarding queues and the hub's notice list are modules, so without the doorway each would restate the 30 days and the predicate, and two copies can close a door on different days
52
53
  'readApplicationVouch', // added by task 1002285 (ADR 0201 D2): the public access-request write's check of the hub's application vouch. NARROW for the same reason — core supplies the hub key and this instance's client_id, so a module passes a token and a login and can neither swap the key nor re-address the audience
53
54
  'enabledDisciplines', // BV1.R81: instance offered-disciplines (1.9.0; onboarding restock backstop)
54
55
  'isModuleEnabled', // task 1003881: is ANOTHER module present? onboarding's Discord approve/decline control is only actionable when the `discord` module's reaction handler is loaded, so the posting half has to be able to ask
@@ -6,8 +6,9 @@
6
6
  // drift quietly is worse than none, so the load-bearing ones are pinned here:
7
7
  //
8
8
  // D1 the hub relays and notifies; it never writes a project's admission row
9
- // D2 an invite is a row the existing gate already reads — `kind` and
10
- // `created_by` are descriptive and never enter the admission read
9
+ // D2 an invite is a row the existing gate already reads — `created_by` never
10
+ // enters the admission read, and `kind` enters it only through the invite
11
+ // expiry, which can close a door and never open one (ADR 0356 amends D2.5)
11
12
  // D3 the admission verdict stays `access_request.review` at the archon floor,
12
13
  // and `project.recruit`, once minted, takes exactly the shape the ADR fixes
13
14
  //
@@ -66,16 +67,22 @@ test('the ADR exists, is indexed, and ADR 0141 names it as superseding its §5 i
66
67
 
67
68
  // Both admission reads: the gate's own (hasInvitedAccessRequest) and the state the
68
69
  // "confirming access" waiting page polls (accessStateByLogin), which must agree with it.
69
- test('D2: both admission reads look at status alone — an invite and an approved application admit identically', () => {
70
+ // Since ADR 0356 (task 1002969) `kind` reaches them through ONE door: the ANDed
71
+ // invite-expiry unit, which only ever narrows `status = 'invited'`.
72
+ test('D2: both admission reads look at status, narrowed only by the invite expiry — never widened by kind', () => {
70
73
  const src = read('src', 'bongos', 'db-kernel.js');
71
74
  for (const fn of ['hasInvitedAccessRequest', 'accessStateByLogin']) {
72
75
  const m = new RegExp(`async function ${fn}\\([^)]*\\)\\s*\\{([\\s\\S]*?)\\n\\}`).exec(src);
73
76
  assert.ok(m, `${fn} is still an admission read in db-kernel.js`);
74
77
  const body = m[1];
75
- assert.match(body, /status\s*=\s*'invited'/, `${fn}: admission is status = invited`);
76
- assert.doesNotMatch(body, /\bkind\b/, `${fn}: kind is descriptive; it must never decide admission`);
78
+ assert.match(body, /status\s*=\s*'invited'\s+AND\s+\$\{inviteLiveSql\(\)\}/, `${fn}: admission is status = invited AND the invite has not expired`);
79
+ assert.doesNotMatch(body, /\bkind\b/, `${fn}: kind is never read directly; only through inviteLiveSql`);
77
80
  assert.doesNotMatch(body, /\bcreated_by\b/, `${fn}: created_by is audit data; it must never decide admission`);
78
81
  }
82
+ // And that unit can only close a door: NOT (an invite AND past its window).
83
+ const { inviteLiveSql } = require('../src/bongos/invite-expiry.js');
84
+ assert.match(inviteLiveSql(), /^NOT \(kind = 'invite' AND created_at <= now\(\) - interval '\d+ days'\)$/,
85
+ 'the expiry is a pure narrowing: a row that is not an invite is untouched by it');
79
86
  });
80
87
 
81
88
  test('D1: no hub (platform-identity) source writes access_requests — the hub relays, it never admits', () => {