All posts
Certant StrataComplianceDocument AIProvenance

Page 146: the number nobody was going to find

Two people paid below a legal minimum buried in a 147-page agreement, found and proved from the page, then fixed with a human signature.

Daniel Voyce··10 min read

A table on page 146 of the Meridian Care enterprise agreement decides what people get paid. Schedule J, minimum hourly rates by classification and level, 44 rows deep, on page 146 of 147, legally binding for three years. Nobody at Meridian has read it. Two employees are already below it: 20.00 and 17.50 an hour, against a floor of 21.34.

Certant PDF viewer on page 146 of 147, Schedule J minimum hourly rates, DDSO 2 at $21.34
Page 146 of 147, DDSO level 2 the second row at $21.34. Captured 13 July 2026 against the seeded Meridian Care knowledge base.

The number that runs a payroll sits in a document, not a database. This walkthrough shows the breach first, then how Strata built the check behind it.

Meridian Care is a fixture, seeded by scripts/strata_demo/seed_demo_kb.py into a real knowledge base (kb-21aa1f74da264ebc95d2a32ff34ae6af). The screenshots below are live captures of the product, but the company and the two employees are invented. The document shape is real; I come back to the 414-page agreement it was modelled on at the end.

What the cockpit shows first

The operations cockpit is where open exceptions land across every knowledge base; selecting the pay-floor alert gives this.

Certant Operations Cockpit showing 2 Employees affected and two breaching rows, E1042 at 20.00 and E1088 at 17.50, both compared against 21.34
The cockpit on 13 July 2026: two breaching rows, each showing the value it was compared against (21.34) and a source chip to page 4 of the payroll export.

E1042, A. Nguyen, DDSO level 2, is on 20.00. E1088, M. Torres, DDSO level 2, is on 17.50. Both rows carry a compared against column showing 21.34. You don't have to trust a red badge: the reason sits in the next column.

That column is newer than the feature. At the July demo review a flagged rate didn't show the floor it broke, making the alert unverifiable on sight; the fix made link-mode dynamic thresholds emit the violated bound on every breach row, in the cockpit and the triggering-rows tables.

Three PDFs

Three files go into a knowledge base to start: the agreement, the payroll export and the HR register.

Certant knowledge-base wizard with three fully indexed PDFs: the agreement, the payroll export and the HR register
The simple wizard after processing. All three sources fully indexed; the agreement was parsed with GLM-OCR.

The agreement is 0.4 MB, the payroll export 9 KB, the HR register 6 KB. They're parsed into tables, which makes everything downstream a query rather than a summary. The Schedule J table comes out with its page and region recorded, and the audit at the end depends on that record.

The working model

From those three files, Strata mints object types and the links between them.

Certant Data Model tab with four object type cards: AwardFloor, CompensationRate, Employee and JobClassification
Four object types in version 2 of the model. AwardFloor carries the Schedule J minimums; CompensationRate carries the payroll rates and wears a unique-constraint warning on employee_id.

This is an ontology: a rate knows which role governs it, a role knows who holds it. AwardFloor is keyed on classification and level, the detail that used to break things. Real agreements key a pay scale on more than one column; the previous join parser returned nothing for an AND condition, so the related and impact endpoints degraded to "join condition not traversable". Segment 054 replaced it with a term parser that splits the composite into per-column equalities and builds the join as an AND of those, backed by 120 unit checks and 22 live checks. Anything outside that grammar, an OR, a literal, a subquery, still returns nothing and degrades rather than guessing: the grammar is an allowlist with no raw-SQL escape hatch.

The cards also carry data-quality rules: CompensationRate.employee_id is marked unique, and hourly_rate has a range of 0 to 200. Both produce their own alerts, and one is already open in the cockpit queue.

The limit is read from the agreement

The compliance question is "is anyone paid below the minimum that governs them", and the tempting build is to type 21.34 into a threshold box. Then page 146 changes and the check is quietly wrong, with nothing on screen to say so.

Certant monitor builder configuring a dynamic threshold: hourly_rate less than the linked AwardFloor min_hourly_rate
The monitor being authored: the right-hand side of the comparison is a link traversal, not a constant, and no number is typed anywhere on this form.

The stored config is four lines:

{ "kind": "dynamic_threshold",
  "left":  { "property": "hourly_rate" },
  "operator": "<",
  "right": { "link_type": "governed_by", "property": "min_hourly_rate" } }

There's no SQL in that config, and the user never writes any. The engine compiles it: validating every identifier against the active projection's property list, re-quoting it, and taking the operator from a fixed allowlist. The one-hop remote comparison compiles to a correlated EXISTS, so exactly one row survives per instance, keeping a join fan-out from inflating the counts or destabilising the fire-once fingerprint:

SELECT s.* FROM "ontology"."compensation_rate" s
WHERE TRY_CAST(s."hourly_rate" AS DOUBLE) IS NOT NULL
  AND EXISTS (
    SELECT 1 FROM "ontology"."award_floor" t
    WHERE t."classification" = s."classification"
      AND TRY_CAST(t."min_hourly_rate" AS DOUBLE) IS NOT NULL
      AND TRY_CAST(s."hourly_rate" AS DOUBLE) < TRY_CAST(t."min_hourly_rate" AS DOUBLE))

Enabled monitors run on a five-minute tick; this one has a check interval of 3600 seconds, so in normal operation nobody presses anything. During the recording we pressed Evaluate now, the same code path with the wait removed.

The row that says we don't have the data

Every evaluated row gets one of three verdicts: breach, compliant, or no_data. A rate whose classification and level have no castable floor in Schedule J is recorded as no_data with the reason right_missing, and it never rounds down to "fine".

Certant alert history with the dynamic-threshold alert carrying a chip reading 1 no-data, right_missing
The alert history for the same run: the chip on the dynamic-threshold row reads 1 no-data · right_missing, sitting beside the two breaches rather than folded into them.

This is the five seconds of the demo I care most about; it took a fixture change, not a code change. The three-way evaluation shipped in segment 056; the recording needed a seeded rate row whose classification isn't in the agreement at all, so a viewer sees the refusal happen rather than hearing me claim it. I'd rather read that verdict than a confident one: it tells me the comparison ran and came up short of evidence.

Who is affected, counted rather than estimated

The next question is how many people this touches. In the cockpit panel above, the answer is 1 JobClassification and 2 Employees, reached through applies_to, with the sample instances named on screen: DDSO · 2, E1042, E1088.

Those integers come from a declarative impact rule evaluated on the same data snapshot as the alert fingerprint, committed in the same DuckDB writer transaction as the fire-once claim and the alert insert. It counts COUNT(DISTINCT <instance-key tuple>) per hop, not distinct rows, so a two-column key can't multiply anybody. The acceptance gate for that segment asserts the integers byte-match an independent hand-run of the same DISTINCT-key SQL. Where a grounded count is unavailable, the panel suppresses the numeric tokens and prints "grounded impact unavailable": a missing count and a guessed count are different things.

The fifteen-second audit

Before anything changes, the cockpit will show you what would happen. Preview is a server-forced dry run.

Certant cockpit preview output with dry_run true and the action parked awaiting approval
Preview returns "dry_run": true and the line "previewed", with no external bytes; underneath, the action is already parked awaiting approval, noting the requester cannot self-approve.

Then you audit one of the two people by hand, from the product's own screens, starting on the record.

Certant Object 360 page for E1042, hourly_rate 20.00, sourced to page 4 of the payroll export
A. Nguyen's compensation-rate instance: 20.00 an hour, sourced to page 4 of the payroll export, with the alert history for this record underneath.

Follow governed_by to the floor that applies, and hover its source.

Certant Object 360 page for AwardFloor DDSO 2 at 21.34, with the page 146 source crop expanded
The governing floor, its key, and the crop of the table region on page 146 it was extracted from. Two employees hang off it, per the related panel.

$20.00 on one page, $21.34 on the other, backed by a picture of the agreement. That's the audit: two clicks from the alert to the page, no step where you take the product's word for it. The provenance record behind the crop carries the document name, page: 146, a bbox_pdf rectangle, scope: "row" and, honestly, exact: false, since the answer is join-derived rather than lifted verbatim from one cell. Rows with a null bounding box fall back to opening the page.

One plan, one signature

The fix is a typed action, act_reissue_award_rate, and it does not run because the monitor asked. It parks.

Certant Approvals inbox with one parked action requested by system:monitor:mon_award_floor
The parked item on 13 July 2026. The requester is the watch rule itself, recorded as system:monitor:mon_award_floor; the page header notes that approving resumes the reviewed manifest exactly, nothing re-planned.

