The software I replaced

The vendor security questionnaire round trip. Sometimes it is a literal spreadsheet on an email thread; sometimes it is a portal that behaves like one. Either way a customer sends two hundred questions, somebody spends a day retyping answers they have written before, and the file goes back.

Why I chose it

Because the slow part is not the typing — it is that nobody can tell which answers are still true.

An answer that was correct last quarter looks exactly like an answer that is correct today. There is no link from a claim back to the thing that makes it true, and no record of who agreed to it. So every round starts from scratch, and the ones that do get reused are reused on faith.

That is a data model problem wearing a productivity-tool costume, which made it a good fit for rebuilding on a backend rather than a prettier front end.

What it does

Evidence comes first. You keep a library of statements about your posture — how MFA is enforced, how backups are encrypted — and each one carries its own review horizon.

When a questionnaire arrives, AnswerProof ranks the library against each question and drafts an answer, recording which terms matched so a reviewer can see why a source was picked. A human approves the draft, and that approval freezes two digests: one over the exact answer text, one over the bodies of the evidence it cited.

Then the part that makes it worth having: the export is a gate, not a formatting step. Before anything leaves the building, every approved answer is re-evaluated against the evidence as it stands right now. An answer goes stale two ways —

  • its evidence aged past its review horizon, with nobody touching anything, or
  • somebody edited that evidence after the approval was recorded

— and either one blocks the export until a person looks at it again. The second case is the one a spreadsheet silently gets wrong.

How Xano does the work

Xano is the backend, not a database behind a bespoke server. The data model (evidence, questionnaire, question, answer), the business logic, and the API all live there:

  • POST /questionnaire creates a questionnaire and its questions in one call
  • POST /questionnaire/{id}/draft ranks the evidence library and writes drafted answers with their matched terms
  • POST /answer/{id}/approve records the approver and freezes both digests
  • GET /questionnaire/{id}/export re-checks every answer and refuses while anything is stale or unanswered
  • GET /evidence/stale lists what to refresh before the next questionnaire lands

The front end is deliberately thin. It renders what Xano decides; it does not hold a second copy of the rules.

The tables are declared as source in schema/tables.json and created through the Metadata API, so the backend can be rebuilt from the repository instead of only existing as clicks in a builder. That mattered more than I expected — it is the difference between a demo and something a second person can stand up.

Which AI tools I used

Claude Code, for the whole build: the policy module, the test suite, the schema push tool, and the front end. The evidence ranking inside the product is deliberately not a language model — it is term overlap, because a security reviewer needs to see which words made the tool pick a source. "The model said so" is exactly the answer that does not survive a review.

How long it took

About half a day of focused work. The part that took the longest was not code — it was settling what "still approved" should mean, and deciding that the export had to be allowed to say no.

What would have taken significantly longer without AI and Xano combined

Standing up an authenticated REST API over a managed Postgres, with the schema versioned and reproducible, and having it be something I could point a front end at the same afternoon. On this project that is most of the backend, and none of it was the interesting part.

The combination mattered more than either half. Xano removed the server, and Claude Code removed the part where you learn a new backend language by reading error messages — the XanoScript in this repository was written, dry-run, and corrected against the live parser in a loop, which is what made declaring the whole backend as source practical inside a day rather than a week.

Challenges

The first version let the export succeed and just annotated stale rows. That is the spreadsheet's behaviour with extra steps, and it took writing the failing test to see it — an export that warns is an export that ships.

Choosing digest boundaries also took a second pass. Hashing evidence ids alone would have missed an edited document entirely, since the id never moves. Hashing the bodies is what makes "the evidence changed underneath this answer" detectable at all.

The last one was self-inflicted. I had written the rules twice — once in XanoScript and once in a local module I described as the same logic — and I had never checked that claim. When I finally wrote the script that checks it, the two disagreed in three places: the local version sorted evidence lexicographically, so 10 came before 2 and produced a different preimage; it compared staleness in milliseconds where Xano floors to whole days; and it read timestamps only as ISO strings, where the rows hold epoch milliseconds. None of those would have surfaced in a demo. npm run parity now pulls the live tables, recomputes the export locally, and fails if the digest does not match the one the deployed endpoint published.

What I learned

Reusable answers are a provenance problem, not a search problem. Once each answer points at the evidence that justifies it, staleness becomes something you can compute instead of something you hope somebody remembers.

What's next

Diffing an incoming questionnaire against the last one from the same customer, so the work is proportional to what actually changed. And letting an evidence owner be notified when their item is about to age out, rather than discovering it mid-questionnaire.

Scope

Single-tenant demonstration with synthetic evidence. The digests bind an approver to a text; they are not a signature and do not prove identity.

Built With

Share this project:

Updates