Skip to content

Full OS $99 USD per location, per month, after a 60-day free trialFirst 100 shops: 90-day pays-for-itself guarantee Start as a founding shop

Security

Each shop sees only its own records.

Access is decided in the database, not on the page. Whoever signs in sees exactly what their job is allowed to see, and anyone whose access cannot be resolved sees nothing rather than everything.

Shop isolation
PostgreSQL row-level security
Database
Contabo · EU (France)
SOC 2
Not certified

Request scoping

The database decides, row by row.

Every shop’s records share one database, and each row carries the shop it belongs to. The browser never names the shop: the database looks it up from whoever is signed in and applies the policy to every row it could return.

  1. 01 · Browser

    Signed in, given Location A

    GET /rest/v1/work_ordersAuthorization: Bearer ‹your session›

    The shop is not part of the request.

  2. 02 · PostgREST

    Checks the session, runs as you

    role = authenticatedauth.uid() = ‹you›

  3. 03 · Row-level security

    Checks every row

    • current_tenant_id() IS NOT NULLYour shop, from your membership Passes.
    • AND tenant_id = current_tenant_id()Another shop’s rows drop out Passes.
    • AND deleted_at IS NULLDeleted rows stay out Passes.
    • AND (has_global_role(ARRAY['owner', …])The shop’s owner Not this person.
    • OR location_id = ANY (user_location_ids())A location the shop gave you Admits this person.
    • OR EXISTS (… technician_id = auth.uid() …))An active job assigned to you Not this person.
  4. 04 · Rows returned

    2 of 4

    • Your shopLocation A · active, assigned to youReturned
    • Your shopLocation AReturned
    • Your shopLocation BWithheld · Location not given
    • Another shopAny locationWithheld · Another shop
The select policy on work_orders, abbreviated from BayStacker’s schema. The rows are a diagram, not records.

Evidence trail

Who changed what, and when.

A change to a customer, a vehicle, a person’s profile or a legal entity writes an audit row. Owners and admins read it in the shop’s audit log.

tenant_id
The shop
table_name · record_id
The record
action
Insert, update, archive or delete
old_values → new_values
Before and after
changed_by
The signed-in person
changed_at
When

And some records cannot change at all.

  1. An analysis, once written

    UPDATE analysis_results SET hypotheses_json = …ERROR analysis_results content is immutable
  2. A purchase order’s lines, once it leaves draft

    UPDATE purchase_order_lines SET unit_cost = …ERROR FYV635_SUBMITTED_PO_LINES_ARE_IMMUTABLE
  3. The shop operations log

    DELETE FROM shop_operations_audit_log WHERE …ERROR shop_operations_audit_log is immutable — DELETE is prohibited.
Triggers hold against BayStacker’s own server key, not only a browser.

Gates

A gate is a refusal, not a hidden button.

On a repair with a diagnosis case BayStacker manages, each step is refused until its evidence is on the record.

  1. 01

    Commit intake

    A captured, decoded 17-character VIN.

    Refused by the databaseVIN_REQUIRED

  2. 02

    Invoice

    Post-repair verification passed, or a manager’s recorded waiver.

    Refused by the serverVERIFICATION_REQUIRED

  3. 03

    Vehicle release

    A verification result — a waiver only with its scope and reason — on an invoiced work order.

    Refused by the databaseVEHICLE_RELEASE_VERIFICATION_REQUIRED

  4. 04

    Case closeout

    The latest verification passed after the conclusion: road test, code recheck, QC checklist and evidence, with a named verifier.

    Refused by the databasePASSING_VERIFICATION_REQUIRED

What it does and does not do

Stated both ways.

AreaBayStacker doesBayStacker does not
Shop isolationDecides what a browser may read in the database, with row-level security keyed to the signed-in person.Take the shop from the request, or fall back to showing everything when access cannot be worked out.
Privileged keyKeeps the database’s privileged key on the server and uses it there — for example, to open a customer’s secure link.Send it to a browser.
MoneyShows money by job: full, limited or none. A technician sees none of it.Recalculate rebuild labour or parts tax — the shop’s own rules apply.
PaymentsRecords what a customer owes and paid on the invoice.Process card payments.
AIHolds Analysis A as a draft until a person confirms it.Treat a model’s answer as the conclusion.
UnknownsShows a missing measurement, an unknown payment or an unreceived part as exactly that.Invent a measurement, render an unknown payment as a success, treat a purchase order’s status as receipt, or a core shipment as a recovered credit.

Security summary

Report a vulnerability or a suspected incident to laurence@bien.mx WhatsApp +52 614 479 2338. The same contact is published in security.txt.

SOC 2
Not certified. BayStacker has no SOC 2 report.
Penetration testing
None yet. No independent penetration test of BayStacker has been run.
Data location
The database runs on a server hosted by Contabo in the EU (France). The web application and its API are served by Netlify.
Server access
BayStacker’s server holds a privileged database key and uses it on the server side, for example to open a customer’s secure link. The key is never sent to a browser: a browser reads only under the signed-in person’s own access rules.
Sub-processors
ServiceWhat it handles
ContaboHosts the database server (EU, France).
SupabaseDatabase software, self-hosted on that server.
NetlifyServes the web application and its API.
StripeSubscription billing for the shop’s own BayStacker plan.
ResendSends account and customer email.
TwilioSends text messages.
SentryReceives application error reports (US).
TypeSafeAdjudicates the candidate causes in a diagnosis, from the repair facts.
OpenRouter (DeepSeek)Model calls TypeSafe does not take, from the same repair facts.

Bring your reviewer’s questions to a walkthrough.

Or send them to laurence@bien.mx.

Request a walkthrough