Browse documentation

🧭 Start Here

Blackcap Overview ✨ Blackcap Feature Catalog Installation First Run

🚀 Deploy Blackcap

Platform Stacks and Raspberry Pi Hardware Raspberry Pi Deployment Raspberry Pi Client Services GCP Deployment Application Updates Environment Variables and Secrets Reverse Proxy and TLS Background Jobs and Schedules

🛠️ Administer Blackcap

Organizations Users, Permissions, and Authentication Configuration Workspace Backups and Restore Database Administration Regression Testing Performance and Job Status Audit, Access Activity, and Logging GeoIP and Access Location Data Retention and Purge Support Requests API Tester and Postman Instance Reporting

🍽️ Use Recipes

Recipes and the Recipe Library Recipe Import and Discovery Recipe Editing and Cache Artifacts Recipe Sharing Social Recipe Import AI Recipe Image Generation

📅 Plan Meals

Meal Planner

🛒 Use Shopping Lists

Shopping Lists and Shop a List External and Household Shopping 🧩 Chrome Extension Shop With

🧺 Manage Kitchen Inventory

🧺 Kitchen Inventory

🖥️ Use Displays

Displays and Connections Assigning and Scheduling Display Content Remote Pi Client E-Ink Rendering Menu Refresh and Rendering Noun Project Footer Images

🧑‍🍳 Cook with Let’s Cook

🧑‍🍳 Let’s Cook 🧑‍🍳 Let’s Cook Controls and Timers

🤖 Use and Administer AI

🤖 AI in Blackcap 🤖 AI Providers and Connections 🤖 AI Seeds and Usage

🧩 Use the Chrome Extension

🧩 Blackcap Chrome Extension 🧩 Chrome Extension Recipe Capture 🧩 Chrome Extension Shop With 🧩 Chrome Extension Release and Privacy

🎮 Play Games

🎮 Games and Trivia

🔌 Integrations

Email Integration Cloud Storage Integrations Voice Assistants Shop With Integrations Authentication Providers

⚙️ Develop Blackcap

Application Architecture Database Service and Data Access SQLite and PostgreSQL Database Migrations Background Job Architecture Testing API Architecture Security and Organization Scoping UI, Icons, and Documentation Assets Blackcap-Safe Emoji Documentation Standards Terminology

⚠️ Troubleshoot Blackcap

⚠️ Troubleshooting Deployment Troubleshooting Display Troubleshooting Recipe Import Troubleshooting 🤖 AI Troubleshooting Backup Troubleshooting Database Troubleshooting Diagnostic Organization Clones Support Requests

Organizations

Audience: System Admin, Org Admin, Support, Developer Related: Users Permissions And Authentication · Configuration · Security And Scoping

Organizations are the primary tenant boundary in Blackcap. Users operate in an Active Organization; most recipes, meal plans, shopping lists, inventory, displays, settings, connections, jobs, and audit activity are organization-scoped. The Default Organization has additional bootstrap and local-display responsibilities.

System Admins can create, archive, restore, soft-delete, inspect, support, and switch context into organizations. Org Admins manage their own organization within the permission model. Organization names are friendly labels and are not required to be unique; public organization IDs remain the stable identifier.

Organization administration

Blackcap Pi now has a modular organization-management foundation intended to work for both self-hosted Raspberry Pi deployments and future hosted/SaaS deployments.

Key Design Decisions

  • Organization display names are not unique. Multiple organizations can be named Smith Family.
  • The organization primary key is the unique support identifier and is a prefixed text ID such as org_....
  • Normal one-organization users see the friendly organization name only.
  • System admins and multi-organization users see organization name plus org ID anywhere ambiguity matters.
  • The default organization remains a real first-class organization and is protected from archive/delete.

Core IDs

Core records use prefixed text primary keys directly:

organizations.id       = org_...
devices.id             = dvc_...
displays.id            = disp_...
display_clients.id     = dcli_...
connections.id         = conn_...
provider_configs.id    = pcfg_...
users.id               = usr_...

Runtime code should use these IDs directly. Legacy numeric ID alias tables and redundant public_id columns were removed by migrations 086 and 087.

