SHOPPYCAT HOUSEHOLD SYSTEM // S04
Households & Synchronization
Household membership, invitations, live collections, activity, notifications, and server rules let several people coordinate without turning every user into an administrator.
Shared data activeSHOPPYCAT.APP ↗Collaboration has boundaries
A household has explicit members and roles. Invite codes expire, email invitations have a lifecycle, and membership changes update the person and household together. Shared lists, pantry items, recipes, and meal plans live beneath the household they belong to.
Firestore Security Rules enforce those relationships independently of the interface. A hidden button is not authorization. Reads and writes are checked against identity, household membership, role, expected document shape, and the fields an operation is allowed to change.
Reactive without duplicate truth
- Snapshot listeners expose personal and shared collections as flows
- Multiple shared-list queries merge through coherent state before emitting
- Document IDs remain canonical when models are decoded
- Transactions protect list edits against stale read-modify-write races
- Batch writes keep household creation and membership changes together
- Cloud Functions deliver invitations, activity, and release notifications
- Activity destinations route a notification back to the relevant place
Authorize the operation, not the screen
The same policy can be explained as readable code. A write is accepted only when the caller is a member and the proposed document still belongs to the same household. Administrative operations require the stronger role.
' Decide whether a household operation has enough authority.
FUNCTION CanChangeHousehold(actor, household, operation)
' Nobody receives household authority without membership.
IF NOT Array.Contains(household.MemberIds, actor.UserId) THEN
RETURN {"Allowed": FALSE, "Reason": "NOT_A_MEMBER"}
END IF
' Ordinary members can coordinate shared household content.
memberOperations = ["EDIT_LIST", "EDIT_PANTRY", "EDIT_RECIPE", "EDIT_MEAL_PLAN"]
IF Array.Contains(memberOperations, operation) THEN
RETURN {"Allowed": TRUE, "Reason": "MEMBER_OPERATION"}
END IF
' Membership and household lifecycle changes require an administrator.
IF actor.Role == "ADMIN" THEN
RETURN {"Allowed": TRUE, "Reason": "ADMIN_OPERATION"}
END IF
RETURN {"Allowed": FALSE, "Reason": "ADMIN_REQUIRED"}
END FUNCTIONSession continuity without hiding conflict
Live listeners make changes from another household member visible quickly. Transactions prevent a local edit from overwriting a newer document by accident, and optimistic interface state rolls back when persistence fails.
The current architecture is Firebase-first. An older self-hosted Node and SQLite service also exists as an experiment, but the active Android integration does not pretend there is one interchangeable REST backend.
Authorization lives at the boundary, not the button
The specific policy function here, membership first, then operation category, then role, is ShoppyCat's household model. A different system will have different roles and different operations. What matters is where the check lives and what it is actually checking.
This is the same discipline behind row-level security in a shared database, API gateway policies that do not trust a mobile app's claimed role, and any multi-tenant system where one tenant must never see another's data because a screen happened not to show it. Authorization checked only in the interface is not authorization; it is a suggestion.
- Firestore Security Rules enforce membership and role independently of the Android interface, so a hidden button is never the actual authorization boundary. Any client-server system should assume the client can be bypassed entirely and enforce every rule server-side regardless of what the interface shows.
- CanChangeHousehold checks membership first and returns NOT_A_MEMBER before it even considers what the operation is. Ordering matters in an authorization check: verify identity and scope before evaluating what the actor is trying to do, so a non-member never gets far enough to learn what operations exist.
- Ordinary members can edit shared content, but only an ADMIN role can change membership or household lifecycle. Separating can use the shared thing from can change who is allowed to use the shared thing is a distinction worth making explicit in any multi-user system, not folding both into one flag.
- Batch writes keep household creation and its initial membership record atomic, and transactions protect list edits against stale read-modify-write races. Anywhere two related writes must both succeed or both fail together, reach for the primitive that guarantees that, rather than hoping the two calls land in order.
Key terms, in plain language
You do not need a systems background to follow the work. These are the specialized terms used on this page.
- Cloud Firestore
- A document database that can synchronize data and notify connected clients when documents change. Security Rules independently decide which authenticated operations are allowed.
- Transaction
- A read-and-write operation that succeeds against one consistent version of the data or retries when another change happened first. It prevents a stale edit from silently replacing newer state.
- Role-based access
- Authorization based on both membership and assigned responsibility, such as household member or administrator. A role grants a limited set of operations rather than unrestricted access.
- Schema validation
- Checking that proposed data has the expected fields, types, ownership, and limits before accepting it. This protects shared collections from malformed or overpowered writes.