Skip to content

feat(household): archive / un-archive families (households)#959

Open
jee7s wants to merge 1 commit into
mainfrom
feat/household-archive
Open

feat(household): archive / un-archive families (households)#959
jee7s wants to merge 1 commit into
mainfrom
feat/household-archive

Conversation

@jee7s

@jee7s jee7s commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What & why

Adds soft-archive for households (UI "families") — a way to set a family
aside (moved away, aged out, duplicate) without deleting anything, so it drops
out of the board's day-to-day surfaces and its members can't start new activity,
while every historical row stays intact and one action fully restores it.

Design + decisions + the full query sweep:
checkin-app/docs/designs/HOUSEHOLD_ARCHIVE.md.

Archive is orthogonal to membership Deny — it does not block login; it hides
the family from board lists/crons and blocks new activity. The two can coexist.

Decisions (from the product interview)

  • Nullable Household.archivedAt DateTime? stamp (@sensitivity:internal).
    Non-null == archived. Additive/nullable → expand-only migration.
  • Who archived is in the AuditLog, not a second column (mirrors the existing
    household Deny / membership-archive audit rows).
  • Board / sysadmin only, surfaced in the membership-ops households view
    (rides the existing sanctioned households POST — no new route, no routeAuthDrift
    change).
  • Hidden from default lists / searches / pickers; the households view gets an
    "Include archived" toggle to find them for un-archive.
  • Members blocked from new activity — program enrollment POST, check-in/scan,
    and membership-application start — via one shared guard assertHouseholdActive
    (→ 409 "household is archived"), guarded at the service/route level, not per caller.
  • Crons & fan-outs skip archived via one shared ACTIVE_HOUSEHOLD_WHERE
    filter folded into the existing predicates: renewal + person-BG sweeps, the
    new-program email blast, the audit/compliance chase lists, and the nav badges.
  • History fully intact; un-archive restores everything. No purge.

The design doc §6 documents every household/person query that feeds a list, cron,
or fan-out as a touched / deliberately-not-touched table (e.g. the safety
emergency-contacts board is deliberately left showing archived households, since
archive doesn't force-checkout a member who may still be physically present).

Testing

  • New integration suite householdArchive.integration.test.ts (6 cases, all
    green against seeded Postgres): archive → stamped + audit row (+ idempotency) →
    hidden-from-list / shown-with-toggle → blocked enrollment (409)blocked
    membership start
    renewal-sweep skip (representative cron) → un-archive
    clears + audit + enrollment restored.
  • Full unit suite green (1676 tests), tsc --noEmit clean, eslint --max-warnings 0 clean. classifications.ts regenerated via prisma generate.
  • Updated two existing unit mocks (scanRoute, programSignupIntegration) to
    supply household.findUnique for the new guard.

🤖 Generated with Claude Code

Soft-archive for households (UI "families"): a nullable Household.archivedAt
stamp sets a family aside without deleting anything. Archived households are
hidden from default board lists/searches/pickers (the membership-ops households
view gets an "include archived" toggle), skipped by notification fan-outs and
crons (renewal + person-BG sweeps, new-program blast, audit/compliance chase
lists, nav badges), and their members are blocked from starting new activity —
program enrollment, check-in/scan, and membership application — through one
shared guard (assertHouseholdActive), returning a clear 4xx.

Board/sysadmin only, surfaced as a toggle in the households view alongside
grant/revoke/deny. Who/when is captured in the AuditLog (no extra column).
History is fully intact; un-archive clears the stamp and restores everything.

See checkin-app/docs/designs/HOUSEHOLD_ARCHIVE.md for the decisions and the
full touched/not-touched query sweep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jee7s jee7s added the post-first-release Deferred until after the first production release label Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

post-first-release Deferred until after the first production release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant