Wages.software · Early access · the engine is built, the service is not

The wages engine, not the payroll service.

The national payroll providers sell you a result. You send the hours and a number comes back. That trade is a good one for most schools — but the arithmetic in the middle is not published anywhere you can read it, so when a number looks wrong there is nothing to check it against.

This is the arithmetic in the middle. Gross-to-net in exact integer cents, a separate taxable basis for every authority, federal withholding worked the way the IRS worksheet is actually written, FICA at rates held as exact rationals so 1.45 percent is never rounded, a NACHA batch proven against the structural rules a bank checks, and a W-2 to W-3 to 941 reconcile that blocks the year rather than balancing it for you. Each step is its own endpoint. All of it is legible.

And it disburses nothing, files nothing, and ships no tax tables. Those are the first three rows of our own comparison table, and the incumbents win all three.

12gate suites over the payroll engines, 62 tests, run green — golden paychecks, penny reconcile, taxable-basis mask, fail-closed sign-off, NACHA structure, the three-way filing reconcile, and six more
0tax rates invented. The table registry ships empty; an unsigned table withholds zero and returns the reason rather than a plausible number
0cents disbursed, ever. The disbursement gate returns honest-off with no partner wired, and no wired route sets that flag
11modules that are written, tested, and have no production caller — each named on this page under “built, not switched on” rather than counted as a feature

The honest frame

What this is, and what it is not.

Payroll software is one of the few categories where being confidently wrong is worse than being useless. A scheduling tool that guesses badly wastes an afternoon. A payroll engine that guesses a withholding rate produces a number that looks exactly like a correct number, on every paycheck, until a notice arrives from a taxing authority eleven months later. So this product was built around a refusal before it was built around a feature.

It is a calculation surface. Earnings and deductions go in as plain values on each request; a typed result comes back. There is no persistence in the wired payroll endpoints at all — the route module says so in its own header, in those words. Nothing is stored, so nothing drifts, and the same input always produces the same output. Every engine underneath is a pure function with no database, no clock, no network and no randomness.

It is not a payroll service. It does not pay anyone. The gate that would authorise a disbursement returns an honest-off verdict whenever no licensed partner is configured, and nothing in any wired route configures one. That is not a feature flag someone could flip by mistake in a config file; it is the shape of the code path.

It is not a tax-compliance service either. It does not file your 941, it does not transmit a W-2 to the Social Security Administration, and it does not ship the brackets. What it does is prove that a filing set agrees with itself before anyone files it, and refuse to compute a withholding from a table nobody signed.

And it is early access. The engines below are written and covered; the service around them is not built. We would rather you read that here than infer it from a quiet quarter after signing.

Built and reachable — a registered handler calls it

The engines that are actually wired.

Each of these is called by a registered API handler behind a tenant wall and a fail-closed finance-role gate. One honest bound on the whole section: reachable is a source-level claim. It means a handler calls the function. It does not mean anyone has run it against a production database, and where a surface reads a table that nothing writes yet, the card says so.

Gross-to-net arithmetic

Gross-to-net arithmetic — exact integer cents, and a paycheck that cannot go negative

Earnings in, deductions in, net out — every field an exact integer number of cents, never a float. Net floors at zero: a paycheck cannot be negative. And when deductions exceed gross, the over-deduction is flagged for review as its own signal rather than silently zeroed and forgotten — the floor being hit is exactly the case a human has to look at. A negative earning or deduction line is treated as zero rather than allowed to corrupt a sum.

back-office-payroll-engine.ts disburseStatus returns honest_off_no_partner. Nothing moves.

Per-authority taxable basis

Per-authority taxable basis — the number one real-money hazard, computed one authority at a time

Different pre-tax deductions reduce different tax bases, and a single wrong mask produces the wrong Social Security and Medicare withholding and the wrong W-2 Boxes 3 and 5 and the wrong 941 — one mistake, three wrong documents. So there is no single “taxable wages” number here. Each authority gets its own basis, computed from a signed per-deduction-class exemption mask, and each carries its own reconcile identity: basis equals gross minus what reduced it, and never falls below zero. An unsigned mask computes no basis at all — it does not guess “reduce everything” and it does not guess “reduce nothing.”

back-office-payroll-taxable-basis.ts No money. A basis is an input to a calculation, not a payment.

Federal withholding

Federal income-tax withholding — IRS Pub 15-T Worksheet 1A, step by readable step

The percentage method for automated payroll systems, written as the worksheet actually reads: annualize the wage, apply the Step 4a and 4b adjustments and the filing-status standard adjustment, look up the tentative annual tax in the supplied bracket table, de-annualize, subtract the Step 3 tax credit, add the 4c extra per-period amount. The per-period division floors down and the dropped sub-cent fraction is returned rather than rounded away. Every constant — the standard adjustment, the bracket table — is supplied and signed. This module invents no IRS figure.

back-office-payroll-federal-withholding.ts No money. An unsigned table withholds zero and names the reason.

FICA, employee and employer

FICA and Medicare — rates as exact rationals, because 1.45% is not an integer per-mille