Admin Header Context

The Admin UI header shows the current organization to the left of the user dropdown.

  • Single-org users see a read-only organization label.
  • System admins can switch organization context from the header.
  • Users with multiple memberships can switch organization context from the header.
  • Archived/disabled/deleted context displays a visible status badge.

Organization Lifecycle

Create

Creating an organization:

  1. Creates the organization record.
  2. Copies organization-scoped default settings from the default org.
  3. Creates a default display record.
  4. Creates an initial org_admin invite.
  5. Sends the invite email through existing email infrastructure.

It does not copy:

  • recipes
  • recipe ingredients
  • shopping lists
  • shopping list items
  • recipe cache assets
  • user API tokens
  • provider tokens/secrets

Archive

Archiving is reversible. It disables normal organization activity while preserving data, audit/lifecycle events, settings, connections metadata, and backups.

Disable and Delete

Disable suspends normal organization access, preserves data, and schedules purge eligibility 90 days out. It covers both temporary administrative suspension and planned closeout scenarios where the organization should remain recoverable/exportable during the retention window.

Delete marks the organization deleted, blocks normal access, and sets purge_eligible_at 24 hours out. The scheduled purge worker can then permanently purge the organization on the next run after it becomes eligible, subject to the normal safety guards.

Both Disable and Delete can optionally create an organization export and email it to the current primary admin/contact before the lifecycle action is completed.

System Admin navigation

System Admin users see both organization navigation entries:

  • Organizations opens the platform-wide organization list.
  • Organization opens the current organization context details page.

When the System Admin context is the Default organization, Organization is the quick path to Default organization details. If the System Admin context is switched to another organization, Organization follows that active context and uses the same scoped organization page as Org Admin users.

Organization Context Switching

System admins and multi-org users can switch context intentionally. The app treats the selected organization as the active organization context rather than granting broad cross-org bypass. This keeps troubleshooting behavior close to real org-user behavior.

Regional preferences

Organizations store regional display preferences on the organization record. The Org Admin Organization page and System Admin organization pages expose preferred time zone, preferred time format, and locale together.

preferred_time_format supports 12h and 24h, defaults to 12h, and is organization-scoped. User-facing organization screens and org-scoped emails should use the shared date/time helpers so both preferred time zone and preferred time format are applied. Examples are 7:05 am / 3:30 pm for 12-hour organizations and 07:05 / 15:30 for 24-hour organizations. Machine-readable API timestamps remain unchanged.

Backup status/history timestamps and Meal Planner slot labels use the organization time format. Native browser input type="time" controls still save canonical HH:mm values, but their picker UI may follow the user's device/browser clock preference. A forced 24-hour picker would require a custom selector rather than the native control.

Backup / Restore

Blackcap Pi supports both platform/default backups and organization-scoped backups.

  • The default organization keeps the historic backup layout and can use the root cloud_backup/local/ folder.
  • Non-default organizations use organization-specific backup folders such as cloud_backup/local/org_<organization_id>/.
  • Organization backups are ZIP packages that include organization-owned SQLite data, recipe cache files, Noun Project icon cache assets, shopping-list support files, and organization-scoped configuration. Noun Project rules live in SQLite and are exported with the org data.
  • Organization backups exclude other organizations, platform-only configuration, and provider credentials that do not belong to that organization.
  • Cross-environment organization restores remove attached/remote display-client credentials and transient delivery jobs. A Client display returns to Waiting for Link. For a restore back onto the same installation, the restore form can explicitly preserve that installation’s current live display-client links for matching records; stale link credentials embedded in the backup are not activated directly.
  • Diagnostic clones and configuration copies strip display-client token/credential fields and never copy a live client relationship.
  • Org admins can run and restore backups for their own organization only.
  • Cloud backup provider connections, encrypted provider tokens, backup status/history, and backup run records are scoped to the active organization context and stored in SQLite.
  • Child-organization local backup retention is capped at three backups to protect Raspberry Pi storage. Dropbox and Google Drive retention remain controlled by each organization's backup settings.

Manual and automatic backup status/progress is scoped by organization and user so a default-org admin view does not show a child organization's completed backup progress. Full/platform backups also appear in the Default organization's backup-run history with backup_scope='platform'.

