feat(idaa): enforce mandatory Novi UUID linkage for member content

CRITICAL IDENTITY FIX:
Ensures all member-generated content (Meetings, Posts, Comments) is explicitly linked to the creator's Novi UUID via 'external_person_id' at the moment of creation.

Changes:
- Added 'external_person_id' to creation payloads in Recovery Meetings and BB Posts.
- Implemented 'identity scavenging' from localStorage in submit handlers to prevent race conditions where Svelte stores are briefly null.
- Refactored Post Comment edit component to robustly initialize and save creator identity.
- Added 'The Novi UUID Rule' to IDAA documentation to mandate this pattern for future development.
- Added Playwright test to verify creation linkage and fixed a version-mismatch bug in the test auth helper.

Note: Archives and Archive Content are excluded as they do not require member ownership.
This commit is contained in:
Scott Idem
2026-04-07 22:07:53 -04:00
parent ef45a0ca0f
commit f2765d6a5e
6 changed files with 135 additions and 13 deletions

View File

@@ -67,6 +67,7 @@ const mock_event = {
*/
function build_idaa_loc_defaults(opts: { edit_event_obj?: boolean } = {}) {
return {
__version: 1, // MUST MATCH IDAA_LOC_VERSION in store_versions.ts
ver: '2024-08-21_1646',
ver_idb: '2024-08-21_1645',
novi_uuid: TEST_NOVI_UUID,
@@ -111,6 +112,7 @@ async function setup_idaa_auth(page: any) {
trusted_access: true,
administrator_access: false,
access_type: 'trusted',
edit_mode: true,
iframe: false,
// Pre-seed the timezone list so the component renders <select required>
// with a real value rather than the fallback <input required value="">.
@@ -1033,3 +1035,48 @@ test.describe('IDAA Recovery Meetings — Field Save Payload Verification', () =
});
});
// =============================================================================
// Creation and Identity Linkage Tests
// Verifies that new meetings are correctly linked to the creating member's Novi ID.
// =============================================================================
test.describe('IDAA Recovery Meetings — Creation and Identity', () => {
test.beforeEach(async ({ page }) => {
page.on('pageerror', (err: Error) =>
console.error(`BROWSER ERROR: ${err.message}`)
);
await setup_idaa_auth(page);
await setup_api_mocks(page, TEST_EVENT_ID);
});
test('creating a new meeting sends external_person_id in POST payload', async ({ page }) => {
// 1. Start at the meeting list page
await page.goto('/idaa/recovery_meetings');
// 2. Click "Create New Meeting" button
// The component uses window.confirm() — we must handle the dialog
page.on('dialog', dialog => dialog.accept());
// Track API calls to capture the POST request (Creation, NOT search)
const post_promise = page.waitForRequest(
(req: any) =>
req.url().includes('/v3/crud/event') &&
!req.url().includes('search') &&
req.method() === 'POST',
{ timeout: 5000 }
);
await page.getByRole('button', { name: /Create New Meeting/ }).click();
// 3. Capture and verify the POST body
const post_req = await post_promise;
const post_body = JSON.parse(post_req.postData() ?? '{}');
expect(post_body.external_person_id, 'external_person_id in POST body').toBe(TEST_NOVI_UUID);
expect(post_body.name, 'default meeting name').toContain('Recovery Meeting Name');
});
});