Medicare is 1.45 percent. Expressed as an integer per-mille that is 14.5, which is not an integer — so a per-mille engine has to round the rate itself, and a rounded rate is wrong on every paycheck forever. This module takes the rate as an exact integer rational (145/10000, 62/1000, 9/1000) and floors the product, surfacing the dropped fraction. No rate is ever rounded. Social Security is capped against year-to-date wages; Medicare is uncapped; Additional Medicare is employee-only with no employer match, because that is the law and a symmetric implementation would have quietly over-charged the employer.

back-office-payroll-fica.ts No money. Unsigned config withholds zero.

Gross-to-net pay stub

The pay stub — one composed statement over the same primitives

The stub composition endpoint assembles the earning lines, the per-authority bases, the withholding lines and the net into a single statement, using the same primitives the individual endpoints expose. There is no second arithmetic path for “the stub version” of a number: if the FICA endpoint and the stub ever disagreed, one of them would be calling something that does not exist.

back-office-payroll-gross-to-net.ts No money. A statement is not a payment.

NACHA structural proof

NACHA PPD — the batch is built and proven, and the file is withheld

The direct-deposit builder assembles a balanced NACHA PPD batch as fixed 94-character records and proves the structural invariants a receiving bank checks: every record exactly 94 ASCII characters; each routing number passing ABA mod-10 with the 3-7-1 weights, and a bad routing withheld and flagged rather than sent; the entry hash agreeing between batch control and file control; block-fill padded to a multiple of ten; a single balancing debit whose amount equals the sum of the credits exactly; a zero-dollar prenote that holds instead of settling. Then the endpoint withholds the raw file and returns the proof, with transmitted and railLive both false. No socket is opened. There is no originating bank.

back-office-payroll-nacha.ts Structural proof only. No file leaves. No money moves.

W-2 / W-3 / 941 reconcile

The three-way filing reconcile — the year has to agree, or it blocks

Before anything could be filed, one identity has to hold: the sum of every employee W-2 equals the W-3 transmittal equals the sum of the four quarterly 941s. If it does not, the reconcile blocks and shows the leg that disagrees. On top of that the per-W-2 box invariants: Box 3 never exceeds the annual Social Security wage base, Box 5 is uncapped, Box 1 excludes pre-tax deferrals while Boxes 3 and 5 include them (so Box 1 is at most Box 5 by construction), and the 941 fractions-of-cents drift is carried as an explicit adjustment line, never hidden and never force-balanced to make a total look tidy. This proves a filing set. It emits no form and transmits nothing.

back-office-payroll-filing-reconcile.ts No money. Nothing is transmitted to the IRS or the SSA.

Four-eyes approval

Segregation of duties — four-eyes in the engine, not in the org chart

The approval decision refuses when the approver is the same person who assembled the run. Above a configurable total-gross threshold it requires a second, distinct approver who differs from both. And the approval binds to the register content hash: change a single line after sign-off and the hash changes, which invalidates the approval and forces a re-approve. Four-eyes written into a policy document is a hope. Four-eyes that fails closed in the function that returns the verdict is a control.

back-office-payroll-sod-approval.ts A decision, not a payment. This module commits nothing.

Employee self-service

Employee self-service — built end to end, and honestly inert

The self-service lane is built the whole way through: the decision module, thirteen registered endpoints, a web route, and a row-level isolation test. It is deny by default — a user may touch only the row whose employee reference equals their own server-resolved reference, a blank viewer authorizes nothing, and there is no widen path and no privileged branch anywhere in the lane. A direct-deposit submission is validated and returned, not stored: the routing number is checked against ABA mod-10, the account comes back masked to its last four digits, the full account is never echoed in any response, and the disbursement status is always honest-off. A W-4, direct-deposit or address submission computes a SHA-256 hash-chained audit event and returns it to the caller, with a one-way digest standing in for the sensitive value. Leave balances are counted in days, never money. Now the honest part, and it matters. Two things are missing, and they are the same kind of thing. The stub, W-2 and total-compensation views read pay tables that nothing in production writes, so they return an honest empty result until a run writer exists. And the change endpoints persist nothing at all — the repository behind this lane has five read methods and no write method, so a submitted election or address is validated, audited, returned, and then gone when the response ends. Built, wired, walled, and not switched on.

back-office-payroll-ess.ts No account is ever debited or credited, and no election is stored.

Quarterly 941 read

The employer quarterly 941 — assembled from movements, filed nowhere

The quarterly return is built by reading recorded payroll movements out of the database and assembling the employer figures. It files nothing and transmits nothing, and it returns a clean service-unavailable when no database is provisioned rather than a plausible-looking zero. Same honest caveat as self-service: it reads a table that has no production writer today.

back-office-payroll-941-940.ts / -941-feeder.ts No money. No transmission.

Hiring and HR records

Hiring and HR records — the one lane in this whole category that actually persists

Job requisitions, the application funnel, stage advancement, the hiring-approval and hire decision, the pipeline board, and the HR dashboard are all reachable — and unlike every payroll surface above, this lane writes to the database. Employee records, staff credentials, professional development activity, job postings, applications, leave balances and leave requests all have real persistence behind them. If you are looking for the part of this product that is finished rather than proven, it is this one. Leave is tracked in days; nothing in it touches money.