Organization Login Providers and Internal MFA

Authentication provider configuration is split between platform and organization scopes:

  • System admins configure OAuth/OIDC application credentials under Configuration → Platform → Authentication Providers.
  • Org admins enable/disable which configured providers their organization allows under Configuration → Organization → Login Providers.
  • Users can link/unlink their own provider identities from My Account when org policy allows it.

The login page can carry explicit organization context with ?org=org_..., with ?organization=... accepted as a compatibility alias. The org id is intentionally not shown on the login page. Invite links derive org context from the invite token.

Organization-level MFA policy is also managed from Configuration → Organization → Login Providers. The current policy applies only to internal/password login; external-provider logins are not challenged again by Blackcap. If the org requires internal-login MFA, non-enrolled users are forced through TOTP setup after a successful password check and before a full app session is created. If an individual user has MFA enabled, future internal/password logins for that user require MFA even if the org-wide requirement is off.

Org login-provider policy controls are intentionally narrow:

  • Auto-link allows verified/trusted provider emails to link to existing active users in the organization.
  • Invite-only represents the invite/existing-user-first model. In the current implementation, disabling it does not enable open self-registration by itself.
  • Self-link allows already-authenticated users to connect that provider from My Account when allowed.

Org admins can see whether users in their organization have MFA enabled, can clear a user's trusted browsers to force the next MFA challenge, and can reset a user's MFA enrollment when recovery is needed. They cannot see TOTP secrets, recovery codes, or trusted-browser tokens.

Kiosk receiver URLs, hosted display receiver URLs with valid tokens, and remote display-client APIs remain token-authorized device endpoints. They intentionally bypass user-login MFA because they are unattended device integrations, not user browser sessions.

Important Security Notes

  • Org admins do not receive system-wide organization management permissions.
  • Organization-scoped users are filtered to their active/current organization context.
  • Archived, disabled, or deleted organizations reject normal app/API activity.
  • The default/system organization is protected from archive/delete.
  • Database Admin organization-scoped resources, including Settings, Setting Audit Log, Shopping List Items, and App State, use the active organization context for counts, lists, details, edits, exports, and direct route calls.
  • System admins do not automatically see all organization business data; they must switch context intentionally before viewing or affecting another organization.

See ../development/security-and-scoping.md for the current resource-by-resource scoping model.

Context-switch enforcement update

When a system_admin switches from the default/system organization into a non-default organization, Blackcap Pi now treats that view as an organization-admin context for normal application pages. This is intentional: troubleshooting an organization should show the same recipes, shopping lists, settings, and permissions an organization admin would see, instead of silently bypassing organization boundaries.

The header switcher remains available so a system admin can return to the default/system context. Organization names are still allowed to repeat; admin-facing selectors continue to display both the friendly name and unique org id.

Legacy records that do not yet have an organization_id are treated as default-organization records only. This keeps newly-created organizations clean and prevents default recipes or shopping lists from appearing after switching context.

Organization-Scoped Stored Files

Generated runtime files should follow the same organization boundary as SQLite data. The default/system organization keeps the historic root folders for backward compatibility:

recipe_cache/<recipe_id>.pdf
recipe_cache/<recipe_id>.png
recipe_cache/thumbnails/<recipe_id>.jpg

Non-default organizations use an organization-specific subfolder:

recipe_cache/org_<organization_id>/<recipe_id>.pdf
recipe_cache/org_<organization_id>/<recipe_id>.png
recipe_cache/org_<organization_id>/thumbnails/<recipe_id>.jpg
recipe_cache/org_<organization_id>/capture_uploads/<recipe_id>/...
noun_cache/org_<organization_id>/...
display_previews/org_<organization_id>/...
cloud_backup/local/org_<organization_id>/...
runtime/assisted_browser/org_<organization_id>/...

Shopping lists, shopping-list items, and recipe-cart state are SQLite-only; new runtime code should not create shopping_lists/ or shopping_lists/org_*. Organization lifecycle purge still removes any legacy/accidental shopping_lists/org_* folder it finds, but those folders are treated as cleanup debris, not expected storage.

