Documentation Index
Fetch the complete documentation index at: https://grantmaster.dev/llms.txt
Use this file to discover all available pages before exploring further.
Domain Model
| Status | Updated | Covered Directories |
|---|
| 🟢 Stable | 2026-03-19 | src/types, src/schemas, src/core/firestoreCollections.ts |
Overview
GrantMaster’s domain is centred on the Organization (tenant). Every entity except global taxonomies and platform configurations belongs to exactly one organization via the TenantScoped base interface. The domain is organised into eleven aggregate groups, each with its own Firestore collection(s), owning feature/shared contract modules, and Zod validation schemas in src/schemas/.
TenantScoped
└─ organizationId: string (required on every write)
└─ createdAt: Timestamp
└─ updatedAt: Timestamp
Aggregate Groups
- Identity
- Grant Lifecycle
- Project Engine
- Finance
- Time Tracking
- Compliance
- Relations / CRM
- Documents
- People & HR
- Platform & Extensions
- Partnerships & Credits
1. Identity
Governs who can access the platform, under which tenant, and with what credentials.
Firestore collections: organizations, employees, invitations, sessions, apiKeys, securityEvents
Naming gotcha: User documents are stored in the employees collection, not a users collection.
Organization
The root tenant. All multi-org relationships are handled at the platform layer — users belong to exactly one organization.
| Field | Type | Notes |
|---|
id | string | Firestore document ID |
name | string | Display name |
slug | string | URL-safe identifier |
subscriptionTier | SubscriptionTier | free | starter | growth | enterprise |
settings | OrganizationSettings | App preferences, locale, branding |
classification | TenantClassification | ngo | charity | social_enterprise | ... |
missionPillars | string[] | Tags used for mission-alignment scoring |
User
Stored in the employees Firestore collection.
| Field | Type | Notes |
|---|
id | string | Firebase Auth UID |
email | string | |
role | UserRole | admin | manager | member | auditor | viewer |
status | UserStatus | active | invited | suspended | deactivated |
department | string | |
jobTitle | string | |
mfaEnabled | boolean | |
referralCode | string | null | Set when user joins via referral |
Invitation
Pending email invitations. Expire after 7 days.
| Field | Type | Notes |
|---|
email | string | Invitee |
role | UserRole | Intended role |
token | string | Signed JWT, verified on accept |
expiresAt | Timestamp | |
status | 'pending' | 'accepted' | 'expired' | |
Session
Managed by SessionService. Concurrent session limits enforced per subscription tier.
| Field | Type | Notes |
|---|
userId | string | |
deviceInfo | DeviceInfo | Browser/OS fingerprint |
lastActiveAt | Timestamp | Used for idle timeout |
expiresAt | Timestamp | |
2. Grant Lifecycle
A grant moves through five entity types as it progresses from discovery to close-out. Each type has its own Firestore collection.
Firestore collections: grantOpportunities, grantPipeline, grantApplications, activeGrants, grantReports
GrantOpportunity
A potential funding source, either discovered via the GrantMaster discovery engine or entered manually.
| Field | Type | Notes |
|---|
title | string | |
grantor | string | Funder name |
fundingRange | { min: number, max: number } | |
deadline | Timestamp | |
eligibilityCriteria | string[] | |
source | 'platform' | 'manual' | |
matchScore | number | AI-computed relevance 0–100 |
GrantPipelineEntry
An opportunity being actively pursued. Stored in grantPipeline.
| Field | Type | Notes |
|---|
opportunityId | string | FK → grantOpportunities |
stage | PipelineStage | prospect | preparing | submitted | under_review | won | lost |
probability | number | 0–100 |
requestedAmount | number | |
assignedTo | string[] | User IDs |
notes | string | |
GrantApplication
Formal application document. Stored in grantApplications.
| Field | Type | Notes |
|---|
pipelineId | string | FK → grantPipeline |
submittedAt | Timestamp | |
narrative | string | AI-assisted application text |
attachments | string[] | Storage URLs |
status | 'draft' | 'submitted' | 'under_review' | 'awarded' | 'rejected' | |
ActiveGrant
A won grant. Tracks disbursements and reporting obligations.
| Field | Type | Notes |
|---|
applicationId | string | FK → grantApplications |
awardedAmount | number | |
currency | string | ISO 4217 |
disbursementSchedule | Disbursement[] | Tranche dates and amounts |
reportingRequirements | ReportingRequirement[] | Deadlines and report types |
projectIds | string[] | FK → projects — multiple allowed |
status | 'active' | 'completed' | 'suspended' | |
GrantReport
Funder-facing report generated at a reporting milestone.
| Field | Type | Notes |
|---|
grantId | string | FK → activeGrants |
period | { from: Timestamp, to: Timestamp } | |
content | string | Report body |
status | 'draft' | 'submitted' | 'approved' | |
submittedAt | Timestamp | null | |
3. Project Engine
Projects are the operational unit through which grant funds are spent and outcomes are tracked.
Firestore collections: projects, budgetItems, milestones, tasks, deadlines
Project
| Field | Type | Notes |
|---|
name | string | |
status | ProjectStatus | planning | active | on_hold | completed | archived |
budget | number | Total project budget |
currency | string | |
startDate | Timestamp | |
endDate | Timestamp | |
grantIds | string[] | FK → activeGrants |
phase | ProjectPhase | initiation | planning | execution | monitoring | closure |
missionPillars | string[] | Tags aligned to org mission |
teamMemberIds | string[] | FK → employees |
managerId | string | FK → employees |
sectorTags | string[] | |
templateId | string | null | If created from a template |
BudgetItem (BudgetLine)
Line items within a project budget. Stored in budgetItems.
| Field | Type | Notes |
|---|
projectId | string | FK → projects |
category | BudgetCategory | One of 6 standard categories (see Finance) |
description | string | |
allocatedAmount | number | |
spentAmount | number | Computed / denormalized |
grantId | string | null | Funding source |
Milestone
| Field | Type | Notes |
|---|
projectId | string | FK → projects |
title | string | |
dueDate | Timestamp | |
status | 'pending' | 'in_progress' | 'completed' | |
completionPercentage | number | |
Task
| Field | Type | Notes |
|---|
milestoneId | string | FK → milestones |
projectId | string | Denormalized for query efficiency |
title | string | |
assigneeId | string | FK → employees |
status | TaskStatus | todo | in_progress | blocked | done |
dueDate | Timestamp | |
priority | 'low' | 'medium' | 'high' | 'urgent' | |
Deadline
Project-level external deadlines (reporting, funder meetings).
| Field | Type | Notes |
|---|
projectId | string | FK → projects |
title | string | |
dueDate | Timestamp | |
type | 'reporting' | 'submission' | 'payment' | 'milestone' | |
completed | boolean | |
4. Finance
Tracks all financial flows: expenses, allocations, invoices, and receipts.
Firestore collections: expenses, invoices, receipts
Expense allocations are stored as a sub-array on the Expense document, not a separate collection.
Expense
| Field | Type | Notes |
|---|
amount | number | |
currency | string | |
category | ExpenseCategory | |
description | string | |
projectId | string | FK → projects |
grantId | string | null | FK → activeGrants |
submittedBy | string | FK → employees |
status | ExpenseStatus | draft | pending | approved | rejected | paid |
approvedBy | string | null | FK → employees |
allocations | ExpenseAllocation[] | Split across multiple grants |
receiptUrl | string | null | Firebase Storage URL |
xeroTransactionId | string | null | Set after Xero push |
ExpenseAllocation
Embedded in the Expense document. Allows a single expense to be split across multiple grants.
| Field | Type | Notes |
|---|
grantId | string | FK → activeGrants |
amount | number | Portion allocated |
percentage | number | 0–100, must sum to 100 across all allocations |
Invoice
| Field | Type | Notes |
|---|
projectId | string | FK → projects |
vendorName | string | |
amount | number | |
currency | string | |
dueDate | Timestamp | |
status | 'unpaid' | 'paid' | 'overdue' | |
attachmentUrl | string | |
BudgetCategory (enum)
Six standard categories used for both BudgetItem.category and Expense.category:
| Value | Description |
|---|
Personnel | Salaries, benefits, HR costs |
Operations | Office, supplies, utilities |
Travel | Transport, accommodation, per diems |
Equipment | Hardware, software licences |
Consultancy | External contractor fees |
Overhead | Indirect/admin cost allocation |
5. Time Tracking
Staff time is tracked via JournalEntry records, grouped into monthly submissions for manager approval.
Firestore collections: timesheets (JournalEntry), retrospectives (JournalSubmission)
Naming gotcha: The timesheets collection stores JournalEntry documents (not “timesheet” objects), and retrospectives stores JournalSubmission documents. These names reflect legacy collection naming; the UI calls them “journals.”
JournalEntry
Stored in the timesheets collection.
| Field | Type | Notes |
|---|
userId | string | FK → employees |
projectId | string | FK → projects |
date | string | ISO date YYYY-MM-DD |
hours | number | |
description | string | Activity description |
submissionId | string | null | FK → retrospectives — set when submitted |
grantId | string | null | Optional: time directly attributed to a grant |
JournalSubmission
Monthly summary submitted by a staff member for manager approval. Stored in the retrospectives collection.
| Field | Type | Notes |
|---|
userId | string | FK → employees — submitter |
month | string | YYYY-MM |
status | SubmissionStatus | draft | submitted | approved | rejected |
totalHours | number | Denormalized sum |
approvedBy | string | null | FK → employees |
approvedAt | Timestamp | null | |
rejectionReason | string | null | Set on rejection |
entryIds | string[] | FKs → timesheets |
6. Compliance
Rule-based compliance monitoring across the organization, with audit logging of every significant action.
Firestore collections: compliance, auditLogs
ComplianceRule
| Field | Type | Notes |
|---|
name | string | |
description | string | |
domain | ComplianceDomain | financial | operational | legal | ... |
checkType | 'threshold' | 'deadline' | 'document' | 'approval' | |
threshold | number | null | For numeric checks |
severity | 'low' | 'medium' | 'high' | 'critical' | |
enabled | boolean | |
ComplianceAlert
Generated when a rule’s check condition is met.
| Field | Type | Notes |
|---|
ruleId | string | FK → compliance rules |
entityType | string | The domain entity that triggered |
entityId | string | FK into that entity’s collection |
status | 'open' | 'acknowledged' | 'resolved' | |
assignedTo | string | null | FK → employees |
resolvedAt | Timestamp | null | |
AuditLog
Append-only log of every significant action. Written by shared/audit/auditLogDataAccess.
| Field | Type | Notes |
|---|
action | AuditAction | Enum of 80+ action constants |
userId | string | Actor |
resourceType | string | Entity type |
resourceId | string | Entity ID |
organizationId | string | |
metadata | Record<string, unknown> | Before/after diffs, extensionId |
ipAddress | string | null | |
timestamp | Timestamp | |
7. Relations / CRM
Tracks relationships with funders, donors, and external partners.
Firestore collections: foundations, contacts, grantorRelationships, reportingCalendar
Foundation
Shared library of grant-making organizations. Shared across the platform (not strictly tenant-scoped — orgs can reference the same Foundation).
| Field | Type | Notes |
|---|
name | string | |
website | string | |
type | 'foundation' | 'government' | 'corporate' | 'bilateral' | |
focusAreas | string[] | |
geographicFocus | string[] | |
totalGrantsValue | number | Aggregated across all orgs |
Individual stakeholder at a Foundation or external partner.
| Field | Type | Notes |
|---|
foundationId | string | null | FK → foundations |
firstName | string | |
lastName | string | |
email | string | |
role | string | Job title at their org |
segments | string[] | Segment IDs |
hubspotContactId | string | null | Set after HubSpot sync |
Interaction
Log entry for a meeting, call, or email with a contact.
| Field | Type | Notes |
|---|
contactId | string | FK → contacts |
type | 'meeting' | 'call' | 'email' | 'event' | |
date | Timestamp | |
summary | string | |
followUpDate | Timestamp | null | |
userId | string | FK → employees — who logged it |
Dynamic grouping of contacts by criteria. Evaluations run on a schedule via segmentService.
| Field | Type | Notes |
|---|
name | string | |
criteria | SegmentCriteria[] | Filter rules |
memberCount | number | Denormalized |
lastEvaluatedAt | Timestamp | |
GrantorRelationship
Per-org engagement tracking for a specific Foundation.
| Field | Type | Notes |
|---|
foundationId | string | FK → foundations |
engagementScore | number | 0–100, computed by calculateEngagementScore() |
engagementLevel | 'low' | 'medium' | 'high' | 'champion' | Derived from score |
notes | string | |
deadlines | GrantorDeadline[] | Embedded reporting/meeting deadlines |
8. Documents
Document storage with AI-powered search via a RAG (Retrieval-Augmented Generation) pipeline.
Firestore collections: documents, documentFolders
Document chunks for RAG are stored in a separate vector index, not directly in Firestore.
DocumentFolder
| Field | Type | Notes |
|---|
name | string | |
parentId | string | null | Self-reference for nesting |
path | string[] | Ancestor folder IDs (for breadcrumbs) |
Document
| Field | Type | Notes |
|---|
folderId | string | null | FK → documentFolders |
name | string | Display name |
storageUrl | string | Firebase Storage path |
mimeType | string | |
sizeBytes | number | |
uploadedBy | string | FK → employees |
status | 'processing' | 'indexed' | 'failed' | RAG indexing state |
tags | string[] | |
projectId | string | null | FK → projects if scoped |
grantId | string | null | FK → activeGrants if scoped |
DocumentChunk
Produced by the RAG pipeline. Stored in the vector store, not Firestore.
| Field | Type | Notes |
|---|
documentId | string | FK → documents |
chunkIndex | number | Position in document |
content | string | Text segment |
embedding | number[] | Vector embedding |
citationSource | CitationSource | Page number, section heading |
AIQueryLog
Logs all Document Brain AI queries for audit and quality improvement.
| Field | Type | Notes |
|---|
userId | string | FK → employees |
query | string | User’s natural-language question |
retrievedChunkIds | string[] | |
response | string | Generated answer |
feedback | 'positive' | 'negative' | null | Thumbs feedback |
9. People & HR
Extends the User entity with HR-specific data: time-off requests and security events.
Firestore collections: employees (extended User), timeOffRequests, securityEvents
TimeOffRequest
| Field | Type | Notes |
|---|
userId | string | FK → employees |
type | LeaveType | annual | sick | parental | unpaid | ... |
startDate | string | ISO date |
endDate | string | ISO date |
totalDays | number | |
status | 'pending' | 'approved' | 'rejected' | |
approvedBy | string | null | FK → employees |
notes | string | |
SecurityEvent
Immutable log of authentication and key events. Written by securityMonitoringService.
| Field | Type | Notes |
|---|
userId | string | FK → employees |
type | SecurityEventType | login | logout | failed_login | mfa_enrolled | password_changed | ... |
ipAddress | string | |
userAgent | string | |
success | boolean | |
metadata | Record<string, unknown> | Additional context |
ApiKey
Scoped API keys for external integrations. Secrets stored as SHA-256 hashes.
| Field | Type | Notes |
|---|
userId | string | Owner |
name | string | Label |
keyHash | string | SHA-256 of the raw secret |
prefix | string | First 8 chars, shown in UI |
scopes | ApiKeyScope[] | Permissions granted |
expiresAt | Timestamp | null | |
lastUsedAt | Timestamp | null | |
revokedAt | Timestamp | null | |
The extension/module system that allows feature packs to be installed per-organization.
Firestore collections: organizations/{id}/moduleInstallations, widgetDefinitions, widgetAssignments
ModuleInstallation
Runtime state of an installed extension module.
| Field | Type | Notes |
|---|
moduleId | string | Matches manifest id |
version | string | Installed semver |
status | 'installing' | 'active' | 'error' | 'deactivated' | |
installedBy | string | FK → employees |
installedAt | Timestamp | |
config | Record<string, unknown> | Module-specific settings |
Registered dashboard widget contributed by an extension.
| Field | Type | Notes |
|---|
extensionId | string | Source extension |
widgetId | string | Namespaced: {extensionId}_{id} |
name | string | Display name |
component | string | Lazy-load identifier |
defaultSize | 'sm' | 'md' | 'lg' | 'xl' | |
allowedRoles | string[] | |
A specific widget placed on a specific user’s dashboard.
| Field | Type | Notes |
|---|
userId | string | FK → employees |
widgetId | string | FK → widgetDefinitions |
position | { row: number, col: number } | |
size | 'sm' | 'md' | 'lg' | 'xl' | May differ from default |
11. Partnerships & Credits
The partner/reseller programme and the mission-credit wallet system.
Firestore collections: partnerOrganizations, partnerInvitations, referrals, missionCredits, creditTransactions, subscriptions
PartnerOrganization
A reseller or implementation partner.
| Field | Type | Notes |
|---|
name | string | |
contactEmail | string | |
revenueShareRate | number | 0.10–0.20 (10–20%) |
status | 'active' | 'suspended' | 'pending' | |
referralCode | string | Format: PARTNER_{PREFIX}_{RANDOM} |
Referral
Created when a referred user converts to a paying customer.
| Field | Type | Notes |
|---|
referredOrganizationId | string | FK → organizations |
referringPartnerId | string | FK → partnerOrganizations |
referralCode | string | Code used at signup |
convertedAt | Timestamp | |
creditAwarded | number | Default: €100 |
MissionCredit
Wallet balance for an organization. One document per org.
| Field | Type | Notes |
|---|
balance | number | Current balance in credits |
lifetimeEarned | number | |
lifetimeSpent | number | |
lastUpdatedAt | Timestamp | |
CreditTransaction
Ledger entries for the credit wallet.
| Field | Type | Notes |
|---|
amount | number | Positive = credit, negative = debit |
type | 'referral_award' | 'subscription_discount' | 'manual_adjustment' | |
description | string | |
referenceId | string | null | FK to the triggering entity |
Subscription
Stripe subscription linked to an org.
| Field | Type | Notes |
|---|
stripeSubscriptionId | string | |
tier | SubscriptionTier | |
status | 'active' | 'past_due' | 'canceled' | 'trialing' | |
currentPeriodEnd | Timestamp | |
seats | number | Licensed user count |
Cross-Aggregate Relationship Map
Firestore Collection → Entity Reference
| Collection | Entity | Notes |
|---|
organizations | Organization | |
employees | User | Not users — legacy naming |
invitations | Invitation | |
sessions | Session | |
grantOpportunities | GrantOpportunity | |
grantPipeline | GrantPipelineEntry | |
grantApplications | GrantApplication | |
activeGrants | ActiveGrant | |
grantReports | GrantReport | |
projects | Project | |
budgetItems | BudgetItem | |
milestones | Milestone | |
tasks | Task | |
timesheets | JournalEntry | Not journals or journalEntries |
retrospectives | JournalSubmission | Not journalSubmissions |
expenses | Expense | Allocations embedded as array |
invoices | Invoice | |
compliance | ComplianceRule + ComplianceAlert | Single collection, discriminated by type |
auditLogs | AuditLog | Append-only |
foundations | Foundation | |
contacts | Contact | |
documents | Document | |
documentFolders | DocumentFolder | |
partnerOrganizations | PartnerOrganization | |
referrals | Referral | |
missionCredits | MissionCredit | One per org |
creditTransactions | CreditTransaction | |
subscriptions | Subscription | |
organizations/{id}/moduleInstallations/{moduleId} | ModuleInstallation | Subcollection |
Multi-Tenancy Rules
- Strict Isolation — No entity can belong to more than one Organization.
- Scoping — All Firestore queries must include an
organizationId filter.
- Exceptions —
foundations is a shared library readable by all orgs; write access is platform-admin only.
- Cross-Tenant References — Prohibited at both the Service and Firestore Security Rules layers.
- BaseService enforcement —
organizationId is validated in BaseService.create() and BaseService.update() via the Zod schema layer before any Firestore write.
Maintenance
Update this model when:
- Introducing a new high-level module (e.g., Inventory, Payroll).
- Changing the ownership structure (e.g., multi-organization users).
- Adding or renaming a Firestore collection (update the reference table above).
- A new “naming gotcha” is discovered — document it in the collection reference table.
For collection-level query patterns and denormalization decisions, see docs/engineering/data/collection-relationships.md.
For state machine diagrams covering entity lifecycles, see state-machines.md.