back-office-applicant-tracking.ts, hr-recruiting-funnel-ats.ts, hr-job-postings-catalog.ts No money anywhere in the hiring or records lane.

Built, not switched on

Eleven modules with no production caller. Here they are by name.

This table is the part of the page we would remove if we were selling harder. It is here because the characteristic dishonesty of a technical product page is to describe a green test suite as a working feature, and this codebase has eleven places where that temptation exists. Every module below is written, is covered by tests, and is imported by nothing outside its own siblings and its own test files. None of them is reachable from any API route.

The largest consequence is worth stating plainly rather than burying in a row. Five payroll tables exist in the database with row-level security on them, and the module that would create a payroll run is in this table. So nothing in production writes a pay run — which means the employee self-service views and the quarterly return read correctly-wired tables that have no producer. They are built. They are walled. They will return an honest empty result until a run writer exists. That is the single most important sentence on this page.

There is a second absence with the same shape, and it is not in the table above because it is not a module — it is a missing write path. The payroll surfaces persist nothing. The wired calculation routes say so in their own header, in those words. The self-service lane goes one step further and is worth naming precisely: its repository exposes five read methods and no write method at all, so a W-4, a direct-deposit election or an address submitted by an employee is validated, masked, audited and returned, and then it is gone. Read the whole page with that in mind. Everything below is arithmetic and access control, both of which are real. None of it is storage.

ModuleWhat it doesWhy it is not reachable
Payroll run workflowThe whole run lifecycle — assemble, review, approve, post, disburse, void — plus the register content hash the four-eyes approval binds to.Nothing under apps/ imports it. This is the module that would write a pay run, and its absence is why the pay tables have no producer.
W-2 / W-3 / SSA EFW2 e-fileBuilds the W-2 and W-3 figures and the fixed-width EFW2 records the Social Security Administration accepts, and verifies them.No production caller. The three-way reconcile (which IS reachable) proves a filing set agrees; this module, which would produce the actual file, is not wired.
Form 940 FUTA and the annual 941 setThe federal unemployment return and the full-year 941 assembly.No production caller. The single-quarter 941 build is reachable; these two siblings are not.
Run reversal and off-cycle runsBuilds a reversing run and assembles an off-cycle run.No production caller. It reverses a run that nothing can currently create.
Payroll audit hash-chain ledgerAppends run-lifecycle events to an append-only hash chain and verifies the chain end to end.No production caller. The self-service change lane computes its own chain event on each request, but nothing stores it and its verifier has no production caller either, so neither chain is a working audit log today.
General-ledger posting and labor distributionBuilds and posts the payroll accrual entry and distributes labor cost across accounts.No production caller. Its gate suite proves the entry balances; nothing calls it.
Year-to-date accumulatorApplies YTD postings idempotently, tracks wage-base caps and the elective-deferral headroom, and builds reversal postings.No production caller. The FICA engine takes YTD wages as a supplied input instead.
SME-signed tax-table registryHolds effective-dated, signed withholding tables and resolves the one that applies, holding on no table, an ambiguous overlap, an unsigned table or a malformed one.No production caller yet. The withholding engines take a supplied table directly. The registry is the framework that would govern them.
Payroll AI corridorThe grounding wall for an assistant that answers questions about pay: self lane filtered to the viewer's own rows, and SSN, bank routing, account number, direct deposit and tax id never admitted to the grounding set at all — not even for an HR administrator.No production caller. There is no assistant wired to payroll data, which is the safest possible state for it to be in.
Timesheet to earningsConverts timesheet entries into earning lines.No production caller. Its siblings in the same file (the run preview, hourly and salary earning helpers) are reachable; this one is not.
Onboarding workflowThe new-hire onboarding task model.No production caller. The hiring decision and the persisted employee record are reachable; the onboarding sequence between them is not.

Exact integer cents

Money is an integer here. All the way down.

Every money field in every engine is an exact integer number of cents. Not a decimal type, not a float, not a dollar figure that happens to have two places. There is no arithmetic path in this product where a fraction of a cent can be created and then quietly disposed of, because rounding drift in a payroll ledger is not a small bug — over a year of periods and a few hundred employees it is the bug, and it is the one that is hardest to find afterwards because every individual number looks fine.

Where a division genuinely produces a fraction — annualised withholding divided back down to a pay period, or a rate applied to a wage — the result floors down and the dropped sub-cent fraction is returned to the caller. It is not rounded to nearest and forgotten. That fraction is real money in aggregate, and the engine hands it back so the caller can account for it rather than discovering it as an unexplained variance.

The same discipline runs into the year-end forms. The 941 fractions-of-cents difference is carried as an explicit adjustment line in the reconcile, exactly as the form provides for. It is never hidden inside another figure and never used to force a total to balance. If the quarters do not agree with the W-2 set, the reconcile blocks and shows which leg disagrees; it does not quietly absorb the difference into the adjustment and report success.