Two things make that inbox worth having. Requester identity is a signed origin claim, not anything in the request body, so a forged actor is rejected. The effect plan is frozen and hashed before the gate, so the bytes a human signs off are the bytes that execute, with no re-templating afterwards. A requester approving its own request gets a 409, trivially satisfied here since the requester is a monitor and the approver is a person.

Certant cockpit outcome panel marked Executed, showing requester and approver
The outcome on the alert: Executed, with who requested it, who approved it and when, on the record.

The wording we use internally, "compensatable, not reversible", is deliberate. Compensation is derived at compile time from the frozen plan, checks the recorded after-image before acting, and refuses a stale or duplicate attempt. For the Xero case a draft invoice compensates by deletion, since Xero only permits voiding from authorised. When a dispatch crashes between send and acknowledgement, the reconciler writes outcome_unknown rather than picking a side. I'd rather ship a ledger that admits an uncertain outcome than one reporting a clean success it can't prove.

It keeps working as data arrives

Payroll posts its weekly export to a stream receiver rather than emailing a spreadsheet.

Certant Stream Receivers page with the payroll weekly export receiver, 10 rows across 4 files
The receiver after the batch landed during the recording: 10 rows across 4 files, 2.9 KB, 4 accepted POSTs at 1 credit each. Auto-promote is toggled off in this capture.

The receiver lands data quickly; what happens next matters more, since an export can arrive in a shape the model has never seen. Promotion routes through the matcher and the family sweep, so a novel shape parks instead of reshaping the live model.

Certant Review Queue with a pending EmployeeAllowance shape awaiting approval
A weekly export that brought allowance columns nobody had modelled. The new family waits for a person; the live model is untouched.

That park raises its own cockpit alert, which is why the exception queue in the earlier screenshot carries a "schema drift: novel family" entry next to the pay-floor breach.

What has not shipped, and what was not measured

Demos flatter, so here are the honest edges.

Dispatching an action from inside the cockpit is planned, not shipped; the recording uses the shipped path: preview from the cockpit, run from the Object 360 dialog or the monitor-requested park. Feed history with a hardened receiver, and the PostgreSQL logical-replication CDC connector, are also planned. The compensation state machine is proven against a recorded stub connector; the live Xero draft-to-deleted proof is still open, so I won't claim it. Auto-promotion is proven in its own segment, but the receiver capture above has the toggle off, so that screenshot isn't evidence of it.

One measurement I'd like and don't have: how long the end-to-end run took in wall-clock terms, or how much of it was model time versus query time. The only cost signal I can read off the recording is the workspace credit counter, which moved from 466.5 to 461 across the session, covering everything I did on screen, not the pipeline alone.

Everything ran on one machine

All of this ran on one machine. The stream receiver posts to http://localhost:38003; the chatbot embed endpoint is http://localhost:33001. The agreement, the payroll export and the two employees' names never left it. For a care provider whose lawyers have opinions about where employment records live, that's why this conversation can happen at all.

Back to page 146

Certant PDF viewer reopened on Schedule J, page 146 of the Meridian agreement
Where the walkthrough ends: the same page it started on, reached from the alert rather than by scrolling.

Meridian Care is invented; the document shape isn't. The walk-back below is the same feature running on the real corpus this fixture was modelled on: the Aruma DSEAV 2022-2025 enterprise agreement, 2.28 MB, 1,679 chunks, whose Schedule J is a table of 44 rows and 6 columns, and whose page number happens to be 146.

Certant PDF viewer on the real 2.28 MB Aruma agreement, opened at Schedule J, chunk described as a table with 44 rows and 6 columns
The provenance chip on a real enterprise agreement opening the PDF at the exact salary table the instance rows came from. Captured 23 June 2026.

We also have the full narration timings for the 11 minute 17 second cut of this walkthrough, recorded live at 3840x2160 against the seeded knowledge base and paced at about 145 words per minute: strata_p146_demo_2026-07-13.srt. The subtitles are in the repository; the rendered master is not.

Page 146 used to be a wall of text. Now it's a check that runs every five minutes against live payroll, and the two names it flagged are sitting on an approval record with a page number attached.

Build a brain for your business.

Certant turns your documents, data and processes into agents, dashboards and assistants you can actually trust.