भारत सरकार की सेवाओं की स्वतंत्र परिकल्पना · Independent government-service concept
कॉरपोरेट कार्य मंत्रालयMinistry of Corporate AffairsMCA portal — filings, fees and deadlines
Register
Business Facilitation

HOW THIS WOULD REALLY WORK

The parts that decide whether this survives contact with a real registry

A prototype is easy to make convincing and hard to make true. This page separates what is running in this deployment from what is only decided, because a reader cannot check the difference from the outside and should not have to guess.

Built and running · 6Designed, not built · 2Deliberately not built · 1

A director disputes a penalty two years from now. What was the rule that day?

Built and running

Every engine is a pure function whose rule set carries a version, and `asOf` is an argument rather than a clock read. That combination is what makes a figure reproducible: the same inputs and the same version produce the same rupees on any future day, on any machine, including one belonging to the person disputing it.

Rule sets are never edited in place. A change to the arithmetic is a new version, and the version is printed next to the figure it produced, so an old screenshot can always be reconciled against the rules in force when it was taken.

This is also why the circular-override layer is data handed to the engine rather than a branch inside it. Due dates moved twice by circular last season. An engine that can only be corrected by a deploy is an engine that is wrong for every day between the circular and the deploy.

Values in force in this deployment right now.
Exposuremca-exposure-2026.08.2
Feesmca-fees-2026.08.1
Registered officemca-registered-office-2026.08.1
Intakemca-intake-2026.08.1
Inbox derivationmca-inbox-2026.08.1

The same form is submitted twice. Is the director charged twice?

Built and running

On the WhatsApp channel the idempotency key is the Meta message id, and it is claimed with an INSERT into `whatsapp_messages` before the reply is composed. The insert IS the lock. A check-then-set would race against the very retry it exists to catch, and Meta retries on any non-2xx and on its own timeouts.

The same discipline applies to account linking: a one-time code is redeemed in the UPDATE itself, conditional on `consumed_at IS NULL`, so two messages carrying the same code cannot both succeed. Reading first and writing second would let both through.

Filing is not implemented here and therefore charges nobody. When it is, the key is the SRN, not a request id: a resubmission of the same SRN is one filing in two attempts, and a fee engine that keys on the request would bill the retry.

The backend is down. What does the director see?

Built and running

Fail closed, say so, and never imply that anything succeeded. The concrete case in this build is the company register: it is served from an official open dataset over a shared, rate-limited key.

A throttled lookup is not the same fact as an absent company, and the code refuses to collapse them. A 429, or a 200 carrying a rate-limit error body, returns `register-unavailable` with an explanation — never the six demonstration companies, and never "no match". The distinction is enforced one level lower too: a degraded lookup is not written to the cache, because caching a throttle would pin "no such company" over a company that exists for the next hour.

The model layers degrade the same way. No key, a timeout, a refusal or an exhausted budget all return unavailable, and the deterministic answer underneath is unaffected — which is the point of keeping the legal content out of the model in the first place.

Where does authority come from, and what is it allowed to do?

Built and running

Possession of a phone number is not authority for a statutory act. The WhatsApp channel is therefore permanently read-only: no filing, no payment, no consent grant, no profile change is reachable from it, and every action that would alter a record hands off to the portal. That is a structural property, not a policy — there is no write path in that module to disable.

Binding a channel to an account requires proof of both sides: a signed-in browser session mints a six-character code, valid ten minutes, redeemable once, and sending it from the handset completes the pair. No one can bind a number to an account they cannot already sign into.

Identifiers are hashed before storage. The binding table is the one place a phone number would sit beside a DIN, and that pairing is worth more to an attacker than either column alone.

Inbound webhooks are verified before they are parsed: HMAC-SHA256 against the app secret, compared in constant time. A webhook URL is public, and without that check anyone who learns it can impersonate the platform.

Who changed this, when, and why?

Built and running

The filing lifecycle records an actor, a reason and a timestamp on every state transition, and the inbox is DERIVED from those timestamps rather than written alongside them.

That choice is deliberate and it is the interesting one. An events table has to be written by something, and the thing that writes it eventually misses a case — and a missing row in an inbox is invisible, because nobody notices the notice that never came. Deriving means a notice exists exactly when the underlying fact does, and asking "what did this account look like on the day that scheme closed" answers itself.

Telling a director before the penalty starts, not after.

Built and running

This is the part the product is actually for, because the value is arriving before the money starts rather than explaining it afterwards. It is also the only part of this build that runs when nobody is looking at a page.