Two smaller guarantees follow from the same posture. A paycheck cannot be negative: net floors at zero. But an over-deduction is not silently zeroed either — it is raised as its own separate signal, because hitting that floor is precisely the case that needs a human to look at it. And a negative earning or deduction line arriving as input is treated as zero rather than allowed to corrupt a sum.

The number one real-money hazard

There is no such thing as “taxable wages.” There are several, and they differ.

This is the mistake that costs the most and shows the least. Different pre-tax deductions reduce different tax bases, and treating them as one number produces the wrong Social Security and Medicare withholding, and the wrong Boxes 3 and 5 on every W-2, and the wrong 941 for every quarter. One conceptual error, three wrong documents, discovered at year end.

The rules are not obscure, they are just easy to collapse. A Section 125 cafeteria deduction, an HSA or an FSA reduces federal and state income tax and FICA and FUTA — the full pre-tax treatment. A 403(b) or 457(b) elective deferral reduces federal and state income tax but not FICA, which is exactly why Boxes 3 and 5 of the W-2 still include it while Box 1 does not. A 414(h)(2) employer pickup reduces federal income tax but not FICA, and the state treatment varies. A post-tax deduction or a garnishment reduces nothing at all.

One period, two different taxable basesDollars
Gross wages for the period4,000.00
Section 125 medical premiumReduces federal and state income, and FICA, and FUTA-200.00
403(b) elective deferralReduces federal and state income — but NOT FICA-300.00
Union dues, post-taxReduces nothing-25.00
Federal income tax basisGross less s125 less 403(b)3,500.00
Social Security and Medicare basisGross less s125 only3,800.00

Illustration with round example figures. Note the two bases differ by three hundred dollars on a single ordinary paycheck, because a 403(b) deferral reduces income tax but not FICA. A product that carries one “taxable wages” number through the year gets Boxes 3 and 5 of the W-2 wrong by that difference, every period, and the error only surfaces at year end. This figure contains no tax rate, because this product ships none.

So the engine computes an independent basis for each authority from a per-deduction-class exemption mask, and each basis carries its own reconcile identity that has to hold: the basis equals gross minus what reduced it, and it never falls below zero. The mask is supplied and signed by a subject-matter expert. The module encodes none of the rules above as a default — they are documented in the source for a signer to read, and the signer supplies them.

And when the mask is not signed, the engine computes no basis at all. It does not fall back to “reduce everything” and it does not fall back to “reduce nothing.” Both of those are wrong in a way that produces a number, and a wrong number is worse than a refusal.

Why we ship no tax tables

The registry ships empty. That is the feature.

Maintained statutory tax tables are a standard part of what a full-service payroll provider advertises, and updating them for you each year is genuinely valuable — a real reason to buy from one. What we can say about our own side of that trade is that it is the opposite bargain: nothing here is applied on your behalf without a signature you can point to.

We took the other side of that trade, on purpose. The table registry ships with no entries. Tables are supplied and an expert signs them, and the registry records the sign-off — it does not sign anything itself. Then resolution is strict in four separate ways, each with its own named outcome:

  • No table matches the jurisdiction, kind and tax year at the supplied pay date — held, and the withholding calc returns zero.
  • More than one matches, because two effective windows overlap — held as ambiguous. It does not pick one. An overlap is a data error, and choosing arbitrarily is how a whole quarter gets computed against the wrong brackets.
  • A table matches but is unsigned — held, pending sign-off. The rates might even be right. Nobody has said so on the record, so it does not run.
  • A table matches but is malformed — held as invalid, rather than half-applied.

In every held case, the downstream withholding is zero and the reason travels with it. Zero is obviously wrong to anyone who looks at the stub, which is the point: a visible refusal gets fixed on Tuesday, and a plausible guess gets discovered in January. The engines invent no rate, no bracket and no wage base, and the registry signs nothing on anyone’s behalf.

The honest cost of this design, stated plainly: it means you need somebody who can sign for the tables. If you do not have that, a full-service provider that maintains them for you is a better fit than we are, and we would tell you so rather than sell you a registry you would leave empty.

Controls that are code, not policy

Four eyes, and a signature bound to what it signed.

Segregation of duties is usually a paragraph in a finance manual and a habit in an office. Here it is the function that returns the verdict. The approval decision refuses outright when the approver is the same person who assembled the run. Above a configurable total-gross threshold it requires a second, distinct approver who differs from both of the others. Neither of those is a warning; both are refusals.

The part that matters most is subtler. The approval binds to the content hash of the register it approved. If a line is edited after sign-off, the hash no longer matches and the approval is invalid — a re-approve is required. This closes the gap that makes most approval workflows theatre: approving a run and then changing it is the entire attack, and a status flag on a record cannot detect it.

Alongside that, an employee-initiated change to a W-4, a direct deposit election or a mailing address computes a SHA-256 chain event for itself. The event’s detail reference is a one-way hash of what changed, so the event can bind the change without carrying the sensitive value. The chain arithmetic is genuinely correct: a verifier walks a chain, recomputes every entry hash, and reports the index where a back-dated edit broke it. Gate 10 proves exactly that, and it passes.

Two scope bounds on this section, kept here rather than in a footnote, because both of them change what the controls are worth today.

The four-eyes decision is stateless. It returns a verdict; it does not persist an approval, because the run-persistence lane it would write to has no producer yet. It is a correct control that nothing currently drives.

And the change chain is computed but never kept. Nothing in production stores an event, and the verifier that would detect a back-dated edit has no production caller — it is exercised by its own gate suite and nowhere else. The chain position even arrives as a request parameter, defaulting to genesis, so there is no server-held head for a new event to extend. What exists today is correct hash-chain arithmetic and an honest absence of a log to run it over. Tamper-evidence is a property of a stored chain, and we do not yet store one.

The disbursement rail

We built the file format. We did not build a way to send it.

The NACHA builder assembles a real, balanced PPD direct-deposit batch as fixed 94-character records and proves the invariants a receiving bank actually checks. Every record exactly 94 characters. Each routing number validated by ABA mod-10 with the three-seven-one weights, and a failing routing withheld from the batch and flagged rather than sent and rejected. The entry hash agreeing between the batch control record and the file control record. The block count padded with all-nines records to a multiple of ten. A single balancing debit whose amount equals the sum of the credits exactly. A multi-account split summing to the employee’s net. And a zero-dollar prenote that holds for account verification instead of settling as a live credit.

Then the endpoint hands back the structural proof and withholds the raw file. The response carries a transmitted flag and a rail-live flag, and both are false. No socket is opened anywhere in the module. There is no originating financial institution behind this, and there is no code path that would send the batch if one appeared — connecting a licensed institution is a deliberate decision by a person, not a configuration value.

Which is why the honest description of this capability is narrow and specific: we can prove a direct-deposit batch is structurally correct before it goes anywhere. We cannot send it. Both halves of that sentence are load-bearing.

Against the full-service model

Ten rows. The full-service model wins the first three, outright.

Most vendor comparison tables are a list of things the author is good at, and everyone reading knows it. So here are the three axes where a full-service payroll provider beats us, first, without qualification: they run payroll and move the money, they file your returns and carry the penalty exposure, and they maintain the statutory tables for you every year. Those are not small. For most schools those three are the whole reason to buy payroll software at all, and if that is what you need, buy it from a provider that does it.

The seven rows underneath are the axes where we are better, and there is a discipline in how they are written that matters more than the result. We describe our own behaviour precisely, because we can read our own source. Where the other column would require us to assert what another company’s code does internally, it says we have not established it — because we cannot, and a guess in the direction that flatters us is not a comparison. An earlier version of this table claimed a competitor rounds sub-cent fractions internally and models an approval as a status flag. We could not source either sentence from anything, so both are gone.

The left column states our own measured behaviour. The middle column states only what the category openly advertises, or that we could not establish it. Rows are marked for whoever wins them.
What you are buyingA full-service payroll providerWages.software
Runs your payroll and moves the moneyYes. It is the advertised product of every full-service provider, and it is the reason most schools buy one.No. Not built. The disbursement status is hard-wired to honest-off in every wired route.
Files your 941 and W-2 with the IRS and the SSAYes. Filing on your behalf, with the penalty exposure carried, is a standard advertised service.No. We reconcile a filing set and prove it agrees. We transmit nothing.
Ships current statutory tax tables, maintained for youYes. Maintained statutory tables are a standard advertised service, updated for you each year.No. The registry ships empty. You supply the tables and a subject-matter expert signs them.
The withholding arithmetic is published for you to readNot that we have found published. We make no claim about how any provider computes internally.Yes. Pub 15-T Worksheet 1A, in one readable module, in the order the worksheet reads.
Each calculation step callable on its own, as an endpointWe do not audit other vendors' APIs and will not characterise them here.Yes. Taxable basis, federal withholding, FICA, stub, NACHA structure and the filing reconcile are each their own endpoint.
What this product does when a tax table is missing or unsignedNot established. We cannot see another provider's behaviour in this case, so we do not describe it.It withholds zero and returns the reason: no table, ambiguous window, unsigned, or malformed. Never a plausible-looking guess.
Sub-cent fractions are surfaced to the callerNot established. Whether a provider surfaces the dropped sub-cent fraction is not something we can read from outside, so we do not describe it.Floored, with the dropped fraction returned. The 941 fractions-of-cents drift is an explicit adjustment line.
A separate taxable basis per authority, exposedNot established as an exposed surface. The finished form shows one taxable-wages figure per box.Each authority's basis computed independently from a signed exemption mask, each with its own reconcile identity.
An approval is bound to the content it approvedNot established. We cannot inspect another product's approval model and will not guess at it.The approval binds to the register content hash. Change one line afterwards and the approval is invalid.
The calculation runs with no database, clock or network of its ownA hosted service processes your payroll on its own infrastructure; that is the model being bought.The engines are pure functions with no database, no clock and no network; the routes sit behind a tenant wall and a fail-closed finance-role gate.

The K-12 back office, and the two seams we have not wired

