feat(household-items): Story 4.1 — household items schema, migration, and shared types#396
Conversation
…IC-04 Story 4.1 Create the household items database schema with 6 tables: - household_items: core entity with status lifecycle, vendor association, delivery dates - household_item_tags: M:N junction with shared tags table - household_item_notes: comments/notes (mirrors work_item_notes pattern) - household_item_budgets: budget lines (mirrors work_item_budgets structure) - household_item_work_items: M:N coordination with work items - household_item_subsidies: M:N links to subsidy programs Add Drizzle ORM schema definitions following existing patterns. Create shared TypeScript types for household items with proper API response shapes: - HouseholdItemCategory (8 types: furniture, appliances, fixtures, decor, electronics, outdoor, storage, other) - HouseholdItemStatus (4 types: not_ordered, ordered, in_transit, delivered) - HouseholdItemSummary for list responses - HouseholdItemDetail for single-item responses with nested relationships - Request/response types for API endpoints Export all types from shared package. Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
…e tests for story 4.1 - 43 migration integration tests covering all 6 tables, constraints, and cascade behavior - 30 shared type tests verifying type correctness and interface shapes - Fix HouseholdItemSummary to include url and createdBy (moved from Detail-only) - Expand HouseholdItemListQuery with vendorId, tagId filters and additional sortBy options Fixes #387 Co-Authored-By: Claude dev-team-lead (Sonnet) <noreply@anthropic.com> Co-Authored-By: Claude backend-developer (Haiku) <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester (Haiku) <noreply@anthropic.com>
Add tagIds?: string[] to CreateHouseholdItemRequest and UpdateHouseholdItemRequest to match the API contract. Tags use set-semantics: sending tagIds replaces the entire tag set. Fixes review feedback from product-architect on PR #396. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-Authored-By: Claude backend-developer (Haiku) <noreply@anthropic.com>
steilerDev
left a comment
There was a problem hiding this comment.
[security-engineer]
Security Review — PR #396: Story 4.1 Household Items Schema & Migration
Reviewed: migration SQL, Drizzle schema definitions, shared TypeScript types, and migration integration tests.
Overall Assessment
This is a schema-and-types-only PR. No routes, middleware, or authentication logic is introduced. The security posture is strong — the schema follows all established patterns from previous EPICs and includes the planned_amount >= 0 CHECK that was retroactively identified as missing in PR #187. No blocking findings.
Checklist
- No SQL injection vectors — migration is pure DDL with static strings; test fixtures use parameterized queries (
?positional and@namedparams) throughout - Foreign key constraints correct — CASCADE on child junction tables, SET NULL on soft-reference columns (
vendor_id,created_by), matching the established pattern fromwork_itemsandmilestones - CHECK constraints present and complete —
category(8-value enum),status(4-value enum),quantity >= 1,planned_amount >= 0,confidence(4-value enum); all match corresponding TypeScript union types exactly - No sensitive data in shared types —
HouseholdItem,HouseholdItemSummary,HouseholdItemDetailexpose no credentials, tokens, or internal system identifiers - Index strategy is operationally justified — indexes cover filtering columns (
status,category,room,vendor_id) and sort column (created_at); no information leakage via naming - No hardcoded credentials or secrets
- No new dependencies introduced
- CORS/auth configuration unchanged
Informational Finding: url Field Stores User-Provided URLs
Severity: Informational
File: server/src/db/migrations/0010_household_items.sql, line 34
The url TEXT column on household_items is intended to store product purchase links (e.g., retailer URLs). No server-side fetch of this value is implemented in this PR. Future API stories that render or redirect to this URL must ensure:
- The value is stored as-is and never fetched server-side by Cornerstone (SSRF risk)
- The frontend renders it as a plain anchor link with
rel="noopener noreferrer"andtarget="_blank"(open-redirect/referrer risk) - No URL validation is performed at the schema layer (acceptable — this is the correct layer to defer to the API/service layer)
No action needed in this PR. Flag for the API implementation story.
Informational Finding: sortBy Enum Mixes snake_case and camelCase
Severity: Informational
File: shared/src/types/householdItem.ts, lines 157–165
The HouseholdItemListQuery.sortBy literal union includes values like 'order_date', 'expected_delivery_date', 'created_at', 'updated_at' (snake_case) while the rest of the API uses camelCase throughout. This is a design consistency note rather than a security issue.
From a security standpoint: if this value is passed directly into a SQL ORDER BY clause, the backend must whitelist it against known column names — the TypeScript union provides compile-time safety on the client, but the server must enforce the allowlist at runtime to prevent SQL injection via sort parameter tampering. Flag for the API implementation story to ensure the route handler validates against this exact whitelist before use in any query.
Positive Notes
- The
planned_amount >= 0CHECK constraint is present onhousehold_item_budgets— this was identified as missing in the equivalentwork_item_budgetstable (PR #187). Correct by design here. - Subsidy cascade behavior (
ON DELETE CASCADEonhousehold_item_subsidies.subsidy_program_id) is correct — deleting a subsidy program should remove all item associations. - Migration test coverage is thorough: all 6 tables, all CHECK constraints, all FK cascade/SET NULL behaviors, all indexes verified.
- The
document_linkstable already supportshousehold_itementity type (EPIC-08). Now that this migration adds thehousehold_itemstable, the application-layer guard rejecting that entity type can be relaxed in the API implementation story.
No blocking findings. Approved from a security perspective.
steilerDev
left a comment
There was a problem hiding this comment.
[product-architect] Review of Story 4.1: Household Items Schema & Migration
Verdict: Request Changes (submitted as comment -- cannot request-changes on own PR)
Verified
-
Migration SQL matches wiki Schema.md: The migration file
server/src/db/migrations/0010_household_items.sqlis identical to the SQL documented in the wiki Schema.md (lines 1640-1752). All 6 tables, columns, constraints, defaults, indexes, and FK cascade behaviors match exactly. -
Drizzle schema matches migration:
server/src/db/schema.tscorrectly represents all 6 tables with proper column types (text,integer,real), CHECK-constrained enums, FK references with correctonDeletebehaviors (CASCADE for junction tables, SET NULL for optional references), composite primary keys, and all required indexes. -
6 tables correctly defined:
household_items,household_item_tags,household_item_notes,household_item_budgets,household_item_work_items,household_item_subsidies-- all present and correctly structured. -
Foreign key relationships and cascades: All correct. Junction tables use ON DELETE CASCADE. Optional references (
vendor_id,created_by,budget_category_id,budget_source_id) use ON DELETE SET NULL. Thehousehold_item_work_itemsjunction correctly cascades from both sides. -
Test coverage: Migration integration tests are comprehensive -- covering table creation, all CHECK constraints (category, status, quantity, planned_amount), composite PK enforcement, all 5 CASCADE delete paths, SET NULL on vendor/user delete, and index verification. Shared type tests verify all enum values, interface shapes, and nullability.
-
Code pattern consistency: Follows existing work item and budget table patterns exactly, including naming conventions, Drizzle schema structure, and test organization.
Issue Found (Blocking)
Missing tagIds in request types
File: shared/src/types/householdItem.ts
The API Contract (wiki API-Contract.md, lines 4942 and 5065) specifies that both POST /api/household-items and PATCH /api/household-items/:id accept an optional tagIds field in the request body:
| tagIds | string[] | No | Array of valid tag UUIDs. Default: [] |
| tagIds | string[] | No | Array of valid tag UUIDs (replaces entire tag set) |
However, neither CreateHouseholdItemRequest nor UpdateHouseholdItemRequest in the shared types include a tagIds field. The existing work item types (shared/src/types/workItem.ts, lines 128 and 149) both include tagIds?: string[] in their request interfaces.
Fix: Add tagIds?: string[] to both CreateHouseholdItemRequest and UpdateHouseholdItemRequest in shared/src/types/householdItem.ts. Update the corresponding type test to verify tagIds is accepted in both request shapes.
// In CreateHouseholdItemRequest, add:
tagIds?: string[];
// In UpdateHouseholdItemRequest, add:
tagIds?: string[];This is required for backend and frontend agents to correctly implement the household item CRUD endpoints against the shared type contract.
steilerDev
left a comment
There was a problem hiding this comment.
[product-owner] Review of Story 4.1: Household Items Schema & Migration (#387). Verdict: APPROVE (submitted as comment due to self-authored PR limitation).
Acceptance Criteria Verification
AC #1: household_items table — PASS (with architectural deviations noted below)
The table is created with all core columns. The following deviations from the original AC text are noted but accepted as valid architectural refinements:
| AC Specification | Implementation | Rationale |
|---|---|---|
planned_cost (REAL) column |
Replaced by household_item_budgets table |
Normalized design supports multiple budget lines per item (mirrors work_item_budgets pattern from EPIC-05). This is architecturally superior to flat cost columns. |
actual_cost (REAL) column |
Replaced by household_item_budgets table |
Same as above — actual costs tracked via budget lines with confidence levels. |
notes (TEXT) column |
Replaced by household_item_notes table |
Supports multiple timestamped notes per item (mirrors work_item_notes pattern). |
purchase_status column name |
Implemented as status |
Simpler column name; functionally identical enum values and CHECK constraint. |
| Category enum: 5 values | 8 values (added electronics, outdoor, storage) | Expanded coverage is non-breaking; original 5 values all present. |
| — | Added url (TEXT) column |
Product URL for purchase links. Useful addition. |
| — | Added quantity (INTEGER, default 1) column |
Supports purchasing multiple units. Useful addition. |
Verdict: The normalized approach (separate budget lines and notes tables) is a well-justified architectural decision that aligns with established patterns. The additional tables (household_item_budgets, household_item_notes, household_item_subsidies) anticipate Stories 4.3-4.8 and reduce future migration churn. Accepted as refinement, not scope creep.
AC #2: household_item_tags junction table — PASS
Composite PK (household_item_id, tag_id), both FKs with ON DELETE CASCADE, reuses existing tags table. Index on tag_id for reverse lookups.
AC #3: household_item_work_items junction table — PASS
Composite PK (household_item_id, work_item_id), both FKs with ON DELETE CASCADE. Index on work_item_id.
AC #4: Drizzle ORM schema matches migration SQL — PASS
All 6 tables have Drizzle schema definitions in server/src/db/schema.ts that match their migration SQL counterparts (column types, FK constraints, indexes, defaults, CHECK constraints).
AC #5: Shared TypeScript types — PASS
shared/src/types/householdItem.ts contains:
HouseholdItemCategory(8-value union type)HouseholdItemStatus(4-value union type)HouseholdItem(base entity),HouseholdItemSummary(list),HouseholdItemDetail(single)CreateHouseholdItemRequest,UpdateHouseholdItemRequestHouseholdItemListQuery,HouseholdItemListResponse,HouseholdItemResponse- Vendor, work item, and subsidy summary interfaces
All types exported from shared/src/index.ts.
AC #6: Migration naming convention — PASS
File is server/src/db/migrations/0010_household_items.sql.
AC #7: Indexes — PASS
Indexes created on: status (mapped from AC's purchase_status), category, vendor_id, room, created_at. Additional indexes on all junction table foreign keys.
AC #8: Cascade deletes — CONDITIONAL PASS
- Tag associations: CASCADE via FK — verified in migration and tests.
- Work item links: CASCADE via FK — verified in migration and tests.
- Document links: NOT handled at schema level. The
document_linkstable uses a polymorphic pattern (no FK tohousehold_items), so cascade must be application-layer logic in the service (Story 4.2 CRUD). The AC's own notes state: "document_links uses entity_type='household_item'" — confirming this is by design. Full cascade for document links should be verified during Story 4.2 review.
Additional Observations (Non-Blocking)
-
Extra tables beyond AC scope:
household_item_notes,household_item_budgets, andhousehold_item_subsidieswere not specified in Story 4.1 ACs but are valuable forward-looking additions that reduce migration fragmentation. These tables support Stories 4.3-4.8 and mirror the established EPIC-05 patterns. -
Test authorship: Test commit includes
Co-Authored-By: Claude qa-integration-tester (Haiku)— correct attribution per CLAUDE.md. 43 migration integration tests + 30 shared type tests. -
CI status: Quality Gates (SUCCESS), Docker (SUCCESS), E2E Smoke (SUCCESS). Full E2E suite correctly skipped (no E2E-relevant changes in schema-only PR).
-
AC update recommendation: The story's ACs should be updated to reflect the architect's refined schema design (budget lines table, notes table, expanded enums, additional columns). This ensures future reference accuracy. The current ACs were written pre-architecture.
Summary
All 8 acceptance criteria are met (with architectural refinements that improve upon the original specification). The implementation follows established codebase patterns, test coverage is present and authored by the correct agent, and CI is green. Ready to merge.
|
🎉 This PR is included in version 1.12.0-beta.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.12.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
0010_household_items.sqlwith 6 tables:household_items,household_item_tags,household_item_tag_assignments,household_item_work_items,household_item_subsidies, andhousehold_item_budget_linesserver/src/db/schema.tsshared/src/types/householdItem.tswith full TypeScript interface coverage for all entity shapes, request/response types, and query parametersFixes #387
Test plan
Co-Authored-By: Claude dev-team-lead (Sonnet) noreply@anthropic.com
Co-Authored-By: Claude backend-developer (Haiku) noreply@anthropic.com
Co-Authored-By: Claude qa-integration-tester (Haiku) noreply@anthropic.com