GIK Marketplace · data model
This is a map of the GIK data: what gets stored and how the pieces connect. Start with the spine, then pick a domain to see its tables.
The spine — seven stages
- 1. Onboardwho is this orgWho is this organization, and which side of the exchange are they on?
organization,profile,organization_member,onboarding_submission,join_policy - 2. Declareneed and supplyWhat does an NPO need, and what can a supplier give?
need,resource,need_resource,resource_import,disaster - 3. Qualifywho is eligibleWho is eligible for what, under which constraints?
organization_restriction,resource_restriction,restriction_type - 4. Matchwhat answers whatWhich resources answer which needs, and how well?
match_run,match_score, search gate, grade - 5. Offera supplier commitsWhich supplier commits to which need?
offer,offer_item,offer_status,offer_source - 6. Fulfillgoods moveThe promise becomes goods in motion.
cart,order,order_item,order_transport_details - 7. Accountwhat happenedWhat actually happened, and who saw it?
activity_event,notification,activity_visibility
| Stage | The question it answers | Core concepts | |
|---|---|---|---|
| 1 | Onboard | Who is this organization, and which side of the exchange are they on? | organization, profile, organization_member, onboarding_submission, join_policy |
| 2 | Declare | What does an NPO need, and what can a supplier give? | need, resource, need_resource, resource_import, disaster |
| 3 | Qualify | Who is eligible for what, under which constraints? | organization_restriction, resource_restriction, restriction_type |
| 4 | Match | Which resources answer which needs, and how well? | match_run, match_score, search gate, grade |
| 5 | Offer | Which supplier commits to which need? | offer, offer_item, offer_status, offer_source |
| 6 | Fulfill | The promise becomes goods in motion. | cart, order, order_item, order_transport_details |
| 7 | Account | What actually happened, and who saw it? | activity_event, notification, activity_visibility |
The twelve domains — one Postgres schema (public); domains are a decided grouping, not a database boundary
Notes and recommendations
Findings from reading the schema as decided, not the database as deployed. Each one was verified against the migration SQL this explorer is generated from, and the constraint — or its absence — is cited so you can check the claim rather than take it on trust. Re-verified 2026-09-03 against gik-platform develop at 843dfa1b, the snapshot master schema 1.1 reads; every count below is from that SQL.
Ordered by what we would fix first. The first three are the ones we would raise in a meeting; the rest are worth a ticket each.
1. Every profile, including email, is readable by the anonymous role
profiles grants full table privileges to anon, and its SELECT policy carries no role restriction and no predicate. The two statements, verbatim:
GRANT ALL ON TABLE "public"."profiles" TO "anon";CREATE POLICY "auth" ON "public"."profiles" FOR SELECT USING (true);
A policy written without a TO clause applies to PUBLIC — every role, including anon. Combined with USING (true), any caller holding the project's anonymous key can read every profile row, email included. Nothing about this requires an account.
It is worse for deleted users. profiles uses tombstones rather than deletion: the visible PII columns are anonymised at delete time, and the original values are stashed in deleted_metadata on the same row. RLS is row-level, not column-level, so that column is readable under the very same policy. A user who deletes their account keeps their real name and email in a field the anonymous role can read.
Recommendation. Decide what a profile is meant to expose publicly. If the answer is a display name and avatar — which is all the "resolve a name for attribution" argument actually needs — expose those columns through a view, or scope the policy TO authenticated and serve public attribution from a narrower surface. deleted_metadata should not be reachable through a row policy at all; move it somewhere only service_role can read.
2. Eligibility is not enforced anywhere in the database
organization_restrictions and resource_restrictions describe who may receive what. Nothing in the database makes those rows binding: there is no CHECK constraint, trigger, RLS policy, or function anywhere in the schema whose definition mentions eligibility. The restriction tables do carry RLS, but it governs who may read and write restriction rows — not whether a restriction is honoured when an offer or order is created.
The only enforcement is client-side, in application code, and it checks a single field. A direct API call, a service-role write, or a bug in one screen bypasses the rules entirely, and the database will accept the row.
Recommendation. Decide deliberately whether eligibility is a rule or a hint. If it is a rule it needs a database-layer gate — a trigger, or an RPC all writes go through — because a client-side check is unenforceable by construction. If it is a hint, say so in the schema documentation so the next engineer does not assume protection that is not there. Either answer is fine; the current state is that the model implies enforcement it does not have.
The 1.1 schema shows what the alternative looks like in this same codebase. The unlisted rule on resources (GIK-336) is held by two triggers that refuse or correct the write wherever it comes from, and create_offer_with_items re-validates inside its transaction what RLS cannot see, because RLS gates rows and not the relationships between them. Eligibility has none of that, and the new partner reads widen the gap: the category, state, disaster and faith filters that search_need_items_by_embedding and portfolio_matches_for_org accept are the partner's own choices, not the restriction rows anyone declared, so a supplier's portfolio can rank a need from an NPO its own resource_restrictions say may not receive from it.
3. Six SECURITY DEFINER functions do not pin search_path
Of 39 SECURITY DEFINER functions, 33 set search_path and six do not:
matching.enqueue_match_runmatching.process_pending_match_runsneeds.create_need_with_itemsonboarding_rpc.stamp_email_on_sessiononboarding_rpc.lookup_organization_by_domainprofiles.handle_auth_user_update
The three added since 1.0 — ensure_match_worker_secret, close_unlisted_resource_on_offer_close and search_need_items_by_embedding — all pin it, so these are the same six as before.
Such a function runs with its owner's privileges and bypasses the caller's RLS. Without a pinned search_path, an unqualified name inside it resolves against whatever the caller's search path happens to be — the standard privilege- escalation shape for this class of function.
Recommendation. Add SET search_path = '', or an explicit schema list, to all six, matching the thirty-three that already do it. Additive and low risk.
4. Three tables are fully readable by every signed-in user
Each of these is TO authenticated USING (true) — any authenticated account reads every row, not only the rows relating to them:
organizations— the full organisation directory.organization_restrictions— every organisation's eligibility rules.organization_members— every membership in the system. The policy is namedUsers can view their own memberships, which is not what it does; the name describes an intent the predicate does not implement.
This may be deliberate for a marketplace where participants need to see one another. It is worth confirming, because the membership policy's name suggests at least one of the three was not.
Recommendation. Confirm each is intended, and rename the membership policy to match its behaviour whichever way you decide. A policy whose name contradicts its predicate is a trap for the next person auditing this.
5. organization_partners.status defaults to a value its own CHECK rejects
The column defaults to 'active'. Its CHECK admits only pending, accepted, and rejected. A plain INSERT relying on the default therefore fails outright.
This stays invisible only because the INSERT policy independently forces every application-written row to carry an explicit pending or accepted. It surfaces the moment anything inserts without naming a status — a migration, a backfill, a service-role script.
Recommendation. Change the default to 'pending', matching the sibling organization_invites.status, which already defaults that way.
6. Two foreign keys point at Supabase auth where every sibling points at profiles
The naming is inverted in both directions, which makes this easy to miss:
| column | actually references |
|---|---|
oauth_authorization_codes.user_id | public.profiles |
activity_events.user_id | public.profiles |
organization_partners.created_by_profile_id | auth.users |
organization_partners.responded_by_profile_id | auth.users |
Every other actor column targets public.profiles — created_by (seven of them), uploaded_by, triggered_by, invited_by_profile_id, deleted_by, converted_by_profile_id, and a created_by_profile_id on a different table that resolves correctly.
The join still works, because a profile's primary key is its auth user's id. But the constraint does not require a profile row to exist, and its ON DELETE SET NULL fires when the auth user is deleted — while profiles deliberately uses tombstones rather than deletion. So these two columns follow a different lifecycle from the rest of the schema.
Recommendation. Repoint both at public.profiles. The column names already say that is what they meant.
7. Three tables permit duplicates the model treats as impossible
None has a unique index, so the database will accept a second row where the domain expects one:
organization_followers— nothing prevents the same follow twice.organizations.followers_count, which this table is meant to summarise, is only as accurate as whatever recomputes it.organization_partners— nothing prevents two organisations accumulating more than one partnership row. A CHECK bars an org from partnering with itself; nothing bars a duplicate pair.organization_locations.is_primary— a plainboolean DEFAULT false NOT NULLwith no guard. Two locations on one organisation can both claim primary.
Recommendation. Add a unique index to each: the follow pair, the partner pair, and a partial unique on (organization_id) WHERE is_primary. All three are additive and ship without application changes — though each needs a duplicate sweep first.
What the schema gets right
Worth stating, because these are the parts most often wrong:
- RLS is enabled on all 43 tables. No table is left ungoverned.
api_keysuses column-level grants rather than the project-wideGRANT ALL, deliberately and with a comment explaining why, so the secret material is not reachable through the table grant.- Membership and admin checks are re-evaluated per query through helper functions rather than read from a cached permission, so a stale role row cannot grant access on its own.
- The
unlistedinvariant is enforced at the database boundary (1.1). Two triggers onresourcesrefuse an unlisted row from anything but the offer path, refuse a catalog row becoming unlisted or an unlisted one becoming public, and hold a closed one closed;create_offer_with_itemsis SECURITY INVOKER, so RLS still governs every row it writes, and it re-checks membership, need ownership, self-offer and resource ownership inside the transaction. This is the pattern item 2 asks for.
A note on what this document is
These are observations about the schema, drawn from the migrations. They are not a penetration test, and they say nothing about the application code above the database or the project's Supabase settings, where some of these may already be handled. Items 1 and 2 are the ones we would ask about first: item 1 because it exposes data today without an account, item 2 because the model implies an enforcement it does not have.
Whole data model · 43 tables in twelve domains
Every table in one picture, grouped by domain, with the lines that connect them. Click a table to see what it links to, or a domain name to open it.
A dashed line is a reference into another domain. The domain pages carry the columns.
Domain 1 of 12
identity
The people who use GIK, and how they like to be contacted. Each person's profile and their notification settings.
In technical terms
A profile's identity and reachability — display_name, avatar_url, timezone, and the user_settings.notification_preferences that decide how a notification reaches them. Owns the person, not their standing in any organization; membership, role, and join state belong to organizations.
Tables (2) — provenance abridged; click a row to expand, or read the full detail in the master schema
profilesOntology: Profileauth.users row it mirrors. Deletion is a tombstone: deleted_at is set and an edge function (delete-my-account, not a database trigger) anonymizes the PII columns and stashes the originals in deleted_metadata — a heavier hand than the plain, data-intact deleted_at this schema uses elsewhere. The SELECT policy stays USING (true) regardless of deleted_at, so a tombstoned profile still resolves in every "created by" join across the app; only the owner-only UPDATE additionally requires deleted_at IS NULL, so a deleted user can't edit their way back to visibility. email_verified_at exists here rather than reading auth.users.email_confirmed_at because Supabase auto-confirms every signup once confirmations are disabled — and it isn't decorative: cart_items_insert's WITH CHECK requires it non-null, making this column, not a UI prompt, the actual gate on adding to a cart.user_settingsOntology: User Settingsprofiles SELECT is USING (true) so any caller — including anon — can resolve a display name for attribution, while user_settings is owner-only (auth.uid() = profile_id) on every operation. Folding notification_preferences into profiles would inherit that wide-open SELECT and expose every user's channel preferences to any reader. A unique index on profile_id caps it at one settings row per profile, and handle_new_user() creates that row in the same transaction as the profile itself, so a profile with no settings row shouldn't exist outside a migration gap.Domain 2 of 12
organizations
The nonprofits and suppliers that take part, where they operate, and who belongs to each. Each organization, its locations, members, invitations, partners, followers, verified web domains, and any limits on who it may deal with.
In technical terms
Who takes part in the exchange, where they operate, and who may join or partner with them — membership, invites, verified domains, and the organization_restrictions that state eligibility. Owns the eligibility rule itself, not its enforcement: no CHECK constraint, trigger, RLS policy, or RPC applies a restriction_type anywhere in the platform — the one live check is client-side and covers a single type.
Tables (8) — provenance abridged; click a row to expand, or read the full detail in the master schema
organization_locationsOntology: Organization Locationcollapse_locations_into_org_locations) folded a standalone locations table into this one because a shared cross-org lookup forced open SELECT on every authenticated user and blocked clean per-org RLS. Only resources references a location — it snapshots the address columns onto its own row at creation rather than joining live, so a marketplace viewer can read a pickup address through ordinary resource policies without this table needing a partner or public SELECT policy of its own; needs carries the same address shape but with no FK to this table at all, entered fresh rather than copied from a location. Two CHECKs keep the pickup window sane — pickup_days must be a subset of 0-6, and pickup_earliest must precede pickup_latest when both are set — but is_primary carries no such guard: nothing stops two locations on the same org from both claiming it.organization_followersOntology: Organization Followerorganization_partners' bidirectional connection: no acceptance step, no status column. A CHECK bars an org from following itself, but no unique index covers the (follower_organization_id, followed_organization_id) pair, so nothing stops the same follow from being inserted twice at the database layer — organizations.followers_count, which this table is meant to summarize into, is only as accurate as whatever recomputes it.organization_invitesOntology: Organization Inviterequest_type = 'invite' requires invited_by_profile_id IS NOT NULL, request_type = 'request' requires it NULL — an admin invite always names who sent it, a self-request never does. Unlike offers.expires_at, this table's expiry actually fires: expires_at defaults to seven days out, and a BEFORE trigger flips status to expired the next time the row is written — lazy expiry, not a cron sweep, so a row nobody touches past its date stays pending in storage until something writes to it. That same trigger stamps invited_profile_id with auth.uid() on any request row that arrives without one, so a join request always self-attributes regardless of what the client sends.organization_membersOntology: Organization Memberrole (member/admin, with super_admin reserved) has no CHECK constraint of its own — it's a bare text column, and the only guardrails are the RLS policies that touch it, every one of which caps the value at member/admin on insert or update. The row alone grants nothing: every table's admin check re-evaluates check_org_admin_membership() per query rather than reading a cached permission, so a role='admin' row is only as powerful as whatever function call happens to consult it next. No client-facing policy can ever write role='super_admin' — that value can only originate from a service_role write, which bypasses RLS entirely.organization_partnersOntology: Organization Partnerrequested_by_organization_id is pinned to organization_id at creation — not by a table CHECK but by the INSERT policy's WITH CHECK — so the row always records the proposal from the requester's own organization_id column, and reading it from the other side means comparing, not assuming, which side proposed it. Nothing at the database layer stops the same two organizations from accumulating more than one partnership row: there's no unique index over (organization_id, partner_organization_id), only the CHECK barring an org from partnering with itself. status defaults to 'active', a value its own CHECK constraint does not admit — the column accepts only pending/accepted/rejected — so a bare INSERT that relies on the default fails outright; the defect stays invisible only because the INSERT policy's WITH CHECK independently forces every application-written row to carry an explicit pending or accepted value.organizationsOntology: Organizationjoin_policy (open/domain_only/approval/invite_only) is read in several places — search_organizations(), lookup_organization_by_domain(), the org settings UI — but the auto-join decision itself is made by handle_new_user(), a trigger on auth.users, not by anything on organization_domains: a join request against organization_invites can be filed on an invite_only org same as any other; join_policy only decides whether a domain match skips that request step. Retirement is soft delete only: deleted_at/deleted_by/deleted_reason tombstone a row while DELETE is revoked outright at the GRANT level for anon and authenticated, not merely left unpoliced — all but two of this table's FKs cascade (needs.assigned_to_org_id and organization_partners.requested_by_organization_id are SET NULL instead), so a hard delete would still silently take out the orders and offers of organizations that never asked to be deleted. order_counter is what a trigger on orders increments to mint each new order's display_id, so per-org order numbering lives here rather than on orders itself. connections_count/followers_count are written from the client, not by any trigger in this schema — nothing here keeps them consistent with the organization_partners/organization_followers rows they summarize.organization_domainsOntology: Organization Domainhandle_new_user() matches the signup's email domain against is_verified = true rows, but a match alone doesn't auto-join: the org's join_policy must also be open or domain_only, so an approval or invite_only org keeps every domain-matched signup routed through the join-request flow instead. domain carries a UNIQUE index with no organization_id in it, so one domain can be claimed by at most one organization platform-wide, never shared or re-verified by a second. As written, an auto-joined member is inserted with role = 'admin', not 'member' — a traction-phase shortcut the migration itself flags for reversion, not the steady-state behavior the column default implies.organization_restrictionsOntology: Organization Restriction(organization_id, restriction_type), held to exactly one by a unique index — the same one-value-per-type shape resource_restrictions uses in §3, and the org-wide default a resource-level row there is defined to override. value is unconstrained jsonb: no CHECK ties its shape to restriction_type, so a quantity_limit row could hold a string, an object, or nothing numeric at all, and the database accepts it regardless of which of the six types the row claims. Two SELECT policies stack on this table — one scoped to org members, one open to any authenticated user — and because RLS ORs permissive policies together, the broader one is what actually governs reads.Domain 3 of 12
resources
What a supplier can give away, and on what terms. Each listed item, its photos, who may claim it, and the spreadsheets suppliers upload to list many items at once.
In technical terms
What a supplier can give and on what terms: quantity, condition, pickup logistics, and the resource_restrictions that declare, but do not enforce, who may claim it. Owns the listing, not its fate — whether a resource gets matched, offered, or shipped is recorded by matching, offers, and orders, not by this domain.
Tables (5) — provenance abridged; click a row to expand, or read the full detail in the master schema
resourcesOntology: Resourcestatus (draft/live/closed) governs whether it is matchable and orderable at all; visibility (private/partners/public) governs who can see a live one — the two are independent, so a live resource can still be private, visible only to its own org and to any NPO currently holding an offer against it. quantity/ordered_quantity is a reservation ledger, not a display number: a CHECK constraint keeps ordered_quantity inside [0, quantity] no matter which code path wrote it, once quantity is set — but quantity is nullable and status defaults to live, so a live resource with no declared quantity has no database-enforced ceiling on ordered_quantity at all. The Surface-1 logistics columns (transport_payment_type, origin_dock_available, truck_type, …) are soft flags — all nullable by design, after an earlier two-state dock toggle stamped a false "no dock available" onto every listing nobody had actually answered; missing data now surfaces on the compiled logistics record instead of blocking the listing. is_hazmat carries its own free-text explanation (hazmat_materials) so a "yes" doesn't force a support round-trip to find out what. unlisted (1.1) marks a resource born inside an offer rather than loaded from the supplier's inventory: a supplier answering a need can describe the item in the offer itself, and the row is created at once — live, private and unlisted — so that checkout reserves against it exactly as it would a catalog listing. It is a separate boolean rather than a fourth status because reserve_resource_quantity refuses anything not live. The database holds that invariant, not the handlers that happen to know about the flag: a BEFORE INSERT trigger refuses an unlisted row unless the offer path has announced itself with a transaction-local setting (gik.unlisted_write) that no PostgREST caller can set, and a BEFORE UPDATE trigger stops a catalog row becoming unlisted, an unlisted row joining the catalog or leaving private, and quietly holds a closed one closed when release_resource_quantity tries to reopen it on an order cancellation. What the database does not do is hide the row from its owner: resources_select_own grants by membership alone, so keeping offer-born rows out of My Resources, the marketplace and matching is application filtering — the match-run trigger and resource_match_summary exclude them here, and every other reader is held to it by a census in the client repo's contract tests, not by a policy.resource_imagesOntology: Resource Imagefiles row rather than storing bytes itself. is_cover is a plain boolean with no uniqueness constraint behind it — nothing in the database stops two rows from claiming the cover slot on the same resource; the application is responsible for picking one.resource_restrictionsOntology: Resource Restriction(resource_id, restriction_type) — a unique index caps it at one value per type, so a resource can't carry two conflicting allowed_state rules. A resource-level row overrides the org-wide default of the same type carried on organization_restrictions; a resource with no row for a given type inherits the org's default rather than having none. Its resource_restrictions_select_live policy opens read access to any authenticated user once the parent resource is live, because eligibility has to be checkable by whoever is deciding to offer against it, not only the owner.resource_importsOntology: Resource Importtotal_rows/success_count/error_count are the job's own ledger, so a failed import can be diagnosed without recounting resource_import_rows. column_mapping freezes the header-to-field mapping the uploader chose at submit time, so a saved import stays interpretable even after the resource schema itself changes.resource_import_rowsOntology: Resource Import Rowraw_data even after it succeeds — the parsed, normalized result lives only in the resources row it produced, never written back here. resource_id is set on success and only detached (ON DELETE SET NULL), not removed, if that resource is later deleted: the import keeps its record of what was uploaded independent of the resource's own lifecycle.Domain 4 of 12
needs
What a nonprofit is asking for, and how urgently. Each request, the items and quantities on it, its photos, and the disaster it answers.
In technical terms
What an NPO is asking for, and how urgently: demand_status, priority, and deadline on a standing request whose line items live in need_resources. Owns the request, not the event behind it — disaster_id only tags a disaster; the event itself belongs to disasters.
Tables (3) — provenance abridged; click a row to expand, or read the full detail in the master schema
needsOntology: Needstatus (draft → open → partially_fulfilled → fulfilled, or cancelled) is driven from both ends: the owner sets draft/cancelled directly, and a trigger recomputes every other value from need_resources fulfillment, never touching those two owner-controlled states. The delivery address is copied inline rather than FK'd to organization_locations, so partner and public readers inherit visibility from the need itself instead of needing a policy on a locations table they may not otherwise see. latest_match_run_id is a debugging convenience — no read path in the app actually scopes to it. disaster_id is pure metadata: tagging a need with a disaster does not by itself re-trigger matching.need_resourcesOntology: Need Line Itemneeds because a need is a standing request while its lines get revised — editing a quantity must not rewrite the request's own history.need_imagesOntology: Need Imagefiles row rather than storing bytes itself. is_cover is a plain boolean with no uniqueness constraint behind it — nothing in the database stops two rows from claiming the cover slot on the same need; the application is responsible for picking one.Domain 5 of 12
matching
GIK's automatic search for supplies that could answer a need. Each search run and the scores it gave; a good score is not yet an offer.
In technical terms
A run and its scores: which resources were considered for a need, how they ranked, and under which threshold. Owns no commitment — a high score is not an offer, and nothing here moves goods.
Tables (2) — provenance abridged; click a row to expand, or read the full detail in the master schema
match_runsOntology: Match Runstatus (match_run_status: pending → running → completed/failed) tracks the attempt's own lifecycle, independent of whatever match_scores rows it eventually produces. trigger_type (match_trigger_type) records why the attempt exists (resource_live, need_updated, …); enqueuing only writes this row and an activity_events entry — it does not itself invoke the matching worker, the app calls that separately after the mutation that fired the trigger, and since 1.1 a pg_cron job (match-run-drain-every-5-minutes, hand-authored in a migration because cron and Vault live outside the declarative schema) drains whatever is still pending every five minutes, presenting a secret that ensure_match_worker_secret() mints into Vault on first use. The job is a backstop, not the primary path: before it, a run whose fire-and-forget invocation was lost sat pending until some unrelated mutation happened to drain the queue. The latest_match_run_id pointer that needs and resources keep back to this table is for debugging only: no read path in the app scopes results to it.match_scoresOntology: Match Scoresearch_score (semantic + category) gates whether the pair is kept at all, sort_score (quantity + location + semantic) only orders the pairs that already cleared that gate. grade (match_grade) is derived from search_score alone, so a pair with a poor quantity/location fit can still grade A_plus while ranking below a B with a better one. is_eligible defaults true and is flipped false only by a later run's invalidation pass — it marks a row as superseded, not as ineligible under some org's or resource's restrictions. Two partner-facing reads sit on top of this table (1.1). portfolio_matches_for_org answers "across everything I have, which open needs should I offer on?" by taking the newest row per pair first and applying is_eligible and the 0.68 threshold after — filtering first would rank a supplier's portfolio on scores a later run had already superseded — and runs as SECURITY INVOKER, so the caller's own match_scores policy stays the tenant boundary. search_need_items_by_embedding bypasses stored scores altogether: it walks the vector index for a partner's phrase, one row per need line item, with the category, city, state, disaster and faith filters evaluated inside the scan so a filtered search keeps looking rather than returning the nearest forty rows minus the ones that failed. Neither consults organization_restrictions or resource_restrictions — the filters a partner sends are its own, not the eligibility rules the model declares.Domain 6 of 12
offers
A supplier's promise to give specific items toward a nonprofit's need. Each offer, the items on it, and whether it is waiting, accepted, declined, or cancelled.
In technical terms
One supplier's commitment against one need, moving through pending to acceptance, decline, or cancellation. Owns the commitment, not the delivery — acceptance itself creates no order; checkout does that later and backfills offers.order_id as provenance, so moving goods after that belongs to orders.
Tables (2) — provenance abridged; click a row to expand, or read the full detail in the master schema
offersOntology: Offerstatus leaves pending three ways — acceptance, decline, or cancellation — and only acceptance is ever followed by an order. order_id is provenance, not a link forward: the accept action itself never writes it, only checkout does, later, and only for offers that reached accepted — which is why a declined offer leaves no order behind. expired is a real value in offer_status and expires_at is a real, API-settable column, but nothing in the schema ever acts on either: create_offer_with_items stores whatever expires_at a caller sends and nothing reads it back, and no trigger, cron job, or RPC moves a pending offer to expired — the status exists in shape only, ahead of an expiry policy that has not been built. A CHECK enforces that source = 'manual' carries manual_supplier_name, because the model deliberately records off-platform generosity. Since 1.1 an offer can also bring its own resource: create_offer_with_items writes the offer, its items and any resource described inline in one transaction — SECURITY INVOKER, so RLS still decides each insert — and re-checks inside that transaction what RLS cannot, because RLS gates rows rather than the relationships between them: that the caller belongs to the supplier org, that the need is open and owned by the stated recipient, that no organization is offering against its own need, and that every named resource is the supplier's own catalog row rather than another offer's unlisted one. When such an offer is then declined, cancelled or expired without an order (order_id IS NULL), the offers_close_unlisted_resource trigger closes the resource it created, since nothing can ever offer it again. Retiring an organization (soft_delete_organization) cancels its pending and accepted offers on both sides but leaves alone any that reached checkout: by the time the sweep runs, an offer holding an order_id can only point at a delivered, rejected or cancelled order, and cancelling it would relabel a fulfilled offer "Withdrawn" and strip the order link off it.offer_itemsOntology: Offer Line Itemresource_id is nullable — a manual offer for an off-platform supplier has no platform resource to point at, only manual_item_name. The two FKs disagree on delete: losing the underlying resources row cascades and removes the item with it, but losing the need_resources line only detaches it (SET NULL) — the offer already recorded a commitment against that need and keeps its own memory of it. An inline item — one the supplier described in the offer instead of picking from a catalog — still carries a resource_id: create_offer_with_items inserts the unlisted resources row first and points the item at it. So the shapes here are catalog-or-inline for a platform offer and manual_item_name alone for an off-platform one, and nothing on the item says which of the first two it is — that lives on the resource, as unlisted.Domain 7 of 12
orders
Goods on the move, what was agreed, who ships it, and where it is going. Carts, orders, the items on them, and the pickup and transport details for each.
In technical terms
Promises becoming goods in motion: carts/cart_items for NPO-initiated pulls and accepted offers for supplier-initiated pushes, both converging on one orders row with its own order_transport_details. Owns the logistics of moving resources, not the decision to trade — that decision was already made in needs, resources, or offers before an order exists.
Tables (5) — provenance abridged; click a row to expand, or read the full detail in the master schema
cartsOntology: Cartorganization_id makes a second carts row for the same org impossible, so every member of an NPO adds to the same shared cart rather than a personal one. It carries no status column: a cart is nothing but a bucket of cart_items until checkout converts it into an orders row elsewhere; nothing here records that a checkout happened.cart_itemsOntology: Cart Itemresource_id alone) or from an accepted offer via offer_id — the offer-driven path the vocabulary above already names, and the reason checkout can later backfill offers.order_id. Adding a line takes more than org membership: cart_items_insert's WITH CHECK also requires the caller's own profiles.email_verified_at IS NOT NULL, and that is the actual enforcement boundary — the client-side verify-email prompt only front-loads the same rule for a better UX. quantity carries no CHECK of its own; nothing at this layer stops a zero or negative value from being staged.ordersOntology: Orderorganization_id is the receiving NPO, supplier_org_id the giver, and both carry their own SELECT/UPDATE policies against it, so neither party is reading a derived copy. display_id is minted server-side whenever it is absent — a BEFORE INSERT trigger returns early if the incoming row already carries a value, and otherwise generates one from the receiving org's own order_counter, incrementing that counter and prefixing the org's slug, so numbering resets per org rather than running platform-wide; orders_insert's WITH CHECK only requires org membership, so a client-supplied value is admitted, not rejected. delivery_method is CHECK-constrained to self_pickup/own_freight/alan, but whether the chosen method is one the order's own resources actually support is never checked here — that comparison spans every order_items row, so it's enforced only in the checkout edge function, not by this table. Moving an order to cancelled or rejected through transition_order_status() releases every reserved unit back to its resource and un-fulfills the need line it consumed — but only on the first such transition; a repeat is a no-op.order_itemsOntology: Order Line Itemresources → org location default — where NULL means "no override, use the resource," not "unanswered." These overrides live per line rather than per order because one order groups items from a single supplier but each item is a different resource with its own packaging, dock, and hazmat answers; order_transport_details is UNIQUE per order and could only ever describe one of them. Only the supplier may write these columns, and that is enforced below RLS: UPDATE is REVOKEd from authenticated outright and re-GRANTed only on the five override columns, so a defect in the update policy still couldn't let a supplier rewrite quantity or resource_id on a line it doesn't own. The safety data sheet is deliberately excluded from this per-line set (resources.hazmat_doc_file_id stays a property of the substance) while the named hazmat contents (hazmat_materials) are overridable here, because what's actually in a shipment can legitimately differ from the listing.order_transport_detailsOntology: Order Transport Detailsorder_id. Both the receiving and supplying org hold full INSERT and UPDATE on every column here: unlike order_items, there is no column-level GRANT split, so which party is supposed to write which field is a convention the application observes, not a rule this table enforces — the receiving org fills requester_contact_* at checkout, the supplier fills driver_gate_instructions on confirm-pickup, and nothing in the schema stops either party from writing the other's columns. requester_contact_name/_phone/_email exist to close a specific bug: the compiled logistics record used to derive the requester from whoever was viewing the page, so the supplier's own view named itself as the requesting org and its carrier export addressed the wrong party — these columns store one shared value instead of that per-viewer derivation. Three other columns, vehicle_type/has_liftgate/has_pallet_jack, are deprecated in place: that logistics moved to the resource (resources.truck_type/loading_method) when the supplier lists it, and nothing in the current schema writes these columns anymore, though old rows keep their values for backward compatibility.Domain 8 of 12
disasters
The events that make a need urgent, such as a hurricane or a flood. Each disaster, when it happened, and the FEMA details GIK keeps up to date.
In technical terms
The events that make a need urgent — declaration, incident window, and the FEMA fields a disaster_sync_run keeps current. Owns urgency context, not eligibility or matching — a need may carry a disaster_id, but a disaster never gates eligibility or moves a score.
Tables (2) — provenance abridged; click a row to expand, or read the full detail in the master schema
disastersOntology: Disastertype GIK filters by is derived from FEMA's own fema_incident_type rather than stored verbatim, and sub_labels records secondary perils without displacing that one dominant type. Ingestion admits less than the disaster_type enum allows: earthquake is gated off in the sync's config, and only DR/EM declarations are admitted, not FM — which would flood the active list with wildfire suppression grants rather than the disasters suppliers actually respond to. Neither restriction is written as a database rule: both are enforced by the sync edge function before a row is ever inserted, not by any CHECK here. declared_at gates whether a declaration is recent enough to display; active_until is a separate, admin-only override on when the 90-day support window auto-archives the row. Cleanup only ever archives, never deletes: needs.disaster_id is ON DELETE SET NULL, so a hard delete would silently strip a need's disaster tag, and archiving instead leaves the tag pointed at a row the UI renders as archived. Editing disaster_id alone never re-enqueues matching — the trigger that does, needs_enqueue_match_run, doesn't watch that column.disaster_sync_runsOntology: Disaster Sync Runstatus, a counts JSON tally, and a length-capped error once the run finishes. counts summarizes fetched/inserted/updated/merged/archived/llm_classified/skipped/excluded rows; there's no FK back to the disasters rows a run actually touched, so this table is a summary log, not a queryable diff. The next sync's window start comes only from the last row with status = 'success', ordered by started_at — a run stuck at running after a crash is skipped over, never blocking the schedule from advancing. trigger_source distinguishes schedule from manual; only a manual run sets triggered_by, since nothing initiated a scheduled tick as a person.Domain 9 of 12
auth
How a partner's software proves who it is when it talks to GIK. API keys, connected apps, their sign-in tokens, and a log of every call they make.
In technical terms
How an external system proves who it is and what it may call — api_keys and the full OAuth grant chain from oauth_authorization_codes through oauth_refresh_tokens, with every call logged to partner_api_audit. Owns authentication and scope, not business eligibility: a key can prove identity for an organization even though nothing server-side ever checks that organization's restriction_type rules — eligibility is declared, not enforced, anywhere in the platform.
Tables (6) — provenance abridged; click a row to expand, or read the full detail in the master schema
api_keysOntology: API Keykey_hash is a sha256 of the presented secret, shown once at creation and never stored as plaintext. Two credential shapes share this table — a manually issued key (oauth_client_id null) and a remote-MCP connector key capped at one active row per (org, client) by a partial unique index — because both ultimately resolve through the same service_account_profile_id bridge into a scoped JWT. The RLS policy alone would not stop an org admin from self-granting privilege: api_keys_update_admin checks only org-admin membership, so what actually restricts a PATCH to label/revoked_at and keeps scopes/key_hash off the wire is a column-scoped GRANT sitting under an explicit REVOKE of Supabase's table-wide default. Creation never goes through this policy at all — a service-role edge function mints the secret and provisions the account, so an org admin's only writes are relabel and revoke.partner_api_auditOntology: Partner API Auditapi_keys.last_used_at is read off the same rows, so this table is enforcement infrastructure, not merely an audit trail. It sits in auth rather than platform because it audits partner API usage specifically, not the platform's general activity ledger (that's activity_events); the placement is arguable, since it is, structurally, an event log like that one. organization_id carries no foreign key, unlike api_key_id — it's a denormalized copy for the org-scoped index and RLS check, not a guarantee the id still names a live organization. api_key_id is ON DELETE SET NULL: revoking or deleting a key never erases the requests it made, only detaches them from the credential.oauth_clientsOntology: OAuth Clientclient_secret_hash is nullable because a public, PKCE-only client never has one, which is also why token_endpoint_auth_method defaults to 'none'. redirect_uris is an allow-list enforced at issuance, not documentation: /authorize and /token only honor a redirect_uri present in the array, closing off open-redirect and code-interception. RLS is enabled with no policy for authenticated or anon at all — deny-all by omission — so only service_role can read a client's own registration; a future "list my connected apps" admin view would need its own narrow SELECT policy that doesn't exist yet.oauth_authorization_codesOntology: OAuth Grantapi_key_id it will mint — the bridge that lets an OAuth-authenticated MCP call resolve to the exact same AuthContext, and run the same scope-check → scoped-JWT → RLS → audit path, as an X-API-Key request. Single-use is not a database constraint: consumed_at has no CHECK or trigger behind it — the token exchange enforces it itself, with an UPDATE ... WHERE consumed_at IS NULL, so a replayed exchange matches zero rows instead of racing a legitimate one. expires_at works the same way, as a timestamp the /token handler compares to now(), not a row anything prunes. Like oauth_clients, RLS is deny-all: no policy exists for any role but service_role.oauth_access_tokensOntology: OAuth Grantrevoked_at/expires_at before resolving api_key_id to the backing key's org, service account, and live scopes. api_key_id is NOT NULL and ON DELETE CASCADE, so revoking or deleting the backing api_keys row does more than block new grants — it deletes every access token already issued against it, with no separate per-token revoke step required. Same deny-all RLS as the rest of the OAuth tables.oauth_refresh_tokensOntology: OAuth Grant/token for a fresh access token and rotated on use — the old refresh token is revoked the moment a new access token is issued, so a token is never reused across two access-token generations. access_token_id cascades from the very access token it was minted alongside, so deleting that access token row deletes its refresh token too; the two share one lifecycle rather than aging out independently. scopes is copied onto the row rather than joined from api_keys at read time, so a scope downgrade on the key doesn't retroactively narrow a refresh token already issued — the token it mints on rotation still carries the scopes frozen at issuance, not the key's current ones.Domain 10 of 12
webhooks
The messages GIK sends to a partner's software when something happens. What each partner asked to be told about, and every delivery attempt and whether it landed.
In technical terms
What GIK told an external system, and whether the message landed — one webhook_subscriptions row per URL and event set, one webhook_deliveries row per attempt with its own status and attempt count. Owns delivery and retry, not the underlying event: a delivery failing to land never undoes whatever already happened on the GIK side.
Tables (2) — provenance abridged; click a row to expand, or read the full detail in the master schema
webhook_subscriptionsOntology: Webhook Subscriptionsigning_secret_vault_id stores a pointer, never the secret itself: the original spec called for a hash, corrected because a hash can't sign and the delivery worker needs the plaintext at send time, so the secret lives in Supabase Vault and this column is the only way back to it, resolved through a service-role-only function. disabled_at/disabled_reason exist for an auto-disable feature that isn't built — enabled is the only control that actually stops delivery today. The column-scoped GRANT mirrors api_keys: an org admin can flip enabled but can't PATCH url or reach the vault reference, which would otherwise let them redirect a signed feed of another org's order data.webhook_deliveriesOntology: Webhook Deliverypayload starts null: the trigger records only the event facts synchronously, inside the caller's own transaction, and a worker composes the full body later from the same DTO code that serves the matching REST endpoint — the only way a retry can replay byte-identical bytes rather than re-serializing newer state. status reaching failed means the backoff schedule ran out, not that nothing more will happen — it's terminal only until an explicit replay; pending is the sole state the claim query considers due. Both supplier_org_id and receiver_org_id sit on every row, and delivery is never filtered by them: a partner that is both parties gets its own copy of the event and is expected to tell which side it's on from the role field in the payload, not have the platform suppress it at the source.Domain 11 of 12
notifications
What GIK tells a person, and by which channel. Each notice, who received it, how it was sent, and whether it has been read.
In technical terms
What a person is told, and through which channel — one notifications row per event, fanned out to one notification_recipients row per person with its own channel and is_read state. Owns the message and its read state, not a person's standing preference for how they want to be reached; that default lives in identity's user_settings.notification_preferences. The ontology's Account stage groups this domain with activity_event, but the schema keeps the two apart — the event ledger itself is platform's.
Tables (2) — provenance abridged; click a row to expand, or read the full detail in the master schema
notificationsOntology: Notificationtarget_type/target_id pointer to whatever it's about — is created once here, no matter how many people need to hear it; fan-out to individuals is entirely notification_recipients' job. organization_id is nullable, and the SELECT policy never references it: a person can read a notification only by being a listed recipient, so recipient membership is the access boundary, not org scoping. RLS carries that entire boundary alone: unlike auth/webhooks, this table keeps the project's default GRANT ALL to anon and authenticated rather than a REVOKE-first, column-scoped grant — every write being denied depends entirely on there being no INSERT/UPDATE/DELETE policy for anyone, so only service_role can write a notifications row at all.notification_recipientsOntology: Notification Recipientchannel covers only in_app and email, so a person notified both ways gets two rows, and marking the in-app one read never touches the email row's is_read. sent_at is meaningful only on the email channel: the send path inserts every row, in-app included, without touching sent_at, and only the email dispatcher ever writes it, on a successful send — an in_app row's sent_at stays null for its entire life, since there's nothing to send. A recipient whose email failed to go out keeps sent_at IS NULL, which the sender's own code calls "the audit trail a later retry job (if added) would key off" — no such retry job exists yet. Same posture as its parent: GRANT ALL at the table level, with only a SELECT/UPDATE pair of policies — both gated on profile_id = auth.uid() — doing the actual restricting.Domain 12 of 12
platform
Shared record-keeping the rest of GIK leans on. The activity log, uploaded files, sign-up requests from people without an account yet, and app settings.
In technical terms
Cross-cutting record-keeping the exchange leans on but doesn't itself decide: the activity_events ledger, shared files, pre-account onboarding_submissions, and small app_config key-values. Owns infrastructure other domains reference, not their decisions — an onboarding_submission carries no organization_id because it predates the org it may eventually become.
Tables (4) — provenance abridged; click a row to expand, or read the full detail in the master schema
activity_eventsOntology: Activity Eventparent_event_ids lets one event cite others it derives from, building a causal chain rather than a flat log. provider declares a second backend, hedera, beside local — the client's own HederaActivityProvider throws "not yet implemented" on every method, so every row in production is provider = 'local' and provider_ref, meant to hold a Hedera Consensus Service transaction id, is unused. visibility is not an access-control column: the enum runs public through private, but the one RLS policy gating SELECT checks org membership alone, and the application's own query layer treats visibility as an optional equality filter a caller may pass, never a default restriction — a private row is exactly as readable to any org member as a public one.filesOntology: Fileuploads Storage bucket, a separate schema this table has no FK into. It's a shared pointer, not scoped to one domain: need_images, resource_images, resources.hazmat_doc_file_id, and organizations.logo_file_id/banner_file_id all reference the same files.id. created_by carries no FK at all — unlike almost every other actor column in this schema, a removed profile leaves a dangling id here rather than a nulled or cascaded one. RLS is a blanket USING (true) WITH CHECK (true) for any authenticated user, so whatever boundary exists on who can read or write a given file's metadata is enforced by the Storage bucket's own folder-based policies, not by this table.onboarding_submissionsOntology: Onboarding Submissionsession_id, not organization_id, because no org exists yet to own it. 'abandoned' is a declared onboarding_submission_status value the application never writes: only 'in_progress' (on submit) and 'completed' (on conversion) appear anywhere in the edge functions; a session that's actually abandoned stays 'in_progress' forever, distinguished from a live one only by age, not by its own status. expires_at defaults to 90 days out at insert, but like 'abandoned', nothing reads it — no cron, trigger, or handler checks or clears an expired row. stamp_email_on_session is the one write path that merges history: attaching an email to a session also folds in any other still-'in_progress' session that used the same email, so someone who starts, drifts away, and comes back under one address converges onto a single session instead of leaving orphaned rows.app_configOntology: App Configkey is the primary key, so many settings could live here — but exactly one is ever read anywhere in the codebase: super_admin_org_id, which is_super_admin() joins against to resolve the designated super-admin org. This table is invisible to every role but service_role: RLS is enabled with no policy at all, not even a narrow admin SELECT, so an authenticated user cannot read it under any circumstance, super admin included. A missing row doesn't error — is_super_admin() returns false everywhere and the admin console goes inert. Comparing the stored value to an org id as text, rather than casting to uuid, is deliberate: a malformed config value fails the equality check instead of raising an exception inside RLS evaluation, which would otherwise break every policy that calls this function.GIK Marketplace · ontology
GIK Marketplace matches goods that groups can give with groups that need them, then tracks each order to the door.
Module map · each line joins two modules whose terms are related and says what joins them. The arrow points from the module a relationship starts in. Click a module to see it in the whole ontology, or a line to list every relationship it stands for.
The twelve modules
Vocabulary
Reproduced from the compiled ontology document, so the words here are the designer's own.
This document speaks the vocabulary of formal ontology wherever a standard term exists, and says plainly where a term is Augusto's own. The table maps each term used below to its equivalent in formal ontology (OWL) and in Palantir's Ontology.
| Designer term | Formal ontology (OWL) | Palantir Ontology | Meaning |
|---|---|---|---|
| class | class | object type | A kind of thing the business talks about, with a certified definition. |
| property | data property | property | A named, typed value a class carries: string, number, boolean, date, currency, link, or an enum. |
| relationship | object property | link type | A named, directed connection between two classes. |
| domain, range | domain, range | source and target object type | The two ends of a relationship: the domain class is the one the relationship reads from, the range class the one it points at. Formerly spelled from and to, which are deprecated. |
| forward | property name | link name | The relationship read from its domain class, as in "Client has Engagement". |
| inverse | inverse property | reverse link name | The relationship read from its range class, as in "Engagement belongs to Client". Formerly spelled reverse, which is deprecated. |
| cardinality | cardinality restriction | cardinality | How many instances each end of a relationship admits: one-to-one, one-to-many, or many-to-many. |
| enum | controlled vocabulary, value set | value type | A closed list of the values a property may take. |
| instance | individual | object | One real thing a class describes, read from the data a binding points at. |
| module (on a class) | module (ontology modularization); UML package | none; Foundry groups by project | A governed grouping of classes, declared in ontology.yml. See the module rule below. Formerly spelled domain, which is deprecated. |
| group (on a relationship) | none | none | A section heading for the relationship list in this document and in the designer sidebar. Groups partition relationships, never classes. |
| subclass_of | subClassOf | interface, inheritance | The parent class a class specializes, single inheritance. A subclass inherits the parent's properties, stands in for it at relationship ends, and binds its own instances. |
| binding, via, source-of-record table | none; nearest are an R2RML mapping and grounding | none; nearest are a data connection and a backing dataset | Augusto's own terms for where a class's instances live and which field carries a relationship's links. |
| status, steward | none; governance, not ontology | none | Whether a definition is draft, agreed, or deprecated, and who certifies it. Augusto's own terms. |
Module rule. A module is a governed grouping of classes and nothing else; the grouping was formerly spelled domain, which is deprecated. A relationship's domain and range are its source and target classes in the OWL sense: the domain class is the one the relationship reads from, the range class the one it points at. Module never means domain, and domain never means module. A relationship's modules are never declared; they follow from the modules of its domain and range classes. A relationship's group is a different concept: it sections the relationship list, and the groups partition relationships, not classes.
Notation. On the canvas a relationship is a solid line when it is traversable against the vault and a dashed line when it is definition only, and its forward name shows when it is selected. subclass_of follows VOWL, the visual notation for OWL: a dashed line ending in a hollow arrowhead that points at the parent, carrying no name, because it is not a relationship. Readers used to UML should note that UML draws generalization with a solid line and a hollow triangle and reserves the dashed form for interface realization; the VOWL reading is the one intended here. A subclass that names a role its parent can hold and lose (an organization while it holds a role, a product while it is offered as an add-on) is what OntoUML calls a «role» subclass under a «kind»: anti-rigid, because an instance can stop being one, and relationally dependent, because it holds the role through a grant or an offer. In prose, a subclass is-a its parent, an instance is an instance-of its class, and the cardinalities one-to-one, one-to-many, and many-to-many map onto UML multiplicities and OWL cardinality restrictions alike.
Whole ontology · 41 classes in twelve modules
Every term in one picture, grouped by module, with the relationships between them. Click a term to see what it connects to.
A dashed line with no label is subclass_of; the sub-class itself is drawn with a dashed outline, naming the class it is a kind of. The module pages carry the properties.
· module —
Identity · module
Who a person is, and how they prefer to be reached. Owns the person, never their standing in any organization.
Classes (2) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Profileprofiledraftdefinition onlyOne person who can sign in, and how to reach them.
One person who can sign in, and how to reach them. Keyed to the authentication account it mirrors. Deleting an account leaves a tombstone: the visible details are anonymised and the row stays, so everything the person did still resolves to a name. Most records in the exchange name a profile as their actor.
Properties (6)
| Property | Type | Definition |
|---|---|---|
email required · email | string | The address the person signs in with. |
display_name · display_name | string | What the person asked to be called. |
avatar_url · avatar_url | string | Where their picture is. |
timezone · timezone | string | The zone their times are shown in. |
email_verified_at · email_verified_at | date | When the address was verified. Not decoration: adding to a cart requires it. |
deleted_at · deleted_at | date | When the account was deleted, if it was. The row stays as a tombstone. |
Relationships (10)
- Profile has User Settings · one-to-one · inverse: User Settings belongs to Profile definition only
- Profile holds Organization Member · one-to-many · inverse: Organization Member is held by Profile definition only
- Profile sends Organization Invite · one-to-many · inverse: Organization Invite was sent by Profile definition only
- Profile receives or requests Organization Invite · one-to-many · inverse: Organization Invite is addressed to Profile definition only
- Profile is the service account for API Key · one-to-many · inverse: API Key acts as Profile definition only
- Profile consents as OAuth Grant · one-to-many · inverse: OAuth Grant was consented by Profile definition only
- Profile acts in Notification · one-to-many · inverse: Notification was caused by Profile definition only
- Profile receives Notification Recipient · one-to-many · inverse: Notification Recipient is addressed to Profile definition only
- Profile performs Activity Event · one-to-many · inverse: Activity Event was performed by Profile definition only
- Profile converts Onboarding Submission · one-to-many · inverse: Onboarding Submission was converted by Profile definition only
User Settingsuser-settingsdraftdefinition onlyA person's private preferences, including which channels they want to be notified on.
A person's private preferences, including which channels they want to be notified on. Kept apart from the profile because a profile is readable widely for attribution and these are the owner's alone. One per person, created with the profile.
Properties (1)
| Property | Type | Definition |
|---|---|---|
notification_preferences · notification_preferences | string | Which channels the person wants each kind of notification on, as a document. |
Relationships (1)
- User Settings belongs to Profile · one-to-one · forward: Profile has User Settings definition only
Organizations · module
Who takes part in the exchange, where they operate, and who may join or partner with them. Owns the eligibility rule itself, not its enforcement.
Classes (8) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Organization Domainorganization-domaindraftdefinition onlyAn email domain an organization has verified, letting a new signup with that domain join without an invite when the organization's join policy allows it.
An email domain an organization has verified, letting a new signup with that domain join without an invite when the organization's join policy allows it.
Properties (3)
| Property | Type | Definition |
|---|---|---|
domain required · domain | string | The email domain, without the at sign. |
is_verified · is_verified | boolean | Whether the organization has proven it controls the domain. |
is_primary · is_primary | boolean | Whether this is the organization's main domain. |
Relationships (1)
- Organization Domain is verified by Organization · one-to-many · forward: Organization verifies Organization Domain definition only
Organization Followerorganization-followerdraftdefinition onlyA one-way follow of one organization by another.
A one-way follow of one organization by another. No acceptance step, unlike a partnership; the followers of an organization hear about what it posts.
Properties (0)
No properties declared.
Relationships (2)
- Organization Follower is a follow by Organization · one-to-many · forward: Organization follows through Organization Follower definition only
- Organization Follower is a follow of Organization · one-to-many · forward: Organization is followed through Organization Follower definition only
Organization Inviteorganization-invitedraftdefinition onlyAn invitation to join an organization, or a request to join one.
An invitation to join an organization, or a request to join one. Both are the same record, told apart by who started it: an invite names the admin who sent it, a request does not.
Properties (6)
| Property | Type | Definition |
|---|---|---|
email · email | string | The address the invite was sent to, or the requester's. |
role · role | string | The role the person would hold. |
status · status | string | Whether it is pending, accepted, declined or expired. |
request_type required · request_type | string | Whether an admin invited the person or the person asked to join. |
expires_at · expires_at | date | When an unanswered invite lapses. |
accepted_at · accepted_at | date | When it was accepted, if it was. |
Relationships (3)
- Organization Invite concerns Organization · one-to-many · forward: Organization issues or receives Organization Invite definition only
- Organization Invite was sent by Profile · one-to-many · forward: Profile sends Organization Invite definition only
- Organization Invite is addressed to Profile · one-to-many · forward: Profile receives or requests Organization Invite definition only
Organization Locationorganization-locationdraftdefinition onlyA pickup or warehouse address belonging to an organization, with the dock, hours and contact a carrier needs.
A pickup or warehouse address belonging to an organization, with the dock, hours and contact a carrier needs. A resource names the location it is collected from.
Properties (10)
| Property | Type | Definition |
|---|---|---|
name · name | string | What the organization calls the site. |
is_primary · is_primary | boolean | Whether this is the organization's main site. Two sites can both claim it; nothing stops them. |
full_address · full_address | string | The whole address as one line. |
city · city | string | The city. |
state_province · state_province | string | The state or province. |
requires_loading_dock · requires_loading_dock | boolean | Whether a truck needs a dock here. |
liftgate_compatible · liftgate_compatible | boolean | Whether a liftgate truck can work here. |
pickup_days · pickup_days | string | The days of the week goods can be collected. |
pickup_contact_name · pickup_contact_name | string | Who a driver asks for. |
pickup_instructions · pickup_instructions | string | What a driver needs to know on arrival. |
Relationships (2)
- Organization Location belongs to Organization · one-to-many · forward: Organization operates from Organization Location definition only
- Organization Location is the pickup point for Resource · one-to-many · inverse: Resource is collected from Organization Location definition only
Organization Memberorganization-memberdraftdefinition onlyOne person's standing in one organization, and the role they hold there.
One person's standing in one organization, and the role they hold there. A membership grants nothing on its own: every check re-reads it, so a stale role never keeps access alive.
Properties (1)
| Property | Type | Definition |
|---|---|---|
role required · role | string | What the person may do in the organization, for example admin or member. |
Relationships (2)
- Organization Member is held by Profile · one-to-many · forward: Profile holds Organization Member definition only
- Organization Member is a member of Organization · one-to-many · forward: Organization has Organization Member definition only
Organization Partnerorganization-partnerdraftdefinition onlyA two-way connection between two organizations, held as one record rather than one per side.
A two-way connection between two organizations, held as one record rather than one per side. Requested by one, then accepted or rejected by the other. Partners can see each other's partner-only listings.
Properties (4)
| Property | Type | Definition |
|---|---|---|
status required · status | string | Pending, accepted or rejected. |
requested_at · requested_at | date | When the connection was asked for. |
responded_at · responded_at | date | When the other side answered. |
notes · notes | string | Anything either side wrote about the connection. |
Relationships (1)
- Organization Partner connects Organization · one-to-many · forward: Organization connects through Organization Partner definition only
Organization Restrictionorganization-restrictiondraftdefinition onlyA rule stating who an organization may trade with, one per kind.
A rule stating who an organization may trade with, one per kind. Declared, never enforced: nothing server-side checks it, and one kind is checked in the browser to grey out a button.
Properties (2)
| Property | Type | Definition |
|---|---|---|
restriction_type required · restriction_type | enum restriction-type | Which kind of rule this is. |
value required · value | string | The rule's value, shaped by its kind: a list of states, a radius, a quantity limit, an orientation. |
Relationships (1)
- Organization Restriction restricts trade with Organization · one-to-many · forward: Organization declares Organization Restriction definition only
Organizationorganizationdraftdefinition onlyA participant in the exchange: an NPO that receives, or a supplier that gives.
A participant in the exchange: an NPO that receives, or a supplier that gives. Everything else hangs off one. Retirement is a soft delete that keeps the organization's history and cancels what it had in flight, except offers that already reached checkout.
Properties (12)
| Property | Type | Definition |
|---|---|---|
name required · name | string | The organization's name. |
slug · slug | string | The short name used in links and order numbers. |
organization_type required · organization_type | string | Which side of the exchange the organization is on: an NPO or a supplier. |
description · description | string | How the organization describes itself. |
join_policy required · join_policy | enum join-policy | How a person may join. Only decides whether a matching email domain skips the request step. |
verification_status · verification_status | string | Whether GIK has verified the organization as the nonprofit it says it is. |
is_faith_based · is_faith_based | boolean | Whether the organization identifies as faith-based. Unclassified is a third answer, not a no. |
phone · phone | string | A contact number. |
email · email | string | A contact address. |
followers_count · followers_count | number | How many organizations follow this one. Written by the app, not kept in step by the database. |
connections_count · connections_count | number | How many partnerships this organization has. Written by the app, not kept in step by the database. |
deleted_at · deleted_at | date | When the organization was retired, if it was. |
Relationships (24)
- Organization has Organization Member · one-to-many · inverse: Organization Member is a member of Organization definition only
- Organization issues or receives Organization Invite · one-to-many · inverse: Organization Invite concerns Organization definition only
- Organization verifies Organization Domain · one-to-many · inverse: Organization Domain is verified by Organization definition only
- Organization operates from Organization Location · one-to-many · inverse: Organization Location belongs to Organization definition only
- Organization connects through Organization Partner · one-to-many · inverse: Organization Partner connects Organization definition only
- Organization follows through Organization Follower · one-to-many · inverse: Organization Follower is a follow by Organization definition only
- Organization is followed through Organization Follower · one-to-many · inverse: Organization Follower is a follow of Organization definition only
- Organization declares Organization Restriction · one-to-many · inverse: Organization Restriction restricts trade with Organization definition only
- Organization lists Resource · one-to-many · inverse: Resource is listed by Organization definition only
- Organization uploads Resource Import · one-to-many · inverse: Resource Import was uploaded by Organization definition only
- Organization raises Need · one-to-many · inverse: Need is raised by Organization definition only
- Organization is assigned Need · one-to-many · inverse: Need is assigned to Organization definition only
- Organization triggers Match Run · one-to-many · inverse: Match Run was triggered for Organization definition only
- Organization makes Offer · one-to-many · inverse: Offer is made by Organization definition only
- Organization receives Offer · one-to-many · inverse: Offer is made to Organization definition only
- Organization keeps Cart · one-to-one · inverse: Cart belongs to Organization definition only
- Organization receives Order · one-to-many · inverse: Order is received by Organization definition only
- Organization supplies Order · one-to-many · inverse: Order is supplied by Organization definition only
- Organization holds API Key · one-to-many · inverse: API Key belongs to Organization definition only
- Organization consents to OAuth Grant · one-to-many · inverse: OAuth Grant acts for Organization definition only
- Organization subscribes Webhook Subscription · one-to-many · inverse: Webhook Subscription belongs to Organization definition only
- Organization is the context of Notification · one-to-many · inverse: Notification happened within Organization definition only
- Organization logs Activity Event · one-to-many · inverse: Activity Event happened in Organization definition only
- Organization is branded by File · one-to-many · forward: File is the logo or banner of Organization definition only
Resources · module
What a supplier can give and on what terms. Owns the listing, not its fate: whether it is matched, offered or shipped is recorded elsewhere.
Classes (5) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Resource Imageresource-imagedraftdefinition onlyA picture of a resource, shown in its gallery in order.
A picture of a resource, shown in its gallery in order. One may be marked the cover, though nothing stops two from claiming it.
Properties (2)
| Property | Type | Definition |
|---|---|---|
display_order · display_order | number | Where it sits in the gallery. |
is_cover · is_cover | boolean | Whether it is the picture shown first. |
Relationships (2)
Resource Import Rowresource-import-rowdraftdefinition onlyOne line of an uploaded file, kept exactly as it was submitted, and what became of it.
One line of an uploaded file, kept exactly as it was submitted, and what became of it. The resource it produced can be deleted later without disturbing this record of what was uploaded.
Properties (3)
| Property | Type | Definition |
|---|---|---|
row_number required · row_number | number | Its line in the file. |
status required · status | enum import-row-status | Whether it is pending, became a resource, or failed. |
error_message · error_message | string | Why it failed, if it did. |
Relationships (2)
- Resource Import Row is a line of Resource Import · one-to-many · forward: Resource Import contains Resource Import Row definition only
- Resource Import Row produced Resource · one-to-one · inverse: Resource came from Resource Import Row definition only
Resource Importresource-importdraftdefinition onlyOne bulk upload of resources, with its own tally of what succeeded and what failed, and the column mapping frozen exactly as the uploader chose it, so the upload stays readable after the resource shape changes.
One bulk upload of resources, with its own tally of what succeeded and what failed, and the column mapping frozen exactly as the uploader chose it, so the upload stays readable after the resource shape changes.
Properties (5)
| Property | Type | Definition |
|---|---|---|
status required · status | enum import-status | Where the upload stands. |
total_rows · total_rows | number | How many rows the file held. |
success_count · success_count | number | How many became resources. |
error_count · error_count | number | How many were rejected. |
completed_at · completed_at | date | When processing finished. |
Relationships (3)
- Resource Import was uploaded by Organization · one-to-many · forward: Organization uploads Resource Import definition only
- Resource Import reads File · one-to-one · forward: File is read by Resource Import definition only
- Resource Import contains Resource Import Row · one-to-many · inverse: Resource Import Row is a line of Resource Import definition only
Resource Restrictionresource-restrictiondraftdefinition onlyA rule stating who may claim one resource, one per kind.
A rule stating who may claim one resource, one per kind. It overrides the organization's rule of the same kind; a resource with none inherits the organization's. Declared, never enforced.
Properties (2)
| Property | Type | Definition |
|---|---|---|
restriction_type required · restriction_type | enum restriction-type | Which kind of rule this is. |
value required · value | string | The rule's value, shaped by its kind. |
Relationships (1)
- Resource Restriction restricts claims on Resource · one-to-many · forward: Resource declares Resource Restriction definition only
Resourceresourcedraftdefinition onlyWhat a supplier is offering to give, and on what terms.
What a supplier is offering to give, and on what terms. A listing, not a live inventory count: the quantity is a ledger that orders reserve against. Its status says whether it can be matched and ordered at all; its visibility says who can see it while it is live, and the two are independent. Since 1.1 a resource can also be born inside an offer, as an unlisted row its owner never sees as inventory; offers and orders resolve it normally, and that is the only place it appears.
Properties (19)
| Property | Type | Definition |
|---|---|---|
title required · title | string | What the supplier calls it. |
description · description | string | What it is, in the supplier's words. |
category · category | string | Which of GIK's categories it falls under. |
quantity · quantity | number | How much is on offer. |
ordered_quantity · ordered_quantity | number | How much orders have already reserved. Kept inside the quantity by the database once a quantity is set. |
unit_type · unit_type | string | What the quantity counts: pallets, cases, pieces. |
fair_market_value · fair_market_value | currency | What the goods are worth, for the receiving organization's reporting. |
status required · status | enum resource-status | Whether the listing is being drafted, is live, or is closed. |
visibility required · visibility | enum resource-visibility | Who can see a live listing. |
condition · condition | string | The state of the goods: new, used, and so on. |
is_splittable · is_splittable | boolean | Whether part of the quantity can go to one recipient and the rest elsewhere. |
available_from · available_from | date | When the goods can first be collected. |
available_until · available_until | date | When they must be gone by. |
delivery_methods · delivery_methods | string | How the goods can move: self pickup, the supplier's own freight, or an arranged carrier. |
is_hazmat · is_hazmat | boolean | Whether the goods are hazardous materials. |
hazmat_materials · hazmat_materials | string | What the hazard is, in words, so a yes needs no follow-up call. |
pallet_count · pallet_count | number | How many pallets the goods occupy. |
transport_fmv · transport_fmv | currency | What moving the goods is worth, where the supplier pays. |
unlisted required · unlisted | boolean | Created inside an offer rather than from the supplier's inventory. Stays out of every supplier-facing surface for the life of the row, and the database refuses to change that. |
Relationships (12)
- Resource is listed by Organization · one-to-many · forward: Organization lists Resource definition only
- Resource is collected from Organization Location · one-to-many · forward: Organization Location is the pickup point for Resource definition only
- Resource declares Resource Restriction · one-to-many · inverse: Resource Restriction restricts claims on Resource definition only
- Resource is pictured by Resource Image · one-to-many · inverse: Resource Image pictures Resource definition only
- Resource attaches File · one-to-many · forward: File is the safety data sheet for Resource definition only
- Resource came from Resource Import Row · one-to-one · forward: Resource Import Row produced Resource definition only
- Resource was received as Order Line Item · one-to-many · forward: Order Line Item is re-listed as Resource definition only
- Resource fulfils Need Line Item · one-to-many · inverse: Need Line Item is fulfilled by Resource definition only
- Resource is scored in Match Score · one-to-many · inverse: Match Score scores Resource definition only
- Resource is offered in Offer Line Item · one-to-many · inverse: Offer Line Item offers Resource definition only
- Resource is staged as Cart Item · one-to-many · inverse: Cart Item stages Resource definition only
- Resource is moved as Order Line Item · one-to-many · inverse: Order Line Item moves Resource definition only
Needs · module
What an NPO is asking for, and how urgently. Owns the request, not the event behind it.
Classes (3) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Need Imageneed-imagedraftdefinition onlyA picture attached to a need, shown in its gallery in order.
A picture attached to a need, shown in its gallery in order.
Properties (2)
| Property | Type | Definition |
|---|---|---|
display_order · display_order | number | Where it sits in the gallery. |
is_cover · is_cover | boolean | Whether it is the picture shown first. |
Relationships (2)
Need Line Itemneed-line-itemdraftdefinition onlyOne line of a need: how much of what, in which unit, and how much of it has been fulfilled so far.
One line of a need: how much of what, in which unit, and how much of it has been fulfilled so far. Offers and match scores target the line, not the need, because that is where quantity and unit live.
Properties (6)
| Property | Type | Definition |
|---|---|---|
item_name required · item_name | string | What is wanted. |
description · description | string | Any detail about the item. |
category · category | string | Which category the item falls under. |
quantity_needed required · quantity_needed | number | How much is wanted. |
quantity_fulfilled · quantity_fulfilled | number | How much has arrived so far. |
unit_type · unit_type | string | What the quantity counts. |
Relationships (4)
- Need Line Item is a line of Need · one-to-many · forward: Need asks for Need Line Item definition only
- Need Line Item is fulfilled by Resource · one-to-many · forward: Resource fulfils Need Line Item definition only
- Need Line Item is scored in Match Score · one-to-many · inverse: Match Score scores Need Line Item definition only
- Need Line Item is answered by Offer Line Item · one-to-many · inverse: Offer Line Item answers Need Line Item definition only
Needneeddraftdefinition onlyA standing request from an NPO: what it is asking for, and how urgently.
A standing request from an NPO: what it is asking for, and how urgently. Not a line item; the lines are revised without disturbing the request itself. A need may name the disaster that makes it urgent and says where the goods should go.
Properties (11)
| Property | Type | Definition |
|---|---|---|
title required · title | string | What the NPO calls the request. |
description · description | string | The situation, in the NPO's words. |
category · category | string | Which of GIK's categories the request falls under. |
status required · status | enum need-status | Where the request stands. |
priority · priority | enum need-priority | How urgent it is. |
demand_status · demand_status | enum demand-status | Whether the demand is confirmed or a preparedness estimate. |
deadline · deadline | date | When the goods are needed by. |
city · city | string | Where the goods should go. |
state_province · state_province | string | The state or province they should go to. |
delivery_instructions · delivery_instructions | string | What a driver needs to know at the destination. |
contact_name · contact_name | string | Who to reach about the request. |
Relationships (8)
- Need is raised by Organization · one-to-many · forward: Organization raises Need definition only
- Need is assigned to Organization · one-to-many · forward: Organization is assigned Need definition only
- Need is urgent because of Disaster · one-to-many · forward: Disaster makes urgent Need definition only
- Need asks for Need Line Item · one-to-many · inverse: Need Line Item is a line of Need definition only
- Need is pictured by Need Image · one-to-many · inverse: Need Image pictures Need definition only
- Need attracts Offer · one-to-many · inverse: Offer answers Need definition only
- Need is served by Cart Item · one-to-many · inverse: Cart Item is for Need definition only
- Need is served by Order Line Item · one-to-many · inverse: Order Line Item serves Need definition only
Matching · module
Which resources answer which needs, and how well. Owns no commitment: a high score is not an offer, and nothing here moves goods.
Classes (2) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Match Runmatch-rundraftdefinition onlyOne attempt to score the candidates against a need or a resource.
One attempt to score the candidates against a need or a resource. Never a commitment, and never a score itself: it records why the attempt exists and how it went. Enqueued by the database when a need or resource changes, drained by a worker the app invokes, and since 1.1 by a five-minute backstop for any run the app's call never reached.
Properties (7)
| Property | Type | Definition |
|---|---|---|
trigger_type required · trigger_type | enum match-trigger-type | Why the run exists. |
status required · status | enum match-run-status | Where the run stands. |
scores_count · scores_count | number | How many pairs the run kept. |
model_version · model_version | string | Which scoring model produced them. |
error_message · error_message | string | Why the run failed, if it did. |
started_at · started_at | date | When the worker picked it up. |
completed_at · completed_at | date | When it finished. |
Relationships (3)
- Match Run was triggered for Organization · one-to-many · forward: Organization triggers Match Run definition only
- Match Run is recorded by Activity Event · one-to-one · forward: Activity Event records the enqueuing of Match Run definition only
- Match Run produces Match Score · one-to-many · inverse: Match Score was produced by Match Run definition only
Match Scorematch-scoredraftdefinition onlyHow well one resource answers one line of a need, as judged by one run.
How well one resource answers one line of a need, as judged by one run. Two scores do two jobs: the search score decides whether the pair is kept at all, the sort score orders the pairs that were kept. Append-only, so the current answer for a pair is the newest row. A high score is not eligibility, and eligibility is not a commitment.
Properties (5)
| Property | Type | Definition |
|---|---|---|
search_score · search_score | number | Semantic and category fit. Gates whether the pair is kept; the grade is read off it. |
sort_score · sort_score | number | Quantity, distance and semantic fit together. Orders the kept pairs. |
grade · grade | enum match-grade | The letter grade, read off the search score alone. |
explanation · explanation | string | Why the pair scored as it did, in words. |
is_eligible · is_eligible | boolean | Whether this is still the current row for its pair; a later run flips it off. Says nothing about any restriction. |
Relationships (3)
- Match Score was produced by Match Run · one-to-many · forward: Match Run produces Match Score definition only
- Match Score scores Need Line Item · one-to-many · forward: Need Line Item is scored in Match Score definition only
- Match Score scores Resource · one-to-many · forward: Resource is scored in Match Score definition only
Offers · module
One supplier's commitment against one need. Owns the commitment, not the delivery.
Classes (2) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Offer Line Itemoffer-line-itemdraftdefinition onlyOne line of an offer: how much of which resource answers which line of the need.
One line of an offer: how much of which resource answers which line of the need. The resource is a catalog listing, one born in this offer, or, for an off-platform supplier, only a name.
Properties (2)
| Property | Type | Definition |
|---|---|---|
quantity required · quantity | number | How much of the resource is offered. |
manual_item_name · manual_item_name | string | What is offered, when there is no listing to point at. |
Relationships (3)
- Offer Line Item is a line of Offer · one-to-many · forward: Offer commits Offer Line Item definition only
- Offer Line Item offers Resource · one-to-many · forward: Resource is offered in Offer Line Item definition only
- Offer Line Item answers Need Line Item · one-to-many · forward: Need Line Item is answered by Offer Line Item definition only
Offerofferdraftdefinition onlyOne supplier's commitment against one need.
One supplier's commitment against one need. Not a reservation, and not yet an order: accepting an offer stages its goods into the NPO's cart, and checkout later creates the order and writes it back here as provenance. An offer may carry the resource it offers, described inline. A manual offer records an off-platform supplier's generosity.
Properties (7)
| Property | Type | Definition |
|---|---|---|
status required · status | enum offer-status | Where the offer stands. |
source required · source | enum offer-source | Whether it came through the platform or was recorded by hand. |
message · message | string | What the supplier said with the offer. |
expires_at · expires_at | date | When the offer lapses. Stored, and nothing yet acts on it. |
manual_supplier_name · manual_supplier_name | string | Who the off-platform supplier is, when the offer was recorded by hand. |
manual_supplier_email · manual_supplier_email | string | How to reach the off-platform supplier. |
closure_reason · closure_reason | string | Why the offer was declined or cancelled. |
Relationships (6)
- Offer is made by Organization · one-to-many · forward: Organization makes Offer definition only
- Offer is made to Organization · one-to-many · forward: Organization receives Offer definition only
- Offer answers Need · one-to-many · forward: Need attracts Offer definition only
- Offer commits Offer Line Item · one-to-many · inverse: Offer Line Item is a line of Offer definition only
- Offer led to Order · one-to-many · forward: Order fulfils Offer definition only
- Offer stages Cart Item · one-to-many · inverse: Cart Item was staged from Offer definition only
Orders · module
Promises becoming goods in motion, whichever path they took. Owns the logistics of moving resources, not the decision to trade.
Classes (5) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Cart Itemcart-itemdraftdefinition onlySomething staged for checkout: a quantity of a listing, added straight from the marketplace or from an offer that was accepted, optionally against a need.
Something staged for checkout: a quantity of a listing, added straight from the marketplace or from an offer that was accepted, optionally against a need. Adding one requires a verified email, and the database checks that, not the screen.
Properties (1)
| Property | Type | Definition |
|---|---|---|
quantity required · quantity | number | How much is staged. |
Relationships (4)
- Cart Item is staged in Cart · one-to-many · forward: Cart holds Cart Item definition only
- Cart Item stages Resource · one-to-many · forward: Resource is staged as Cart Item definition only
- Cart Item was staged from Offer · one-to-many · forward: Offer stages Cart Item definition only
- Cart Item is for Need · one-to-many · forward: Need is served by Cart Item definition only
Cartcartdraftdefinition onlyAn organization's staging area before checkout.
An organization's staging area before checkout. One per organization, shared by every member, not one per person. It carries no status of its own: it is nothing but its items until checkout turns them into an order.
Properties (0)
No properties declared.
Relationships (2)
- Cart belongs to Organization · one-to-one · forward: Organization keeps Cart definition only
- Cart holds Cart Item · one-to-many · inverse: Cart Item is staged in Cart definition only
Order Line Itemorder-line-itemdraftdefinition onlyOne resource and quantity within an order, with the pickup logistics answers that override the listing's where the supplier said so.
One resource and quantity within an order, with the pickup logistics answers that override the listing's where the supplier said so. Only the supplier may write those overrides, and the database holds that.
Properties (6)
| Property | Type | Definition |
|---|---|---|
quantity required · quantity | number | How much of the resource is in the order. |
transport_payment_type · transport_payment_type | string | Who pays for transport, if it differs from the listing. |
packaging_type · packaging_type | string | How the goods are packed, if it differs. |
transport_fmv · transport_fmv | currency | What moving these goods is worth, if it differs. |
origin_dock_available · origin_dock_available | boolean | Whether there is a dock at the pickup, if it differs. |
hazmat_materials · hazmat_materials | string | The hazard, in words, if it differs. |
Relationships (4)
- Order Line Item is re-listed as Resource · one-to-many · inverse: Resource was received as Order Line Item definition only
- Order Line Item is a line of Order · one-to-many · forward: Order moves Order Line Item definition only
- Order Line Item moves Resource · one-to-many · forward: Resource is moved as Order Line Item definition only
- Order Line Item serves Need · one-to-many · forward: Need is served by Order Line Item definition only
Order Transport Detailsorder-transport-detailsdraftdefinition onlyWhere an order is going, and how it gets collected: the destination's dock and hours, contacts at both ends, what the vehicle needs.
Where an order is going, and how it gets collected: the destination's dock and hours, contacts at both ends, what the vehicle needs. One per order. Both organizations may write every field; which side fills which is a convention the app keeps, not a rule the database enforces.
Properties (11)
| Property | Type | Definition |
|---|---|---|
vehicle_type · vehicle_type | string | What kind of vehicle the pickup needs. |
has_liftgate · has_liftgate | boolean | Whether the vehicle needs a liftgate. |
has_pallet_jack · has_pallet_jack | boolean | Whether a pallet jack is on board. |
destination_dock_available · destination_dock_available | boolean | Whether the destination has a dock. |
appointment_required · appointment_required | boolean | Whether the destination needs an appointment. |
requester_contact_name · requester_contact_name | string | Who at the receiving organization to call. |
destination_contact_name · destination_contact_name | string | Who meets the driver. |
end_recipient_address · end_recipient_address | string | Where the goods finally go, if not the destination. |
estimated_people_served · estimated_people_served | number | How many people the delivery is expected to help. |
preferred_pickup_date · preferred_pickup_date | date | When the receiving organization would like collection. |
driver_gate_instructions · driver_gate_instructions | string | What the driver needs at the gate. |
Relationships (1)
- Order Transport Details describes the delivery of Order · one-to-one · forward: Order is delivered per Order Transport Details definition only
Orderorderdraftdefinition onlyThe record two organizations share once goods are moving: one receiving, one giving.
The record two organizations share once goods are moving: one receiving, one giving. Numbered per receiving organization, and moved through its statuses as the supplier commits, schedules the pickup and delivers, or rejects, or either side cancels.
Properties (9)
| Property | Type | Definition |
|---|---|---|
display_id · display_id | string | The order number, minted per receiving organization. |
status required · status | enum order-status | Where the order stands. |
delivery_method · delivery_method | string | How the goods move: self pickup, the supplier's own freight, or an arranged carrier. |
scheduled_pickup_date · scheduled_pickup_date | date | When collection is booked. |
supplier_pickup_number · supplier_pickup_number | string | The supplier's own reference for the pickup. |
shipping_city · shipping_city | string | Where the goods are going. |
shipping_state · shipping_state | string | The destination state. |
notes · notes | string | Anything either side added. |
closure_reason · closure_reason | string | Why the order was rejected or cancelled. |
Relationships (5)
- Order fulfils Offer · one-to-many · inverse: Offer led to Order definition only
- Order is received by Organization · one-to-many · forward: Organization receives Order definition only
- Order is supplied by Organization · one-to-many · forward: Organization supplies Order definition only
- Order moves Order Line Item · one-to-many · inverse: Order Line Item is a line of Order definition only
- Order is delivered per Order Transport Details · one-to-one · inverse: Order Transport Details describes the delivery of Order definition only
Disasters · module
The events that make a need urgent. Owns urgency context, never eligibility or matching.
Classes (2) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Disaster Sync Rundisaster-sync-rundraftdefinition onlyOne run of the FEMA import: when it ran, what set it off, and what it found or why it failed.
One run of the FEMA import: when it ran, what set it off, and what it found or why it failed.
Properties (5)
| Property | Type | Definition |
|---|---|---|
status · status | string | How the run ended. |
trigger_source · trigger_source | string | Whether the schedule or a person started it. |
started_at · started_at | date | When it began. |
finished_at · finished_at | date | When it ended. |
error · error | string | Why it failed, if it did. |
Relationships (1)
- Disaster Sync Run refreshes Disaster · many-to-many · inverse: Disaster is kept current by Disaster Sync Run definition only
Disasterdisasterdraftdefinition onlyAn event that makes needs urgent: a FEMA declaration kept current by the sync, or one entered by hand.
An event that makes needs urgent: a FEMA declaration kept current by the sync, or one entered by hand. It tags urgency and nothing more; a disaster never gates eligibility and never moves a score.
Properties (10)
| Property | Type | Definition |
|---|---|---|
name required · name | string | What the event is called. |
type · type | enum disaster-type | What kind of event it is. |
status · status | enum disaster-status | Whether it still tags new needs. |
source · source | enum disaster-source | Where the record came from. |
region · region | string | Where it happened. |
fema_disaster_number · fema_disaster_number | number | FEMA's number for the declaration. |
declared_at · declared_at | date | When it was declared. |
incident_began_at · incident_began_at | date | When the event began. |
incident_ended_at · incident_ended_at | date | When it ended, if it has. |
active_until · active_until | date | When the record stops tagging new needs. |
Relationships (2)
- Disaster makes urgent Need · one-to-many · inverse: Need is urgent because of Disaster definition only
- Disaster is kept current by Disaster Sync Run · many-to-many · forward: Disaster Sync Run refreshes Disaster definition only
Partner access · module
How an external system proves who it is and what it may call. Owns authentication and scope, not business eligibility.
Classes (4) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
API Keyapi-keydraftdefinition onlyA partner's credential for the Partner API, scoped to one organization.
A partner's credential for the Partner API, scoped to one organization. Shown once when created and stored only as a hash, so it can be checked but never read back; revocable at any time. An OAuth connection ends in one of these too.
Properties (6)
| Property | Type | Definition |
|---|---|---|
label · label | string | What the partner calls the key. |
key_prefix · key_prefix | string | The visible start of the key, for telling keys apart. |
scopes · scopes | string | What the key may call. |
last_used_at · last_used_at | date | When it was last presented. |
expires_at · expires_at | date | When it stops working. |
revoked_at · revoked_at | date | When it was revoked, if it was. |
Relationships (6)
- API Key belongs to Organization · one-to-many · forward: Organization holds API Key definition only
- API Key acts as Profile · one-to-many · forward: Profile is the service account for API Key definition only
- API Key is audited by Partner API Audit · one-to-many · inverse: Partner API Audit audits API Key definition only
- API Key was obtained by OAuth Client · one-to-many · forward: OAuth Client obtains API Key definition only
- API Key is presented through OAuth Grant · one-to-many · inverse: OAuth Grant resolves to API Key definition only
- API Key registers Webhook Subscription · one-to-many · inverse: Webhook Subscription was registered under API Key definition only
OAuth Clientoauth-clientdraftdefinition onlyAn external application registered to connect, which registers itself the first time it is added.
An external application registered to connect, which registers itself the first time it is added. Its secret is stored only as a hash.
Properties (5)
| Property | Type | Definition |
|---|---|---|
client_id required · client_id | string | The application's public identifier. |
client_name · client_name | string | What the application calls itself. |
redirect_uris · redirect_uris | string | Where the application may be sent back to after consent. |
grant_types · grant_types | string | Which OAuth flows it may use. |
revoked_at · revoked_at | date | When the registration was revoked, if it was. |
Relationships (2)
- OAuth Client obtains API Key · one-to-many · inverse: API Key was obtained by OAuth Client definition only
- OAuth Client holds OAuth Grant · one-to-many · inverse: OAuth Grant was granted to OAuth Client definition only
OAuth Grantoauth-grantdraftdefinition onlyThe consent chain letting an external application act for an organization: the code issued when an admin consents, the access token it is exchanged for, and the refresh token that renews it.
The consent chain letting an external application act for an organization: the code issued when an admin consents, the access token it is exchanged for, and the refresh token that renews it. Every step is scoped, expires, and is stored as a hash. Three tables, one idea.
Properties (3)
| Property | Type | Definition |
|---|---|---|
scopes · scopes | string | What the grant allows. |
expires_at · expires_at | date | When this step of the chain lapses. |
revoked_at · revoked_at | date | When it was revoked, if it was. |
Relationships (4)
- OAuth Grant was granted to OAuth Client · one-to-many · forward: OAuth Client holds OAuth Grant definition only
- OAuth Grant acts for Organization · one-to-many · forward: Organization consents to OAuth Grant definition only
- OAuth Grant was consented by Profile · one-to-many · forward: Profile consents as OAuth Grant definition only
- OAuth Grant resolves to API Key · one-to-many · forward: API Key is presented through OAuth Grant definition only
Partner API Auditpartner-api-auditdraftdefinition onlyA record of one Partner API request: which key, which route, what came back.
A record of one Partner API request: which key, which route, what came back. Also what the rate limit counts.
Properties (5)
| Property | Type | Definition |
|---|---|---|
method · method | string | The HTTP method. |
path · path | string | The path called. |
route · route | string | The route it matched. |
status_code · status_code | number | What the API answered. |
scope · scope | string | The scope the call needed. |
Relationships (1)
- Partner API Audit audits API Key · one-to-many · forward: API Key is audited by Partner API Audit definition only
Webhooks · module
What GIK told an external system, and whether the message landed. Owns delivery and retry, not the underlying event.
Classes (2) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Webhook Deliverywebhook-deliverydraftdefinition onlyOne attempt to deliver one event to one address, with its own retry state.
One attempt to deliver one event to one address, with its own retry state. Failing to land never undoes whatever happened on the GIK side.
Properties (6)
| Property | Type | Definition |
|---|---|---|
event · event | string | Which event this carries. |
status · status | string | Where the delivery stands. |
attempt · attempt | number | How many times it has been tried. |
next_attempt_at · next_attempt_at | date | When it will be tried again. |
last_response_status · last_response_status | number | What the address answered last time. |
last_error · last_error | string | Why the last attempt failed. |
Relationships (1)
- Webhook Delivery is an attempt for Webhook Subscription · one-to-many · forward: Webhook Subscription receives Webhook Delivery definition only
Webhook Subscriptionwebhook-subscriptiondraftdefinition onlyAn external address, and the events it asked to be told about, registered under a partner's key.
An external address, and the events it asked to be told about, registered under a partner's key. Can be disabled, with the reason kept, when deliveries keep failing.
Properties (5)
| Property | Type | Definition |
|---|---|---|
url required · url | string | Where events are sent. |
events · events | string | Which events the address asked for. |
enabled · enabled | boolean | Whether deliveries are being attempted. |
disabled_at · disabled_at | date | When it was switched off, if it was. |
disabled_reason · disabled_reason | string | Why it was switched off. |
Relationships (3)
- Webhook Subscription belongs to Organization · one-to-many · forward: Organization subscribes Webhook Subscription definition only
- Webhook Subscription was registered under API Key · one-to-many · forward: API Key registers Webhook Subscription definition only
- Webhook Subscription receives Webhook Delivery · one-to-many · inverse: Webhook Delivery is an attempt for Webhook Subscription definition only
Notifications · module
What a person is told, and through which channel. Owns the message and its read state, not a person's standing preference for how to be reached.
Classes (2) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Notification Recipientnotification-recipientdraftdefinition onlyOne person's copy of one notification on one channel, and whether they have read it.
One person's copy of one notification on one channel, and whether they have read it.
Properties (4)
| Property | Type | Definition |
|---|---|---|
channel required · channel | enum notification-channel | How this copy reached the person. |
is_read · is_read | boolean | Whether the person has read it. |
read_at · read_at | date | When they read it. |
sent_at · sent_at | date | When it was sent. |
Relationships (2)
- Notification Recipient is a copy of Notification · one-to-many · forward: Notification reaches Notification Recipient definition only
- Notification Recipient is addressed to Profile · one-to-many · forward: Profile receives Notification Recipient definition only
Notificationnotificationdraftdefinition onlySomething worth telling people about, written once however many people need to hear it: what happened, to what, and who did it.
Something worth telling people about, written once however many people need to hear it: what happened, to what, and who did it.
Properties (4)
| Property | Type | Definition |
|---|---|---|
notification_type required · notification_type | string | Which kind of event this announces. |
title · title | string | The headline. |
body · body | string | The message. |
target_type · target_type | string | What kind of thing it is about. |
Relationships (3)
- Notification happened within Organization · one-to-many · forward: Organization is the context of Notification definition only
- Notification was caused by Profile · one-to-many · forward: Profile acts in Notification definition only
- Notification reaches Notification Recipient · one-to-many · inverse: Notification Recipient is a copy of Notification definition only
Platform · module
Cross-cutting record-keeping the exchange leans on but does not itself decide: the activity ledger, shared files, pre-account submissions and settings.
Classes (4) · a solid line is a bound relationship, a dashed one is definition only; a hatched, dashed box is a sub-class, named after the class it is a kind of; the heavier outline is a class others descend from; open a class for its full definition
Activity Eventactivity-eventdraftdefinition onlyA tamper-evident record of something someone did: hashed, chained to the events before it, and shown to a declared audience.
A tamper-evident record of something someone did: hashed, chained to the events before it, and shown to a declared audience.
Properties (5)
| Property | Type | Definition |
|---|---|---|
action_type required · action_type | string | What was done. |
target_type · target_type | string | What kind of thing it was done to. |
visibility · visibility | enum activity-visibility | Who may see the event. |
provider · provider | enum activity-provider | Which ledger holds the record. |
event_hash · event_hash | string | The hash that makes the record tamper-evident. |
Relationships (3)
- Activity Event records the enqueuing of Match Run · one-to-one · inverse: Match Run is recorded by Activity Event definition only
- Activity Event happened in Organization · one-to-many · forward: Organization logs Activity Event definition only
- Activity Event was performed by Profile · one-to-many · forward: Profile performs Activity Event definition only
App Configapp-configdraftdefinition onlyA platform-wide setting: one key, one value.
Filefiledraftdefinition onlyThe details of an uploaded file: its name, type and size, and where it is stored.
The details of an uploaded file: its name, type and size, and where it is stored. Never the file itself.
Properties (3)
| Property | Type | Definition |
|---|---|---|
file_name · file_name | string | The name it was uploaded with. |
content_type · content_type | string | What kind of file it is. |
size · size | number | How large it is, in bytes. |
Relationships (5)
- File backs Resource Image · one-to-many · inverse: Resource Image is stored as File definition only
- File is the safety data sheet for Resource · one-to-many · inverse: Resource attaches File definition only
- File is read by Resource Import · one-to-one · inverse: Resource Import reads File definition only
- File backs Need Image · one-to-many · inverse: Need Image is stored as File definition only
- File is the logo or banner of Organization · one-to-many · inverse: Organization is branded by File definition only
Onboarding Submissiononboarding-submissiondraftdefinition onlyA need or a resource someone described before they had an account or an organization, kept until it is converted into the real thing or expires.
A need or a resource someone described before they had an account or an organization, kept until it is converted into the real thing or expires.
Properties (4)
| Property | Type | Definition |
|---|---|---|
submission_type required · submission_type | enum onboarding-submission-type | Whether it describes a resource or a need. |
status · status | enum onboarding-submission-status | Where the submission stands. |
email · email | string | How to reach the person who wrote it. |
expires_at · expires_at | date | When an unconverted submission is dropped. |
Relationships (1)
- Onboarding Submission was converted by Profile · one-to-many · forward: Profile converts Onboarding Submission definition only
Enums · 23 controlled vocabularies
An enum is a fixed list of choices a field can hold, like the statuses an order can be in. Each one below shows its values and which terms use it.
order-status Order Status
Where an order stands, from the NPO placing it, through the supplier committing and scheduling pickup, to delivery, or to rejection or cancellation.
Used by Order.status
disaster-type Disaster Type
The kind of event a disaster record describes.
Used by Disaster.type
disaster-status Disaster Status
Whether a disaster still makes needs urgent, or has been archived and no longer tags new needs.
Used by Disaster.status
disaster-source Disaster Source
Where a disaster record came from: the scheduled FEMA sync, or an administrator entering it by hand.
Used by Disaster.source
activity-visibility Activity Visibility
Who may see an activity event: everyone, the organization's members, the participants in the thing it records, or only its actor.
Used by Activity Event.visibility
activity-provider Activity Provider
Which ledger holds the event's tamper-evident record: the local hash chain, or Hedera.
Used by Activity Event.provider
match-run-status Match Run Status
The lifecycle of one scoring attempt, from enqueued to finished or failed.
Used by Match Run.status
match-trigger-type Match Trigger Type
Why a scoring attempt exists: a resource went live or changed, a need opened or changed, or someone asked for a rerun.
Used by Match Run.trigger_type
match-grade Match Grade
How well a resource answers a need line, read off the search score alone. A grade says nothing about quantity or distance, which order the results instead.
Used by Match Score.grade
need-status Need Status
Where a need stands: being drafted, open for offers, partly fulfilled and still wanting the remainder, fulfilled, or cancelled.
Used by Need.status
need-priority Need Priority
How urgently the NPO wants the request met.
Used by Need.priority
demand-status Demand Status
Whether the need is a confirmed demand or a preparedness estimate ahead of an event.
Used by Need.demand_status
notification-channel Notification Channel
How a notification reaches a person: inside the app, or by email.
Used by Notification Recipient.channel
offer-status Offer Status
Where an offer stands. It leaves pending by acceptance, decline or cancellation; expired is declared, and nothing yet sets it.
Used by Offer.status
offer-source Offer Source
Whether the offer came through the platform, or was recorded by hand on behalf of a supplier who never used it.
Used by Offer.source
onboarding-submission-type Onboarding Submission Type
Whether a pre-account submission describes a resource someone can give or a need someone has.
Used by Onboarding Submission.submission_type
onboarding-submission-status Onboarding Submission Status
Whether the submission is still being filled in, was completed, or was abandoned.
Used by Onboarding Submission.status
join-policy Join Policy
How a person may join an organization: freely, by matching a verified email domain, by approval of a request, or only by invitation.
Used by Organization.join_policy
restriction-type Restriction Type
The six kinds of eligibility rule an organization or a resource can declare about who may trade with it. Declared, not enforced: no server-side check applies any of them, and one is checked in the browser.
Used by Organization Restriction.restriction_type, Resource Restriction.restriction_type
resource-status Resource Status
Whether a listing is being drafted, is live and can be matched and ordered, or is closed.
Used by Resource.status
resource-visibility Resource Visibility
Who can see a live listing: only its own organization, its partners, or everyone.
Used by Resource.visibility
import-status Import Status
The lifecycle of a bulk upload, from processing through review to completion or failure.
Used by Resource Import.status
import-row-status Import Row Status
What happened to one uploaded row: still pending, turned into a resource, or rejected with an error.
Used by Resource Import Row.status
GIK · architecture
This is a map of how the GIK platform is put together: who uses it, which parts own what, and what it runs on. Start with the people at the top, then open a domain to see what it owns.
A card's colour is its status. Owns says what a domain is the source of truth for; Not says what it leaves to another.
Executive overview2026-09-07Primary user surfaces
NPO & Supplier Portal
corePORTAL
NPO is short for a nonprofit. This is the one web app that nonprofits and suppliers log in to. What you can do here depends on your role at your group. You can browse the marketplace, post needs, list goods, make offers, track orders, work with partners, and check your dashboard.
Data model organizations
Sources · 1
- src/pages/{marketplace, needs, resources, offers, orders, dashboards}, Network.tsx, components/organizations/{AddPartnerDialog,PartnerMetrics,CreatePartnerForm}.tsx
Public Website
externalGIK.ORGSEPARATE REPO
This is gik.org, the public site. Donors, suppliers and nonprofits land here first. The app itself sits at its own address just under gik.org. Mark, GIK's CEO, runs this site. Its code is kept apart from the platform's code, in GIK's own account. It is not part of the platform, and Augusto does not build it. The site is a set of fixed pages with two small live parts. One part lists the disasters that are open now. The other takes newsletter sign-ups. The plan is for both sides to read one shared list of disasters. Then a visitor can click a disaster card on gik.org and land in the marketplace already set to that disaster. GIK's side of that is built. The site still pulls its own copy from FEMA, the U.S. disaster agency, rather than reading GIK's list.
Ontology disasters · Data model disasters
Sources · 5
Team note: Brian Anderson, review 2026-09-07
GIK-Marketplace/gik-website (.eleventy.js · vercel.json · api/active-disasters.js · api/subscribe.js)
README.md:3index.html:18 (og:url proto.app.gik.org)docs/disaster-need-tagging-v4_4.md §D-04docs/disaster-tracking-executive-summary.md:156-168
Public Onboarding
corePUBLIC PAGES
Anyone can offer goods or ask for help here without an account. GIK holds on to what they filled in. Later it can turn into a real group and a real user.
Data model platform
Sources · 2
- supabase/functions/onboarding-session, onboarding-submit, onboarding-convert
src/pages/onboarding
Developer Portal
coreAPI DOCS
An API is a connection other software can use. This page sits inside the app and spells out how GIK's connection works. It is written for partners who build software.
Data model auth
Sources · 2
- package.json: @scalar/api-reference-react
src/pages/developers/ApiReference.tsx
Partner & Agent Systems
externalAPI + MCP
Other software, and AI helpers, can use GIK with no person clicking. Each one proves who it is with a key or a sign-in. They all come in through one guarded door. That door caps how much they may ask for and logs every request.
Ontology auth · Data model auth
Sources · 3
- supabase/functions/api-v1, mcp, mcp-oauth, partner-api-keys
- supabase/functions/_shared/gateway/{authenticate,audit,rateLimit,router,handleRequest}.ts
mcp-server/ (@gik/mcp-server)
Internal surfaces
Admin Console
externalCONSOLE
GIK's own staff watch over every group, listing and request here. They can also switch a group off without wiping its records. A folder in the code with a similar name is a set of operations scripts, not this console; see Ops scripts (a correction from the repo inspection of 2026-09-07).
Data model organizations
Sources · 4
- supabase/schema/super_admin.sql, org_soft_delete.sql
src/pages/adminsuper-admin-spec.mdadmin-utils/
Ops scripts
externalCLI
Small tools an engineer runs by typing, with no screen to click. They refresh data, clean up test data, move items into new categories, make pictures for listings, and answer one-off questions. (override › repo inspection 2026-09-07: these tools are not the Admin Console above. A scan on its own reads the two as one thing.)
Sources · 2
- admin-utils/{db,query,qa-cleanup,migrate-categories,logo-matcher,fix-test-data-integrity,generate-resource-images}.mjs
scripts/data-refresh
Core domains
Resources & Needs
core
Resources & Needs: what a supplier can give, and what a nonprofit is asking for. These are the two sides of every trade here.
Owns the listing and the request. Not what happens to them next. Other parts keep track of whether a listing is matched, offered or shipped, and of the event behind a need.
Ontology resources, needs · Data model resources, needs
Sources · 3
- ontology.yml › resources, needs
- supabase/schema/resources.sql, needs.sql
supabase/functions/resource-import
Matching
core
Matching: finds which supplies fit which needs, and how well. It gives each pair a score. GIK works the scores out again when a listing or a need changes, or when someone asks.
Owns the score. Not any promise. A high score is not an offer, and nothing here moves goods.
Ontology matching · Data model matching
Sources · 3
- ontology.yml › matching
- supabase/schema/matching.sql, matching_scores.sql
- supabase/functions/match-worker, match-preview, generate-embedding
Offers & Orders
core
Offers & Orders: one supplier promises goods for one need, and then those goods move. There are two ways in. A supplier can answer a need that Matching scored. Or a nonprofit can browse and check out on its own, the way you would in a shop — that is the shop override. Both ways end up in Orders.
Owns the promise, and the job of getting goods from one place to the other. Not the choice to trade.
Ontology offers, orders · Data model offers, orders
Sources · 4
- ontology.yml › offers, orders
- supabase/schema/offers.sql, cart_orders.sql
- supabase/functions/checkout-cart, update-order-status
- override › shop (Brian Anderson, review 2026-09-07)
Organizations & Identity
core
Organizations & Identity: the groups that take part, and the people in them. It holds where each group works, and who may join or team up with it. It also holds who each person is.
Owns the rule about who may deal with whom, and the person. Not acting on that rule, or what a person may do inside any one group.
Ontology organizations, identity · Data model organizations, identity
Sources · 2
- ontology.yml › organizations, identity
- supabase/schema/organizations.sql, profiles.sql, locations.sql
Disasters
core
Disasters: the events that make a need urgent. GIK pulls them in each day from FEMA, the U.S. disaster agency. Staff can also add one by hand.
Owns why a need is urgent. Not who may deal with whom, or which goods fit.
Ontology disasters · Data model disasters
Sources · 3
- ontology.yml › disasters
supabase/schema/disasters.sqlsupabase/functions/fema-disaster-sync
AI Agent Chat
core
A chat helper, built on Anthropic's AI, that shows nonprofits and suppliers around the marketplace. (no ontology module names it — the Ontology/Schema lines are left out, not filled with a placeholder.)
Owns the chat itself. Not any fact about the business. It reads and acts through the same connections everyone else uses.
Sources · 2
- package.json: @anthropic-ai/sdk
supabase/functions/ai-agent-chat
Key domain events
Need
A need starts as a draft. It opens for offers. It can be partially fulfilled, then fulfilled. It can also be cancelled.
Offer
An offer waits. It is then accepted, declined or cancelled. If nobody acts, it expires.
Order
An order is placed. The supplier commits. Then it is scheduled, then delivered. It can also be rejected or cancelled.
Resource
A listing starts as a draft. It goes live. Later it is closed.
Match run
A run waits, then runs, then ends as completed or failed. GIK starts one when a listing goes live or changes, when a need opens or changes, or when a person asks.
Disaster
A disaster is active or archived. GIK gets it from FEMA or from an admin.
Notification
A notice reaches a person in the app or by email.
Adapters
Brevo
adapter
The service that sends GIK's email. A notice can go out as email and show up in the app at the same time.
Data model notifications
Sources · 2
- supabase/functions/send-email, send-notification
- README › Key Features
Mapbox
adapter
The maps. It helps people pick a place, finishes typing an address for them, and shows where groups are.
Sources · 1
- package.json: mapbox-gl
Stream Chat
adapter
The live chat that lets two groups talk to each other in the moment.
Sources · 1
- package.json: stream-chat, stream-chat-react
LogRocket
adapter
Watches how people use the app and can play a visit back.
Sources · 1
- package.json: logrocket
FEMA
adapter
FEMA is the U.S. disaster agency. GIK reads its list of disasters and stores them. A timer inside the database (pg_cron) calls it every day at 09:00 UTC, the world's shared clock.
Data model disasters
Sources · 2
- enums.yml › disaster-source
supabase/functions/fema-disaster-sync
Infrastructure
Postgres + pgvector
infra
The store where all of GIK's records live. Its shape is set out in 22 files, in a fixed order, and the steps that change it are made from those files, never typed by hand. An add-on lets GIK weigh how close two pieces of text are in meaning, which is what matching leans on.
Sources · 3
- schema/init.sql: `CREATE EXTENSION IF NOT EXISTS "vector"`
supabase/schema (22 files, ordered in config.toml `schema_paths`)supabase/migrations (90)
Auth & Governance
infra
Signing in, and who is allowed to do what. GIK sends its own sign-in emails. What a person may do is set by their role at their group. Partner software signs in with a key, or with an approval a person granted it once.
Data model auth
Sources · 2
- supabase/functions/custom-auth-email, partner-api-keys, mcp-oauth
- supabase/schema/api_keys.sql, oauth.sql
Edge Functions
infra
Small programs that run right next to the data, with no server for GIK to keep. They handle the partner door, matching, checkout, the daily disaster pull, notices, messages out to partners, and the link for AI helpers.
Sources · 1
- supabase/functions (21 named functions + `_shared`, `utils` shared code) incl. api-v1, match-worker, match-preview, generate-embedding, checkout-cart, update-order-status, fema-disaster-sync, resource-import, onboarding-{session,submit,convert}, ai-agent-chat, send-email, send-notification, webhook-dispatch, custom-auth-email, delete-my-account, mcp, mcp-oauth, partner-api-keys
Partner API + MCP
infra
How a partner's software, and AI helpers, plug into GIK. Every call they can make is written down. GIK caps how often they may call, and logs each call. The server the AI helpers talk to ships two ways, as a package on its own and as functions inside the platform; both are shown here (a correction from the repo inspection of 2026-09-07).
Sources · 6
- supabase/functions/_shared/gateway/{authenticate,audit,rateLimit,router,handleRequest,matchSideEffects}.ts
- override › mcp-deployment (repo inspection 2026-09-07)
supabase/functions/_shared/openapimcp-server/supabase/functions/mcpsupabase/functions/mcp-oauth
Stack · 21 components in 5 bands
The technology the platform is built from, one band per layer, with the evidence for each piece. Start at the top band and read down.
Primary user surfaces
| Component | Detail | Evidence |
|---|---|---|
| NPO & Supplier web app | Single React SPA, role-based within shared organizations; marketplace, needs, resources, offers, orders, network/partnerships, dashboards | src/pages/* |
| Public onboarding pages | No-account share-a-resource / share-a-need flows that convert to an org later | src/pages/onboarding · supabase/functions/onboarding-{session,submit,convert} |
| Developer portal | Scalar-rendered in-app API reference for partners | src/pages/developers/ApiReference.tsx · @scalar/api-reference-react |
| Partner API + MCP clients | External systems and AI agents, authenticated via API key or OAuth | supabase/functions/api-v1, mcp, mcp-oauth, partner-api-keys · mcp-server/ |
| Admin console | Super-admin UI for staff | src/pages/admin · supabase/schema/super_admin.sql |
| Public website (gik.org) | Marketing site; separate repository in GIK's own GitHub organisation, managed by GIK — Eleventy static site on Vercel, serverless routes for active disasters (its own OpenFEMA pull) and newsletter sign-up; reads nothing from this platform today | GIK-Marketplace/gik-website: .eleventy.js · vercel.json · api/active-disasters.js · api/subscribe.js (override › Brian Anderson, review 2026-09-07) |
Applications
| Component | Detail | Evidence |
|---|---|---|
| React 18 + TypeScript | Vite build with the SWC plugin | package.json: react, vite, @vitejs/plugin-react-swc |
| shadcn/ui + Radix + Tailwind CSS 4 | Design-system primitives; lucide icons; tailwindcss-animate | components.json · @radix-ui/* · tailwind.config.ts |
| TanStack React Query + Context | Server state; auth/org context | @tanstack/react-query · README › State |
| Routing, maps, chat, viz, forms | react-router-dom · Mapbox GL · Stream Chat · D3 / Recharts · react-hook-form + zod | package.json |
Data
| Component | Detail | Evidence |
|---|---|---|
| Postgres + pgvector | 22 schema-first declarative files (ordered in config.toml's schema_paths), 90 generated migrations; embeddings on resources, needs, matching scores | supabase/schema (22), supabase/migrations (90) · schema/init.sql: CREATE EXTENSION IF NOT EXISTS "vector" |
| Edge Functions (21) | api-v1 · mcp · mcp-oauth · partner-api-keys · match-worker · match-preview · generate-embedding · checkout-cart · update-order-status · fema-disaster-sync · resource-import · onboarding-{session,submit,convert} · ai-agent-chat · send-email · send-notification · webhook-dispatch · custom-auth-email · delete-my-account (+ _shared, utils shared code, not deployable functions) | supabase/functions |
| Auth · Storage · Realtime | Supabase-managed services; Realtime feeds in-app notifications | README › Backend · supabase/schema/storage.sql, notifications.sql |
AI & integrations
| Component | Detail | Evidence |
|---|---|---|
| Anthropic Claude SDK | Powers the in-app AI agent chat | package.json: @anthropic-ai/sdk · supabase/functions/ai-agent-chat |
| pgvector embeddings | Semantic matching of needs to resources | supabase/functions/generate-embedding · supabase/schema/matching_scores.sql, resources.sql, needs.sql |
| MCP server | Exposes marketplace operations to agent clients; shipped both as a standalone package and as edge functions | mcp-server/ (@gik/mcp-server) · supabase/functions/mcp, mcp-oauth |
| Brevo · Mapbox · Stream Chat · LogRocket · FEMA | Email, maps, chat, session replay, disaster feed | README › Key Features · package.json · supabase/functions/fema-disaster-sync |
Infra & deploy
| Component | Detail | Evidence |
|---|---|---|
| Cloudflare Pages | Hosts the frontend | README › Tech Stack (Deployment) |
| Supabase Cloud | Hosts Postgres, Auth, Storage, Edge Functions, Realtime | README › Tech Stack · supabase/config.toml (project_id = "muirbuzopvoemltuxpws") |
| GitHub Actions | 7 workflows: CI, deploy, and per-environment deploys for develop, staging, sandbox, production, plus a staging-data refresh | .github/workflows/{ci,deploy,develop-deploy,staging-deploy,sandbox-deploy,prod-deploy,staging-refresh}.yaml |
| Node 22.16.0, Vitest, Playwright | Runtime pin and test tooling (dev-time only, not a layer) | .nvmrc · vitest.config.ts · devDependencies: vitest, @playwright/test, playwright |
Evidence
| Source | What it shows |
|---|---|
package.json | react, vite, @supabase/supabase-js, @anthropic-ai/sdk, tailwindcss, stream-chat, mapbox-gl, d3, recharts, @scalar/api-reference-react, logrocket, react-router-dom, react-hook-form, zod |
supabase/config.toml schema_paths | 22 schema files in FK-applied order; project_id |
supabase/schema/init.sql | CREATE EXTENSION IF NOT EXISTS "vector"; activity_provider enum local, hedera |
supabase/functions (directory listing) | 21 function directories + _shared, utils |
src/pages/marketplace/{Marketplace,Cart,Checkout,CheckoutConfirmation}.tsx | code evidence for the shop override |
mcp-server/package.json | @gik/mcp-server — @modelcontextprotocol/sdk, zod |
git: 843dfa1b..HEAD (drift.py) | 7 commits after the ontology snapshot, none touching supabase/ |
.nvmrc | 22.16.0 |
Drift · 7 claims checked
Each row is a claim from the architecture, checked against the code. A grey chip means the check produced no verdict — it could not run, or there was nothing to check against — which is not the same as finding nothing.
| Claim | Reference status | Repo evidence | Verdict |
|---|---|---|---|
| Ontology snapshot currency | ontology.yml description: drafted over gik-platform develop@843dfa1b (2026-08-31) | drift.py --since 843dfa1b: HEAD develop@402d466a is 7 commits past the snapshot; none touch supabase/schema, supabase/functions or supabase/migrations (search ran) | consistent |
| Shop / cart-checkout path | not a module in gik-ontology/ontology.yml (override › shop, ontology_gap: true) | evidence.py 'checkout-cart' ran, matched src/pages/marketplace/Checkout.tsx, supabase/functions/checkout-cart/index.ts, supabase/schema/cart_orders.sql, supabase/schema/resources.sql | built |
Admin console vs. admin-utils/ | override › admin-console correction, repo inspection 2026-09-07 | evidence.py 'super_admin' ran, matched supabase/schema/super_admin.sql and src/pages/admin (not admin-utils/) | built |
| MCP ships two ways | override › mcp-deployment correction, repo inspection 2026-09-07 | evidence.py 'mcp-oauth' ran, matched supabase/functions/mcp-oauth/index.ts, supabase/functions/mcp/index.ts, plus mcp-server/package.json (scan.py) | built |
| Matching is embedding-assisted (pgvector) | override › matching-embeddings correction, repo inspection 2026-09-07 | evidence.py 'CREATE EXTENSION.*vector' ran, matched supabase/schema/init.sql; embedding columns confirmed in resources.sql, needs.sql, matching_scores.sql | built |
| gik.org ↔ platform disasters (one write, two readers) | override › public-website, docs/disaster-need-tagging-v4_4.md §D-04 (2026-07-15) | App side built: supabase/schema/disasters.sql, supabase/functions/fema-disaster-sync, migrations 20260715164204_add_disasters, 20260716104651_schedule_fema_disaster_sync. Website side not independently re-checked here (gik-website is not in sources.json; the override's 2026-09-07 GitHub-API inspection is carried as given, per docs/disaster-tracking-executive-summary.md:156-168) | partial |
| Target vs. built | — | no charter, roadmap or ADR directory in this repo | n/a |
2b99ae7 · committed 2026-09-03 · read 2026-09-11Architecture · as-built · gik-platform develop@402d466a (2026-09-04) · GIK-Marketplace/gik-website main (pushed 2026-09-04; inspected via the GitHub API, not cloned — override › Brian Anderson, review 2026-09-07) · generated 2026-09-11