In schools specifically the incumbent is usually a district ERP that owns the whole back office and treats payroll as one module inside it. We are not that shape and are not trying to become it: no suite, no system of record, no ambition to be the place your general ledger lives. Where we are useful next to an ERP is narrow and real — you can read our calculation, call one step of it, and reconcile the year against it independently. If a district ERP hands your business office a number nobody can explain, this is what you point at it. We are not going to characterise how any particular ERP computes internally, because we have not read it.

Two seams are deliberately unwired and we would rather name them than let you find them. There is no electronic signature: the endpoint returns a clear service-unavailable saying e-sign is not provisioned. We have not integrated any third-party signing provider and we have not written a signature of our own, and recording a signature we did not collect would be a far worse failure than refusing. And there is no benefits carrier connection: an election is held locally as pending and the carrier submission endpoint refuses the same way. Nothing is sent to any carrier.

One note on the category, since this page makes a claim about it. The full-service payroll market this product is measured against includes Gusto, ADP and Paylocity; the K-12 back-office ERP market includes Skyward; and the e-signature seam we have not wired is served by products such as DocuSign and Dropbox Sign. They are named here once, only as factual category references, and none of their code, copy or design is used anywhere in this product. Nothing above is a claim about how any of them is built internally — we have not read their source and cannot, so every row of the table either cites our own measured behaviour or says outright that we could not establish theirs. Their advertised capabilities are their own to describe, and any of them may ship any of the things in our column tomorrow.

Employee self-service

Built the whole way through, and honestly inert.

The self-service lane is the most complete vertical slice in this product: a decision module, thirteen registered endpoints, a web route, and a row-level isolation test that proves the database wall independently of the application code. Eight of those endpoints compute over values supplied on the request; five read stored rows directly. The lane covers a pay stub, a W-2, leave balances, total compensation, and submissions for a W-4, a direct deposit and a mailing address.

The wall around it is a single idea applied without exception. It is deny by default: a user may touch only the row whose employee reference equals their own, and that reference is resolved from the server-side session, never taken from the request. A blank viewer authorizes nothing. There is no widen path, no privileged branch, and no employee picker anywhere in the lane — the administrative side is a separate surface with a separate gate, not a parameter on this one.

Direct deposit deserves its own sentence because it is where this kind of feature usually leaks. The routing number is validated on the way in against ABA mod-10, the account is returned masked to its last four digits, and the full account number is never echoed back in any response — which is the actual defence against payroll diversion, where an attacker who can read the existing account is most of the way to redirecting it. The disbursement status attached to it is always honest-off, because there is no rail.

A W-4, direct-deposit or address submission also computes a SHA-256 hash-chained audit event and returns it, with a one-way digest standing in for the value that changed. Leave balances are counted in days and never converted to money anywhere in this lane.

And now the two things that decide how you should read all of it. First, the stub, W-2 and total-compensation views read the pay tables, and no production code writes those tables — the module that would create a run is not wired. Second, and we would rather be blunt than clever about it: the W-4, direct-deposit and address endpoints store nothing. The repository behind this lane exposes five read methods and no write method, and the route handler returns the validated change and its audit event without committing either. The audit event is also a single event rather than a running chain: the chain position arrives on the request and defaults to genesis, so each submission is hashed against whatever head the caller supplied rather than against a server-held history.

So an employee logging in today would see an honest empty result, and a submitted election would be validated, masked, audited and then discarded when the response ended. Every wall described above is real and every check runs. Nothing behind them retains anything. This surface is built, not switched on, and we will not describe it any other way until a run writer and a change store exist.

Hiring and HR records

The lane that is finished rather than proven.

Everything above this section is arithmetic waiting for a rail. This section is the exception, and it is the strongest thing we can honestly sell today.

Applicant tracking is reachable and, uniquely in this whole category, persisted. A job posting is opened and written; an application is submitted and written; the application advances through stages; a hiring approval is opened and a hire is approved, rejected, or made. The funnel census, the pipeline board and the summary all run over the same records. The requisition catalog builds job requirements and catalog cards and closes postings. An HR dashboard composes the lot.

Behind it, real writes: employee records, staff credentials, professional-development activity, job postings, job applications, leave balances and leave requests all have persistence, through the canonical repository rather than through a second store invented for this lane. Leave is tracked in days and never in money.

Two honest bounds. Onboarding — the task sequence between a hire decision and a first day — is written but has no production caller; it is in the built-not-switched-on table above. And a caveat carried in the route module’s own header rather than discovered by us: the HR-officer capability is not yet plumbed into the session, so the gate that runs in practice today is the administrator tier rather than a distinct HR-officer role. It is a real access wall; it is not yet as finely divided as it is designed to be.

Twelve gates, sixty-two tests

What is actually proven, and what a passing suite does not prove.

Twelve gate suites cover the payroll engines and they run green: sixty-two tests across twelve files. They were run before this page was written, and the numbers here are that measurement rather than a target.

The bound is as important as the result. A green suite proves a function computes the right answer. It says nothing whatever about whether anything calls that function — and five of the twelve below exercise modules that have no production caller at all. Those are flagged in place, on the chip, rather than mentioned once at the bottom.

Gate 01 Golden paychecks Known-answer paychecks come out to the cent.
Gate 02 Penny reconcile The lines sum to the total exactly, with no rounding drift.
Gate 03 Taxable-basis mask Each authority's basis is independent and satisfies its own identity.
Gate 04 SME gate fail-closed An unsigned or malformed table withholds zero and names the reason.
Gate 05 NACHA structural 94-character records, mod-10 routings, a balanced batch, a held prenote.
Gate 06 Filing three-way W-2 sum equals W-3 equals the four 941s, or the filing blocks.
Gate 07 GL balance The payroll accrual entry balances. Exercises a module with no production caller
Gate 08 YTD idempotency and caps Re-applying a posting changes nothing; wage-base caps hold. Exercises a module with no production caller
Gate 09 Four-eyes Approver differs from builder; the threshold forces a second approver.
Gate 10 Audit hash-chain A back-dated edit breaks the chain and is detected. Exercises a module with no production caller
Gate 11 Run-workflow lifecycle The run states advance only in the legal order. Exercises a module with no production caller
Gate 12 Reversal round-trip A reversal returns the register to its prior position exactly. Exercises a module with no production caller

Data, walls, and what we hold

Staff data, walled at the row. Not children’s data.

Payroll and HR are staff-coupled surfaces. The records here carry opaque staff and employee references and no student identity, and they sit deliberately outside the student census wall rather than inside it. No minor’s data enters this product at any point, which is a design decision rather than a happy accident: a payroll engine has no business holding a roster.

Access runs through three layers in order. First the tenant wall: every handler resolves the school before anything else, and a request for another tenant’s school comes back as not-found rather than forbidden, so the wall does not confirm that the row exists. Then a fail-closed role gate — a request that arrives without a resolvable finance role is refused, not waved through — plus a module entitlement check. Then, on the self-service lane, the deny-by-default self wall described above. On the hiring and HR surfaces, a separate access gate that hard-denies sales-side roles outright.

The hard-sensitive fields have their own treatment. Bank account numbers are shown masked to the last four digits and are never returned in full — and on the self-service lane they are not stored at all, because those endpoints persist nothing. And in the grounding wall written for a future payroll assistant, Social Security numbers, bank routing numbers, account numbers, direct-deposit details and tax identifiers are never admitted to the grounding set at all — not for an employee, not for an HR administrator, with no wording or framing that produces them. That wall has no production caller today, which is the safest state it could be in.

To be exact rather than flattering: this is not a claim that we hold no data. An employee record is data, and compensation data is among the most sensitive a school keeps. The honest claim is narrower — it is staff data, referenced opaquely, walled at the row and at the tenant, masked where it is dangerous, and it is not children’s data.

Common questions

The questions a business office actually asks.

Can Wages.software run our payroll?

No, and it is important that we say so in those words. There is no disbursement rail. The gate that would authorise a payment returns honest_off_no_partner in every wired route, because no licensed payroll partner is configured and nothing in the code sets that flag. What it can do is compute the numbers a payroll run is made of, and prove they hold together. If you need the money to move on Friday, you need a full-service payroll provider, and we will say that on a call too.

Then what is it for?

Three honest uses. First, verification: run your existing provider's figures through the engines and see whether the basis, the withholding and the year-end reconcile agree with what you were sent. Second, integration: each calculation step is its own endpoint, so a finance system can call the piece it needs instead of adopting a whole suite. Third, control: the four-eyes approval, the content-hash binding and the hash-chained change log are the parts an auditor asks about, and they are enforced in the function that returns the verdict rather than in a policy document.

Does it ship tax tables?

No. The registry ships empty, and that is deliberate. If no table matches, the resolution is held for “no table.” If two effective windows overlap, it is held for “ambiguous” — we will not pick one arbitrarily. If a table is present but unsigned, it is held for “needs sign-off,” and the withholding calc returns zero rather than an invented rate. A payroll engine that guesses a rate is worse than one that refuses, because the guess looks exactly like an answer.

Twelve gate suites pass. Does that mean the features work?

It means the arithmetic is correct. It does not mean every module is connected to anything, and five of the twelve exercise modules that have no production caller at all — general-ledger posting, the year-to-date accumulator, the audit hash chain, the run workflow and the reversal round-trip. Those are listed by name on this page under “built, not switched on.” A green suite proves a function is right; it proves nothing about whether anyone calls it. Gate 10 is the sharpest example: it proves a back-dated edit breaks a hash chain, and nothing in production keeps a chain for it to protect.

The self-service portal is built. Can an employee log in and see a pay stub?

No. The lane is built end to end — the decision module, thirteen endpoints, a web route, and a row-level isolation test — and it is walled correctly: own row only, deny by default, no widen path. But the pay tables it reads have no production writer, because the module that would create a run is one of the ones that is not wired. So an employee would log in and see an honest empty result. The same answer applies in the other direction: they could submit a W-4 or a direct deposit and it would be validated, masked and audited, but nothing would be saved, because the change endpoints have no write path behind them. That is the accurate answer, and we would rather give it than let you discover it in a pilot.

