Written for whoever in your organization has to sign off on a new vendor. It is not a sales page. It answers the four questions a reviewer actually has: can our data leak, can a record be changed after it is signed, can we get everything back out, and what happens if the vendor disappears.
ROW QC is not yet in service. The database exists, the schema is deployed to it, and the application is now live at rowqc.com - but there is no customer data of any kind, no company account has been created by anyone outside the project, and no customer has ever used it.
That is said first because it changes how to read the rest. Controls described below as working are proven by an automated suite that runs against the real database, not a mock - but they have never been exercised by real users doing real work, which is a different kind of evidence. If you are evaluating ROW QC for a live project, the honest position is that you would be the first, and the items in The gaps below are the ones to weigh.
These are the answers you would otherwise spend a questionnaire round trip extracting. Getting them in the first email is worth more to both of us than getting them in week three.
| Control | Detail | |
|---|---|---|
| In production use | No | The database is provisioned, the schema deployed and the application is live at rowqc.com, but there are no customers and no customer data. Nothing has been run in anger. |
| SOC 2 / ISO 27001 | No | No certification and none in progress. If this is a hard gate in your process, it is better to know now. |
| Third-party penetration test | No | Not commissioned. Security is tested by an automated suite that runs against a real PostgreSQL database, described below. |
| SSO / SAML / SCIM | No | Email and password, with reset by email. No directory integration and no automated deprovisioning — removing someone is a manual step in the app. |
| MFA | No | Not offered today. |
| Audit log | No | Not built. Signed daily reports are individually tamper-evident (see below), which is a narrower guarantee than a full audit trail and should not be read as one. A general append-only log exists in the sibling product and is the intended path here. |
| Defined retention policy | No | Nothing is deleted today, which is not the same as a policy. DOT and PHMSA retention obligations differ by record type — as-builts and weld maps are life-of-asset — and the product does not yet enforce or document a schedule. |
| File uploads | No | MTRs, NDE reports, as-builts, permits and photos are recorded as file references today, not stored files. The drawn signature is the one exception and is held inline in the database. If your job book needs the documents themselves in the system, that is not built. |
| Uptime SLA | No | Nothing has been measured, so nothing is claimed. An availability commitment can be written into an agreement; it would be a commitment going forward, not a statistic about the past. |
| Registered legal entity | In progress | [[ENTITY]] — company registration has not completed. If your process needs a W-9, a certificate of insurance, or an entity on the signature page, raise it early; this is the item most likely to set the timeline. |
| Tenant isolation | Yes | Enforced by the database, not the application. Tested by a probe that was itself validated by deliberately breaking the schema. See below. |
| Tamper-evident signed records | Yes | A signed daily report is digested by the database at the moment of signing, and the digest can be re-checked at any time. See below. |
| Encryption | Yes | TLS in transit, AES-256 at rest, both provided by Supabase and AWS. No customer-managed keys. |
| Data export | Yes | Standard PostgreSQL. See Getting your data out. |
This is the part worth your reviewer's attention, because it is the one structural decision that changes what a frontend bug can do.
Every access rule lives in PostgreSQL row-level security policies, evaluated by the database on every query against the caller's own identity. The browser holds a public key that is not a secret and is visible in the page source; it carries no privileges of its own. A user's reach is decided after the query arrives, by rules the user's session cannot alter.
The practical difference: a coating inspector cannot author a weld record because the database refuses the insert — not because the button is hidden. Give that account a token and a terminal and it still cannot write one, because nothing in the client is part of the boundary.
An isolation probe builds two rival companies, switches role to a real signed-in identity, and checks three separate boundaries:
Two things make the probe worth trusting rather than just reassuring. First, every check has a positive control: each read runs as the person entitled to it and as the person who is not, and the probe reports inconclusive rather than pass if the entitled read also came back empty. "The outsider saw nothing" is equally consistent with a typo in the test.
Second, the probe was validated by breaking the schema on purpose — a policy opened to everyone, a view stripped of its security setting, a privileged function's guard removed, timesheets opened company-wide, row-level security switched off on a table, and a database role granted the right to bypass it. It caught all of them, and passed clean with none applied. A test that has never been seen to fail is not evidence.
The probe was run against the production database on 2026-08-08, along with the sixteen other suites, and all seventeen passed.
The limit of that claim: the probe tests the database policies directly; it does not exercise the HTTP API layer in front of the database. That layer has its own repeatable probe, run against the live API on 2026-08-17: all 43 tables and 5 views refused an anonymous read, every table refused an anonymous write, all 81 database functions granted to signed-in callers refused an anonymous call, and the API's own catalogue document is refused. Because a revoked key produces the same refusals as a correct configuration, that run starts with a control request that must succeed — so a pass cannot be the key failing rather than the walls holding.
The sentence above used to end “and every database function refused an anonymous call.” That part was not true, and the probe that produced it could not have discovered otherwise.
It called every function with an empty argument list. The API answers 404 — no such function when the arguments do not match a signature, and the probe counted that as a refusal. Every function here takes arguments except one, so exactly one function's permissions were ever really tested — and that one returned 200. Rewritten to send each function's real parameters, the probe found fourteen functions reachable without signing in.
No data was exposed by this, and it was not a way in. The functions that read tables were
still stopped by the table permissions behind them. The ones that ran are the privileged helpers,
and each begins by asking who is calling: with no session that question answers nobody,
so they returned false, zero, or an empty list. The cause was a grant nobody wrote — the platform
grants new functions to the anonymous role by default, and this schema revoked from
PUBLIC, which is a different grantee. The revoke read exactly as though it closed the
door.
Closed the same day. The grant is now revoked from the anonymous role across every function, including the internal ones no caller can reach, and again for any function added later. Re-run against the live API, every one of them refuses — eighty-one as of the 2026-08-17 run, and the count moves with the schema rather than being fixed here. The probe was rewritten alongside it: it sends each function's real parameters so a refusal is about permission rather than a mistyped request, and it cross-checks its own list of what it is supposed to be testing.
That cross-check has since earned its keep. A later change wrote a block of grants as one statement spread over several lines — valid, ordinary SQL, and invisible to a probe that reads these files a line at a time. Seven functions fell out of the list of things being tested, and because they fell out of the granted list and the revoked list together, the omission cancelled itself and nothing looked wrong. The cross-check noticed the lists no longer agreed and said so. Every one of those functions refused anonymous callers throughout, so nothing was ever exposed; what had gone missing was the evidence, which on this page is the point. The statements are now written one per line and every one of them is tested — eighty-one on the 2026-08-17 run, and the probe's own cross-check is what keeps that number honest as the schema moves rather than this sentence being edited to follow it.
It is recorded here rather than quietly fixed because the argument this page makes is that the walls are tested, not that they are assumed. A test that could only ever have passed is worth less than no test, and the honest version of “we check this” includes the time checking properly changed the answer.
Documents and photographs — MTRs, coating strips, reader sheets, permits — are not stored in the database. They go to private storage, and that means a second set of walls that the checks above say nothing about. So they get their own probe.
Three properties, each tested by behaviour rather than by reading the configuration:
The one honest limit: attachments are not covered by the signature digest described above, and that is a decision rather than an oversight. Evidence arrives late — a laboratory result comes back days after the report is signed — and a digest that covered attachments would report tampering every time a legitimate one was filed. What is claimed instead is narrower and actually holds: who filed this file, when, against which record, and that it has not changed since.
The regulatory requirement — DOT, PHMSA, FERC and the state bodies — is a signed, dated record, kept. The part nobody specifies is how anyone knows, three years later, that the PDF in the binder is the record that was signed. A PDF cannot testify about itself.
So at the moment of signing, the database computes a SHA-256 digest over the report and its quantities and stores it. The PDF prints that digest. It can be recomputed at any time and compared, and the job book re-checks every report when it is compiled — reporting plainly, in the document itself, if one no longer matches.
Two design choices carry it:
Signing also freezes the record: the report and its quantities stop being editable, and a correction is made by voiding with a stated reason and filing a corrected entry. The original stays visible. The same applies to the weld map — the as-welded facts freeze, while inspection results stay open, because the x-ray comes back after the report is signed.
This was tested the same way as the isolation probe: by tampering with a signed record behind the freeze's back and asserting the digest noticed, plus five deliberate sabotages of the digest itself.
Two places, and it is worth being precise because the answer people expect is usually about the wrong one.
| What | Where | Notes |
|---|---|---|
| Inspection data | Supabase, running on AWS in us-west-2 (Oregon) | Every report, quantity, weld, coating reading, delivery and timesheet. This is the one that matters. Managed PostgreSQL; Supabase operates the platform on AWS infrastructure. |
| The application | Netlify | A single static HTML file. It holds no data and no credentials — the key it carries is public by design and confers nothing. Where this file is served from has no bearing on where your data lives, which is the distinction most hosting questions are really reaching for. |
| Backups | Supabase Pro — daily, retained 7 days | Provided by the database platform, not by ROW QC. Point-in-time recovery is not enabled: it is a paid add-on and has not been bought, so a restore lands on a daily snapshot rather than on a chosen moment. The honest worst case is losing up to a day of entries, and it is stated here rather than left to be discovered during the incident that needs it. |
That is the complete list. There is no analytics, no session recording, no error-reporting service, no chat widget, no advertising or marketing pixel of any kind.
Three account roles — owner, administrator and user — and a per-project discipline, because the same person is a welding inspector on one job and the chief on the next.
| Role | Their own discipline | Across disciplines | Timesheets |
|---|---|---|---|
| Account owner | Read / write | All projects in the company | All, in their company |
| Account administrator | Read / write | All projects in the company | All, in their company |
| Chief / assistant chief | Read / write | Read, on their assigned projects | Their crew's |
| Welding, coating, utility, HDD, environmental, survey, materials | Read / write | Their own records only | Their own only |
The owner is whoever created the company account, and there is exactly one — enforced by the database, not by the interface. An owner can appoint and remove administrators; an administrator cannot. That difference is the whole reason the third role exists, and it is enforced where it cannot be talked around: an administrator who builds the request by hand still cannot create another administrator, promote themselves, or demote the owner. The verification suite for it attacks all four of those paths.
Removing somebody ends their access to the jobs as well as to the company. Those are two different things in the database, and clearing only the second leaves a person able to read every project they were assigned to. Removal is therefore one operation that ends both, and the verification suite for it asserts that the removed person cannot read the job — not merely that their membership row changed. Their filed work is not deleted: a job book compiled next year still has to say who wrote what.
Crew do not sign themselves up. An owner or administrator creates the account, and the person receives a temporary password by email. It works once: the database records that the password must be replaced, the app refuses to load anything until it is, and the flag is cleared by the database itself when the stored password actually changes — not by the app saying so. A signed-in session has no privilege to clear it.
Timesheets are the strictest thing in the system, deliberately. A colleague on the same job cannot read your hours, and nothing about hours or pay reaches a client-facing report — how long a contractor sits on its inspectors' timesheets is nobody's business but the contractor's, and the test suite asserts it.
One default worth naming because your crews may want it otherwise: disciplines do not read each other by default. A welding inspector cannot see the coating reports. Some organizations prefer everyone on a job to see everything; that is a one-line change and a decision for you, not for me.
It is a standard PostgreSQL database. Not a proprietary format, not an export API that lags the real schema — the same database the application reads.
pg_dump of your data, on request, at any
time and at no charge. There is no retention hold and no export fee.What that means practically: if ROW QC stops existing tomorrow, the job book is a stack of PDFs you already have and the underlying records restore into any PostgreSQL instance.
ROW QC is built and run by one person. That is a real risk and pretending otherwise would be the wrong way to start.
What reduces it: there is no proprietary runtime. The data is standard PostgreSQL on a managed platform you could be given direct access to; the application is a single static HTML file with no build step, so it can be served by anything, by anyone, indefinitely. The schema and its tests are plain SQL and readable by any competent DBA.
An enterprise agreement can add an escrow or source-availability clause. Ask, and it will be written in.
ROW QC has siblings — Hot Pass QC, Sparky QC, CodeShop QC and Laydown MC. They share a design philosophy and nothing else. Separate repositories, separate databases, separate credentials, no shared runtime. An incident in one cannot reach another, and a customer of one has no presence in the others' systems.
The honest cost of that choice: a fix in one has to be carried across by hand, and configuration between them has drifted before. It is a deliberate trade of maintenance effort for blast radius.
If your review needs something this page does not answer, send the question rather than the questionnaire and you will get a direct answer, including when the answer is no. Most of the "no" rows above are here because someone would have found them in week three anyway.
support@rowqc.com reaches somebody who can answer it, not a ticket queue.