1b40d7dc00
Why: Task #220 added a client-only `_sid` field on attendee rows so React DnD can identify rows. The two client save sites already enumerate wire fields explicitly and never serialize `_sid`, but nothing prevents a future refactor from accidentally leaking it. The server was zod-default lenient (silently strips unknowns), so a regression would either be silently absorbed (bad — silent contract drift) or land in a future JSONB metadata column without anyone noticing. What changed: - `attendeeSchema` in artifacts/api-server/src/routes/executive-meetings.ts is now `.strict()`. Any unknown attendee key (including `_sid` or any future client-only field) is rejected with HTTP 400 instead of being silently stripped. The schema is reused by all three attendee-bearing endpoints (POST /executive-meetings, PATCH /executive-meetings/:id, PUT /executive-meetings/:id/attendees), so all three are covered by one change. Tests: - Added three API tests in artifacts/api-server/tests/executive-meetings.test.mjs: 1. POST /executive-meetings with attendee carrying `_sid` returns 400 and the error mentions the rejected key. 2. PATCH /executive-meetings/:id with attendees carrying `_sid` returns 400 AND the meeting's existing attendee list is preserved (no partial mutation). 3. PUT /executive-meetings/:id/attendees with attendee carrying `_sid` returns 400 AND the seeded attendee is unchanged. Verification: - Full API test suite: 207/207 green (was 204/204 before; +3 new). - No client-side change needed: existing `saveAttendeeName` (~L943 in artifacts/tx-os/src/pages/executive-meetings.tsx) and the manage-dialog save (~L4004) already project to the documented wire shape (`name, title, attendanceType, sortOrder, kind`). - Architect review: addressed the one gap (PATCH coverage) by adding test #2 above; verdict resolved. Out of scope cleanup: - Marked the descriptions of stale tasks #172 and #179 as STALE (both PDF tests pass and PDF export works in current main). Final cancellation left to the user.