Organization lifecycle purge removes the non-default organization's scoped storage folders above after the same safety guards that protect the database rows have passed. Dry-run/preview counts these file artifacts without deleting them.

Regression test run history is intentionally not organization-owned data for the generated test organization. If a purged organization was created by the regression harness, purge marks its regression_test_organizations marker as purged so it disappears from the live Test Organizations panel, but it leaves the associated run and step history available under the regression history retention policy.

The recipe cache worker must carry organization context into background threads and subprocess renderers using organization_context() and BLACKCAP_ORG_CONTEXT_ID. Without that context, a non-default organization cache refresh can accidentally fall back to the default organization and report that a recipe is missing or appear to do nothing.

Noun Project Provider and Organization Rules

Noun Project API credentials are platform/default-provider configuration and are visible/editable only to system admins. Org admins and lower roles can manage their own organization's Noun Project keyword rules but cannot view, edit, or test the shared Noun Project API key or secret.

Noun Project cached assets are organization-aware:

  • The default organization uses noun_cache/ directly.
  • Non-default organizations use folders such as noun_cache/org_<organization_id>/.
  • When an org needs an icon, Blackcap Pi checks the default cache first and copies an existing icon into the org cache when available.
  • If an icon must be downloaded, it is written to the default cache and copied into the requesting organization's cache.

This keeps noun_cache/ as the complete shared icon repository while each child organization only stores the icons it has actually used. Keyword/rule configuration is stored in the organization-scoped noun_project_rules table; noun_cache/NounProjectWords.csv is no longer active app configuration.

Background jobs and queue visibility

Background jobs are scoped by active organization. Manual jobs such as backup, restore, recipe cache refresh, recipe render, and shopping-list generation also carry the requesting user id so progress banners and completion details do not leak between users. Scheduled/org-wide jobs are marked as system jobs and are visible only inside their organization.

Global hardware state remains intentionally global because there is only one physical display and one shared display lock. The UI should show hardware busy state without exposing unrelated organization/user job details.

Scoped status endpoints are available for page-specific polling: /status/display, /status/backup, /status/recipe-cache, /status/render, and /status/jobs.

Cooking workflow preferences

The current Organization page and System Admin Organization Detail page expose cooking preferences next to Preferred Recipe Units because they describe how the household/organization cooks rather than how a specific display renders.

  • Preferred Recipe Units controls whether recipe rendering/conversion prefers as-written, US, or metric units.
  • Show full recipe before step-by-step cooking is stored as lets_cook.show_full_recipe_review_before_current_step and defaults to true. When a target display is configured for current-step Let’s Cook, Blackcap first shows a full-recipe review phase. Saying or pressing next starts step-by-step cooking, showing generated Setup/Step 0 first when setup items exist.

This preference is org-scoped so it applies consistently to Alexa, kiosk, timed Meal Planner, Admin/Mobile-started sessions, and token-only receiver contexts where no normal logged-in user preference may be available.

Diagnostic Clone

System Admins can start a Diagnostic Clone from a normal organization row or from the organization details page. The action creates an isolated regression/test organization for troubleshooting the selected source organization without modifying the source.

Diagnostic Clone is managed from the Regression Tests page after it is created. The diagnostic organization is always left open, hidden from the normal Organizations list, and available through the regression Test Organizations section for View Organization and cleanup actions. A source organization can have only one active diagnostic clone at a time; if one already exists, the Organizations list/details buttons open the existing clone or its run detail instead of starting another clone.

The configuration-focused clone copies safe org-scoped settings, Noun Project footer/rule configuration, grocery departments/category mappings, kitchen inventory storage locations, organization AI Seed entitlement/billing configuration, and display records without copying display tokens, clients, provider credentials, or physical write targets. A finite clone starts with its full current monthly organization AI Seed allotment. It does not copy users, invites, API tokens, external sessions, backups, audit logs, unrelated job history, older AI usage periods, or performance history. On the creation form, all optional data selections—including the current billing-period AI usage and organization balance snapshot—are grouped under Current operational data. The configuration-only clone does not copy operational artifacts; selected recipe copy and current operational-data sync copy the needed recipe/display artifacts into the diagnostic organization storage so the diagnostic clone reflects the source current state without pointing back to source files. Recipe ingredient copy normalizes legacy nullable notes fields so recipes created under older schemas can be reproduced safely. When the diagnostic regression option is turned off, clone creation stops after the diagnostic org/admin setup and configuration/display copy.

