Skip to content
Every figure traces to a ledger row, or the deliverable does not render

The platform

The parts that decide whether this is safe to put several clients in

A practice hands this software the books, the tax numbers and the bank data of every client it acts for, under a duty of confidentiality that is professional rather than contractual. That makes the architecture a purchasing question rather than an engineering footnote, so it is written down here instead of being described on a call.

Six properties

Each of these is checkable, which is why it is written down

“We take security seriously” is not a claim anybody can test. “Scoping is applied where the query is built, and a build that forgets it fails” is.

Tenancy is enforced below the API

The firm sits on every row and is applied in the layer that builds the query, not in the handlers. That gives scoping exactly one place it can be forgotten, and a build that forgets it fails rather than shipping.

  • A read, list, search or download across firms answers as though the record does not exist
  • That answer is deliberate: a refusal confirms the record is there, and a real engagement id is a fact about another firm’s client list
  • A capability refusal is different and is answered as one — that caller is entitled to be in this firm
  • A build fails if any firm-data function can be called without a scope, and the handful that genuinely run before one exists are listed individually with a written reason

The engine is deterministic, and separate

The computation code is a pure module with no model call in it, now or later. That separation is what makes the output reproducible and the drafting layer replaceable.

  • The same inputs produce the same figures on every run, to the rupee
  • A regression set replays a fixed job per deliverable type with the provider replaced by a recording, and compares every figure exactly — a paisa of drift fails
  • Exact decimals throughout, rounded once at the boundary; no floating-point number touches money
  • Rate sets are effective-dated data, and every computation records the checksum of the set that produced it

Generation runs on a queue

The web process records the job and enqueues it; a separate worker runs the generation. A deploy does not interrupt a deliverable, and a dropped connection does not produce a second one.

  • A submission repeated after a timeout returns the same job rather than generating a duplicate document
  • Tasks are written to be safe to run twice, because at-least-once is what a queue actually gives you
  • A job that reached a terminal state is frozen — further status writes are refused at the data layer, where every writer passes through
  • Startup reconciliation asks the queue before closing a job out, so "processing" does not imply "orphaned" when several workers are running

Provenance and audit are structural

The chain from a rendered figure to the row it came from is stored, not reconstructed. So is the record of who read and exported what.

  • Deliverable sections carry the computation lines they drew on
  • Computation lines carry the source row and the rate set they were computed under
  • Every read and export writes an audit row with the actor and the time
  • A figure with no chain behind it blocks rather than rendering

Client identifiers are redacted before anything is logged

A tax number, a registration number, an account number or a client name is confidential data rather than an ordinary field, and an operational log is a place where confidential data goes to be forgotten about.

  • Redacted on the way into logs, so an operational log is not a client list
  • Retention runs as a sweep against the statutory period rather than as indefinite accumulation
  • Documents encrypted at rest, and a written note of what is stored, where and for how long is available for your own review
  • Multi-factor authentication on by default — these accounts sign documents somebody relies on

The threats specific to this input path

The dangerous inputs here are spreadsheets, ledger exports and structured files from third parties, and all three have well-known ways of being weapons rather than data.

  • A narration beginning with an equals sign exports as inert text rather than as a formula in somebody’s spreadsheet
  • Structured files are parsed with external entity resolution disabled, so a crafted file cannot read the server’s own filesystem
  • Any address appearing in imported data is treated as untrusted rather than followed
  • Narrations and client-controlled text are rendered as text everywhere in the console

The API

The console is one client of it, not a wrapper around a private one

Anything a person can do through the console, a system can do through the API — which is what makes an integration a configuration exercise rather than a project.

  • Request and response types are generated from the API’s own specification, so a change the console has not caught up with fails a build rather than surfacing as a runtime error in front of somebody.
  • Signed completion callbacks over the raw body; a one-character change fails verification.
  • Failed deliveries retried with increasing backoff, then dead-lettered — a delivery is either confirmed or visibly not.
  • Exports as documents, as spreadsheets with the working and cross-references intact, and as structured data.
  • Live job progress readable as it happens, so a caller does not poll blindly.
  • Quotas answer as a quota rather than as a server error, and the console shows that as a real state.

What it runs on

Boring where it can be

None of these choices is interesting, which is the point. The interesting decisions in this product are the refusals, and those are on the standard page.

EnginePython, layered so that route handlers hold no queries and the computation module holds no model calls
DataPostgreSQL with exact decimal columns for every monetary figure, and constraints rather than conventions for the rules that matter
QueueRedis-backed jobs, run by a worker process separate from the web process and deployed as its own service
ConsoleReact and TypeScript, with server state cached and money handled as strings the browser never computes on
IdentitySigned tokens with multi-factor authentication on by default; the token is held in memory rather than in browser storage
DocumentsObject storage with per-firm encryption, and retention sweeps against the statutory period rather than indefinite accumulation

The worker being a separate process is the point of it. Deploying the API restarts the API; a deliverable that was being generated when it did is still on the queue afterwards.

Send this page to whoever does your security review

It is written for them rather than for a buyer, and the data-flow note behind it exists for the same reason. If something they need is not here, tell us what and we will write it down rather than answer it once on a call.

Get in touch

Talk to the people building it

No chatbot and no ticket queue. Tell us what your practice actually looks like — how many members sign, how many prepare, which of audit, tax and compliance you do most of — and someone who works on the software will reply.

info@legosphere.com

Please keep client names, PANs, GSTINs, account numbers and ledger extracts out of this box — it is an ordinary enquiry form, not a channel for a live engagement.