It is a separate Worker on its own Cron Trigger, on the pattern already used for the data pipeline, so a slow or failing delivery run cannot take the citizen portal down. Daily it reads the live subscriptions, recomputes each company through the same pure engine the website uses, and decides whether anything has changed enough to be worth saying.

Two properties decide whether it is trustworthy rather than annoying. It fires on a TRANSITION rather than on a day — thirty days out, seven days out, the day a filing becomes overdue, and once more at thirty days late — and each notice is claimed by an INSERT on a unique (subscription, transition) key, so the second attempt is refused by the database rather than by a conditional someone can forget. And it recomputes rather than storing a figure, so a reminder can never quote a number the portal disagrees with.

What is NOT working is delivery to arbitrary addresses: Cloudflare delivers only to verified Email Routing destinations, so most rows record `blocked-unverified` rather than pretending to have been sent. The scheduling, the arithmetic and the refusal to send twice are unaffected, and all three are visible in the run record.

Values in force in this deployment right now.
Workerbwmi-mca-reminders
Cron15 3 * * * (08:45 IST)
Idempotency key(subscription_id, transition_key)
Run record/reminders

The DSC problem, and what replaces it.

Designed, not built

Statutory filing needs a Class 3 digital signature, and today that means a desktop utility and a physical token — the single most common point at which a founder stops and hires someone. The utility exists because signing must happen on hardware the signer controls, and a browser historically could not reach it.

What replaces it is not a web signing form. It is a signing hand-off with a receipt: the portal produces the exact document set and a hash, the signature happens in the signer's own environment, and what returns is a signature over a hash the portal can verify against what it produced. The portal never holds the private key and never claims to.

What that gives up is honest to state: it does not remove the token, and it does not make signing browser-native. It removes the ambiguity about WHAT was signed, which is the part that currently generates disputes.

What happens at national scale?

Designed, not built

The read path is already the shape it would need to be. Every computation is pure and runs at the edge with no database round trip, so exposure and fee arithmetic cost nothing but CPU and scale horizontally by default. The register lookup is the only external dependency on a page load, and it is cached by query — seven days for a hit, one hour for a genuine miss, and never for a failure.

The write path is where the real work would be. Filing is a queue with a dead-letter queue, bounded retries and per-message acknowledgement, because a statutory submission must survive a bad afternoon at either end. That pattern is already implemented and deployed for the open-data ingest, which is deliberately a separate worker on its own cron for the same reason.

The bulk register is the honest bottleneck, and it was measured rather than estimated. On 28 August 2026 the catalogue was checked directly: it holds exactly ONE resource, there are no per-state files, and the state selector on the resource page feeds an asynchronous queued export rather than a download link. The only scriptable bulk route is the API with a State filter and offset paging — roughly 3,700 requests for 3,674,314 records.

The API rate-limits in short bursts. A paced download of Andhra Pradesh reached 58,000 rows before the first refusal, and the window cleared within minutes rather than the next day. So a full copy is achievable over hours, not minutes, and the downloader in this repository is resumable per State for exactly that reason.

The reason it has NOT been run is the interesting one, and it is an architecture point rather than a scheduling one: the bulk job and the live citizen lookup share one API key. Speed in the downloader is downtime in the portal. Two consumers with different latency needs cannot sensibly share one quota, which is precisely the argument for an ingest that writes an immutable snapshot into local storage and a read path that never touches the upstream at all. The ingest worker here already writes that way; the index it would feed is not built.

What this deliberately does not do.

Deliberately not built

It does not scrape MCA's authenticated or searchable databases, bypass CAPTCHA or OTP, replay tokens, or automate statutory submission. None of that is a limitation of effort; a prototype that automated a government login would be the wrong artifact regardless of how well it worked.

It does not connect a payment rail. If it ever did, it would be through an authorised aggregator's hosted checkout with signed server-side callbacks, and bank credentials would never reach this application.

It does not put a model anywhere near a legal conclusion. Due dates, fees, refusals and citations come from the engines and nothing else. Models are used in exactly two non-load-bearing places, and both re-enter through the same validators the deterministic path uses — a proposed State that does not exist is rejected no matter what proposed it.

It does not assert a trade mark position. Live trade mark search is gated behind human challenges, so this build reads published journals only and hands off to IP India rather than implying a clearance it cannot give.

Check any of this

Nothing above is meant to be taken on trust. /evidence runs the engines on this page with their inputs printed. /changelog carries the dated rule-set history and the numeric floors the test suite fails the build over. /whatsapp separates what is running from what is unprovisioned, secret by secret. /agent-health reports whether the one model-backed surface has ever breached its grounding contract.