When viewing the diagnostic clone organization, the top regression-context banner shows the source organization as a pill and shows an Operational Data Copied - N pill after current operational data has been copied. A collapsed Current Operational Data panel appears directly under that banner on every admin page. It can sync current display assignments/content settings, selected/displayed recipes, current display preview assets, the source organization's current planner week and associated recipes, active Let’s Cook sessions, the single current active shopping list with related recipes, the household list, the current recipe cart, kitchen inventory items including both on-hand and not-on-hand records, and—when explicitly selected—the source organization's current AI billing-period usage and organization balance. The AI option copies no older periods and can be used later even when it was not selected during clone creation. Copied data keeps the same source-facing names/titles wherever practical, such as shopping list names, recipe titles, display names, meal-plan labels, Let’s Cook labels, and inventory item names; only internal IDs/UUIDs are regenerated for diagnostic isolation. Page-specific copy panels are available on Recipe Library, Shopping Lists, and Meal Planner. The Recipe Library panel supports searching source recipes by name or recipe ID. The Shopping Lists panel copies selected source shopping lists, the current recipe cart, and any related recipes. The Meal Planner panel shows populated source weeks within 8 weeks before/after the current planner week and copies selected weeks plus referenced recipes. Already-copied source records show a green check instead of a selectable checkbox. Recipe cache artifacts, thumbnails/images, editable/converted content paths, related artifact metadata, and recipe-scoped AI image status are copied into the diagnostic organization when they exist in the source. Linked latest/success/failure AI jobs are remapped to the generated diagnostic Org Admin, and candidate image files are copied when available, so a copied failed AI image remains visible and retryable under the normal policy even when AI Usage was not copied. Cache profile metadata is remapped to the diagnostic clone's regenerated display IDs. This lets copied recipes show the same practical Recipe Cache Status and AI image diagnostic state as the source organization without a user-facing rebuild option.

The View Clone Run button returns the System Admin to the Default/platform context and opens the owning Regression Tests run detail. That run detail includes a Run Diagnostic Tests action for active diagnostic clones. It can be used after clone creation, including clones originally created with regression tests disabled. New diagnostic test steps append to the same run details and continue the existing step numbering; diagnostic clone total time is calculated from active step duration so idle time between later test passes is excluded. The diagnostic profile includes deterministic mocked AI recipe-image generation/save, Org Admin AI Usage privacy/scope, the dedicated AI Usage page, and the read-only organization and personal AI Seed endpoints. The image test spends from the generated user's personal balance so it does not consume or alter the organization snapshot. The action is hidden after the diagnostic clone has been cleaned up.

Organization page load performance

The System Admin Organizations page now renders the organization list before loading the secondary Organization Configuration Sync dashboard. The sync dashboard is fetched as an HTML fragment after the page is visible, which avoids delaying common organization search, filtering, and detail-review workflows.

The scoped Organization page similarly renders the main profile, cache status, and administration panels first, then lazy-loads the Config Pull panel. The Config Pull route still enforces the same organization-profile permission and active organization context.

Lifecycle model

This document captures the design direction for System Admin organization management, organization-scoped user lifecycle, billing/subscription state, and safe closeout/purge behavior.

Model

Blackcap should continue moving toward a global user/account plus organization memberships model:

  • users is the global login/account identity.
  • organization_memberships controls role, status, last login, and purge eligibility inside one organization.
  • External authentication identities link to the global user account.
  • Organization policy decides whether a linked provider is allowed for that organization.
  • Disabling a membership never disables the global account or other organization memberships.

This keeps multi-organization users safe and avoids duplicate login identities for the same person.

Organization fields