How is this different from what a school ERP already does?

A school ERP owns the whole back office and treats payroll as one module inside it. We are the opposite shape: no suite, no system of record, just the calculation surface with the arithmetic legible and each step callable. If your district already runs an ERP, this does not replace it. It is the thing you point at the ERP's output when a number looks wrong and nobody can tell you why. We will not tell you how your ERP computes internally: we have not read its source and cannot, so that is not a comparison we are able to make honestly.

Is there electronic signature for HR documents?

No, and the API says so out loud: the signature endpoint returns a service-unavailable with the reason “esign not provisioned.” We have not wired any third-party signing provider, and we have not written a signature of our own. An evaluation can be advanced to signed through the status workflow once a real signing provider is connected. Returning a clear refusal is the honest behaviour; recording a signature we did not actually collect would not be.

What about benefits carriers?

Same posture, with one exception worth being exact about. An election is validated against the open-enrollment window and the employment lifecycle and comes back with its carrier status held at “pending,” and the act of electing does write an entry to the audit log — that part is real persistence. What does not exist is a benefits table of record: the election itself is returned to the caller rather than stored as a row, and the plans and elections a status call reasons over are supplied on the request. The carrier-submission endpoint returns a service-unavailable with “benefits carrier not provisioned.” No carrier partner is wired and nothing is submitted to anyone.

Who can call these endpoints?

Every payroll and finance handler resolves the school through the tenant wall first — a request for another tenant's school is a not-found, never a forbidden, so the wall does not leak the existence of the row — and then passes through a fail-closed finance-role gate and a module-entitlement check. The self-service lane is stricter still: the employee reference comes from the server-resolved session, never from the request, and there is no employee picker anywhere in it. The hiring and HR surfaces sit behind their own access gate that hard-denies sales roles.

Does any of this touch student data?

No. Payroll and HR are staff-coupled surfaces. The records carry opaque staff and employee references and no student identity, and they are deliberately outside the student census wall rather than inside it. This is not a “we hold no data” claim — an employee record is data, and quite sensitive data. The honest claim is narrower: it is staff data, referenced opaquely, walled at the row, and it is not children's data.

Will an assistant answer questions about my pay?

Not today. The grounding wall for that is written — it filters an ordinary employee to their own rows before anything can be generated, and it never admits a Social Security number, a bank routing number, an account number or a tax id to the grounding set at all, not even for an HR administrator — but it has no production caller. There is no assistant wired to payroll data, and that is the right state for it to be in until the wall has been exercised for real.

What does it cost?

There is no price on this page and no checkout anywhere on this site. This is early access: the engines are built and the gates are green, and the service around them is not. The honest next step is a conversation about which of the three uses above matches what your finance office actually needs.

Is Wages.software a school, a district, or a nonprofit?

No. It is a for-profit software product built by Stanley Studios. It is not a school, not a district, and not a nonprofit, and nothing here is a charitable gift of any kind. This is software a school buys, and we are precise about that difference because it matters.

Related surfaces

Adjacent products, each its own thing.

Wages.software is money out to staff. These are the neighbouring rails, kept as separate products rather than folded into a suite.

procurement.center

The submission half of a school’s requisition rail: a finance officer files a purchase request in exact integer cents and the budget fit is checked read-only before it moves.

procurement.management

The approval and budget-control half of the same rail, where approval encumbers the budget. Kept as a separate surface on purpose.

tuition.software

The family-billing side of a school’s money. Money in from families is a different product from money out to staff, and we keep them apart.

What this page is and is not claiming

The whole posture, in one paragraph.

Wages.software is early access: the engines are built and the twelve gate suites run green, and the service around them is not built. It does not run payroll — the disbursement gate returns an honest-off verdict with no licensed partner wired, and no wired route configures one. It files nothing: the three-way reconcile proves a W-2, W-3 and 941 set agrees, and transmits none of them. It ships no statutory tax tables — the registry is empty, an unsigned or ambiguous table is held, and the withholding falls to zero with the reason attached rather than to a guess. The NACHA builder proves a batch is structurally correct and withholds the file; nothing is transmitted and there is no originating bank. Eleven modules are written, tested and have no production caller, and they are named above rather than counted as features. Nothing in production writes a pay run, so employee self-service and the quarterly return read tables with no producer and will return an honest empty result until that changes. The payroll surfaces persist nothing: the self-service change endpoints validate, mask and audit a W-4, a direct deposit or an address and then keep none of it, because the repository behind them has no write method, and the hash-chain verifier that would make an audit log tamper-evident has no production caller. There are no adoption counts, no customers named, no testimonials and no invented figures anywhere on this page; the only measurement quoted is twelve gate suites and sixty-two tests, run green before the page was written. Payroll and HR records are staff-coupled and carry no student identity. There is no pricing and no checkout on this site. The honest next step is a conversation.

Wages.software is early access. The calculation engines below are written, wired behind a fail-closed finance-officer gate, and covered by twelve gate suites that run green. What does not exist is a disbursement rail, a filing transmitter, or a set of statutory tax tables -- and we would rather tell you that on the first screen than in a renewal call.