Organizations now carry operational metadata for:

  • preferred time zone
  • locale/language placeholder
  • original invited admin email/name/user id
  • disabled/re-enabled/closed timestamps and actors
  • purge eligibility/status/retention days
  • last actual login metadata
  • subscription type/status
  • Stripe customer/subscription ids
  • support and billing notes

Timestamps should be stored in UTC and displayed through centralized helpers using either the organization preferred time zone or the platform time zone for platform-level views.

Status behavior

Organization status is intentionally separate from subscription status.

  • active: normal org access.
  • disabled: access is blocked, data is preserved, active memberships become org_disabled.
  • closed: graceful closeout state; access is blocked and purge eligibility is scheduled.
  • archived / deleted: legacy safe states that preserve data.

Re-enabling an organization restores memberships that were suspended by org-level disable (org_disabled) but does not re-enable memberships that were individually disabled.

The default/system organization is protected from disable, close, archive, delete, and future purge.

Membership lifecycle

A membership can be disabled for one organization without affecting the user's global account or other memberships.

  • active: membership can access org.
  • disabled: individually disabled membership; not restored by organization re-enable.
  • org_disabled: temporarily suspended due to organization-level disable/close/archive/delete; restored when the organization is re-enabled.

Disabled memberships receive purge_eligible_at = disabled_at + 90 days by default. Destructive membership purge should be implemented only through a future dry-run and confirmation workflow.

Actual login tracking

Actual user login tracking is distinct from System Admin support context switching.

On successful actual login, Blackcap records:

  • organization_user_login_events
  • organization_memberships.last_login_at
  • organization_memberships.last_login_method
  • organizations.last_actual_login_at
  • organizations.last_actual_login_user_id
  • organizations.last_actual_login_method

System Admin context switching is audited separately and must not update actual login fields.

Subscription and Stripe behavior

subscription_type captures how the organization is billed or operated. Suggested values include:

  • self_hosted
  • stripe
  • manual_paid
  • trial
  • free
  • comped

subscription_status stores the provider/manual state. Stripe updates should be applied through a service/controller boundary so hosted deployments can wire it to a verified webhook while local Raspberry Pi installs can ignore Stripe entirely.

Stripe status mapping:

  • active, trialing -> organization status active
  • past_due, unpaid, canceled, cancelled, incomplete_expired -> organization status disabled

Manual organization disable/re-enable should not overwrite the precise Stripe status.

Closeout and purge

Destructive purge is not part of the first implementation pass. The safe foundation is:

  1. Disable or close the organization.
  2. Set purge_eligible_at based on retention, default 90 days.
  3. Show purge status in System Admin views.
  4. Keep backups and audit history preserved.
  5. Future purge screen performs dry-run first.
  6. Future destructive purge requires explicit System Admin confirmation.

Before purge, Blackcap must verify:

  • organization is not default/system
  • organization is disabled/closed/deleted/archived, not active
  • retention period has passed
  • a recent backup/export exists or System Admin explicitly confirms no backup
  • dry-run summary is reviewed
  • action is audited

Future purge can remove organization-scoped records such as recipes, recipe content, shopping lists, meal plans, displays, connections, app state, settings, memberships, and scoped token material. Audit logs should be preserved or anonymized according to the eventual compliance policy.

  • Restyle Organizations list.
  • Add create modal.
  • Add server-side search/filter/pagination.
  • Clean up detail page.
  • Add common organization date/time labels.

Phase 2: Organization time zone and locale placeholder

  • Add org preferred time zone and locale fields.
  • Use centralized UTC -> org/platform display formatting.
  • Avoid scattered template formatting.

Phase 3: Context switching, actual login, membership status

  • Redirect System Admin switch to org Home.
  • Do not count context switches as actual logins.
  • Track actual login per org/user.
  • Add normal multi-org switcher with names only.
  • Add organization-scoped user disable/re-enable.

Phase 4: Organization actions and subscription status

  • Add single/bulk MFA required toggles.
  • Add disable/re-enable org.
  • Add subscription type/status fields.
  • Add Stripe status application service hook.

Phase 5: Closeout and purge foundation

  • Add closed/disabled retention fields.
  • Add purge eligibility and purge setting defaults.
  • Add backup/export action.
  • Design purge screen/dry-run before destructive purge.

Phase 6: Hosted SaaS refinement

  • Add verified Stripe webhook endpoint for hosted deployments.
  • Add billing/admin contact.
  • Add storage/quota placeholders.
  • Add feature flags and support notes.
  • Add force logout and token rotation actions.

Organization data isolation

Blackcap Pi treats operational/business content as organization-owned. This document summarizes the current scoping rules for Database Admin and the major data domains.


🔐 Core rule

Organization-scoped resources are visible and editable only in the active organization context.

  • Org admins see permitted resources for their own organization.
  • Lower roles see only the resources/actions allowed by their permissions.
  • System admins in the Default organization context see Default organization-scoped business data only.
  • A system admin must intentionally switch organization context to inspect or affect another organization.
  • Direct URLs, form values, and API/POST calls must not override the active context.

Platform/global resources remain system-admin-only where appropriate.


🗄️ Database Admin scope behavior

The Database Admin resource registry defines each resource's scope. Organization filtering is enforced by the provider/service layer for:

  • resource counts
  • resource lists
  • filter dropdowns
  • search results
  • detail drawer reads
  • safe edits
  • exports/direct route calls that use resource pages

Resources can be scoped by:

  • a direct organization_id-style column
  • a parent ownership join, such as shopping-list items through shopping lists
  • scoped settings columns, such as scope_type='organization' and scope_id=<active org>
  • organization key suffixes, such as recipe_cart:org:<organization_id> in app state

Organization-scoped provider queries fail closed if no active organization context is available.


✅ Resources explicitly treated as organization-scoped

The Database Admin registry treats these as organization-scoped when shown through Resource Explorer:

  • Recipes
  • Recipe Edit Content
  • Recipe Ingredients
  • Recipe Tags
  • Meal Planner Settings
  • Meal Slots
  • Meal Plan Entries
  • Meal Plan Shopping List Links
  • Shopping Lists
  • Shopping List Items
  • Shopping List Item Sources
  • Recipe Shares
  • Recipe Share Recipient Targets
  • Recipe Share Notifications
  • Recipe Share Consumptions
  • Recipe Share Audit
  • Background Jobs
  • Cache Status
  • Devices
  • Displays
  • Connections
  • Organization Memberships
  • Organization Backup Runs
  • Cloud Backup Provider Tokens
  • Cloud Backup State
  • Organization Noun Project Rules/cache resources
  • Settings where scope_type='organization'
  • Setting Audit Log rows where scope_type='organization'
  • App State rows whose keys belong to the active organization, such as recipe_cart:org:<organization_id>
  • Audit Log rows where organization_id is present

Platform/global resources include organizations, organization lifecycle events, setting definitions, connection types, provider configuration metadata, schema migrations, and platform-level emoji registry/cache data.


🍲 Recipes and recipe content

Recipes are scoped by organization_id + recipe_id. A recipe lookup by recipe_id alone is unsafe unless organization context is applied by the service.

Expected behavior:

  • same recipe_id can exist in multiple organizations
  • create/update/delete/cache-status lookup is organization-scoped
  • recipe image/PDF endpoints must resolve through the active organization or an authorized share token
  • recipe search and ingredient search are organization-scoped
  • recipe sharing/import writes new data into the recipient organization
  • recipe edit content and recipe ingredients are scoped directly or through recipe ownership

📅 Meal Planner

Meal Planner data is organization-owned. Settings, slots, entries, review/shopping-list generation, and move operations all resolve through the active organization context.

Expected behavior:

  • week start day and meal slot configuration belong to the active organization;
  • meal slots are not shared across organizations;
  • meal plan entries reference recipes using the current organization plus recipe id;
  • Recipe Discovery return context creates meal plan entries only in the active organization;
  • shopping lists generated from Meal Planner are created inside the active organization;
  • item/slot/day move endpoints cannot move records across organizations;
  • recipe archive/delete dependency checks consider only the current organization.

A system admin must switch organization context before viewing or managing another organization's meal plans. Forged organization_id, slot_id, entry_id, or recipe_id values should not override the active context.

🧂 Ingredients and canonical cleanup

Recipe ingredients and shopping-list items are organization-owned. Ingredient search/typeahead and canonical cleanup should stay inside the active organization.

Canonical maintenance now uses strict active-org filters. Missing organization context means no rows, not all rows.


🛒 Shopping lists and shopping-list items

Shopping lists are organization-owned by direct organization_id.

Shopping-list items are also organization-owned and are enforced in Database Admin with both:

  1. the item row's organization_id, and
  2. the parent shopping list's organization_id.

This defense-in-depth check prevents an orphaned or mismatched child row from leaking through Resource Explorer.


⚙️ Settings, Setting Audit Log, and App State

Settings and Setting Audit Log are not treated as platform-wide in Database Admin when they represent organization settings. Resource Explorer shows organization settings where:

scope_type = organization
scope_id = active organization id

System Admin users also see platform-scoped settings in the Settings resource so platform and active-organization settings can be reviewed together. That mixed view still does not include settings from other organizations unless the system admin intentionally switches organization context.

App State is organization-scoped when it stores operational state for an organization. The current recipe-cart app-state pattern uses keys like:

recipe_cart:org:<organization_id>

Platform/global app state should be represented separately and should stay system-admin-only. Platform settings are allowed in the System Admin Settings view, but only alongside the active organization's settings.


☁️ Backup and Noun Project data

Backup provider connections, encrypted provider token payloads, backup status/state, and backup run records are organization-scoped. The Default organization additionally shows full/platform backup runs in organization_backup_runs with backup_scope='platform'.

Noun Project API credentials are platform/provider configuration, but Noun Project keyword rules are organization-scoped rows in noun_project_rules. Downloaded icon files remain filesystem cache artifacts under noun_cache/ and org-specific subfolders.

👥 Users, security, and activity pages

The Admin Users, Security, and Activity pages use the same active-organization rule as Database Admin organization resources. A System Admin in the Default organization context sees Default users, tokens, invites, security activity, and audit events. To inspect another organization, the System Admin must intentionally switch organization context first. Org admins can view their organization's Security dashboard, including org user counts, Google-linked users, API-token status, and scoped security activity; platform security configuration links remain hidden from org admins.


🧪 Isolation test checklist

  • Org admin can see Settings, Setting Audit Log, Shopping List Items, and App State for their org.
  • Org admin cannot see another org's records through lists, details, search, filters, exports, or crafted URLs.
  • System admin in the Default organization context sees only Default organization-scoped business data, users, tokens, security activity, and audit events.
  • System admin sees platform settings together with active-org settings, without seeing settings from other organizations.
  • System admin switches context intentionally and then sees that selected org's business data and org-owned admin data.
  • Direct detail/edit calls with a forged organization_id do not override active context.
  • The same recipe_id can exist in two organizations and resolves only in active context.
  • Meal Planner settings, slots, entries, moves, review, and shopping-list generation stay in the active organization.
  • Recipe Discovery return context cannot create a meal plan entry in another organization.
  • Shopping-list items require both child and parent org ownership.
  • SQL Console is System Admin only. It allows read-only inspection of organization-scoped tables, but it does not automatically inject the active organization context; production queries should add explicit organization_id filters and exclude regression/diagnostic clone organizations when appropriate.
  • SQL Console can query tables that contain hidden/masked fields, but protected fields cannot be selected. Use explicit safe columns instead of SELECT * / alias.* for those tables.
  • Secret settings, token hashes/encrypted tokens, password hashes, provider config JSON, cloud-backup token payloads/state JSON, and token references are hidden or masked in Database Admin.
  • Recipe Library, recipe add/edit/delete, recipe sharing, shopping lists, completed shopping lists, cache queue, backup, mobile UI, and Chrome Extension still work.

🧹 Purge and organization isolation

Manual purge is scoped to the active organization context. A system admin in the default organization purges only default-organization records unless they intentionally switch organization context first. Org admins can purge eligible content in their own organization only.

Scheduled purge is controlled by platform-level configuration and iterates across all non-deleted organizations using the same retention rules. Purge ordering is domain-aware: shopping lists are evaluated before recipes so shopping-list references are cleaned before recipe parent records are removed.

On this page