AI reads. Rules decide. You approve.
The division of labour is the design, not a limitation. A language model is the fastest reader you will ever hire and the worst witness you could put on the stand: it can find the one sentence in four hundred pages, and it cannot be cross-examined about a number it produced. So it reads, and it is given nothing else to do. Every quantity, every severity, every compliance call and every citation is produced by code from rules written down in files you can open before you buy anything. That is what makes a finding survive the conversation with the engineer whose drawings it contradicts.
This page is the long version, written for the person your estimator forwards it to. It walks the pipeline stage by stage, names the module behind each one, says where a model is permitted, and labels every stage with one of four build states: Available, Pilot, In Development or Planned.
Models read, code decides Enforced by a test, not a convention Byte-identical replay Every line cited
Twelve stages. One of them is allowed to call a model.
Two independent entry points feed one evaluator. There is no single command that runs
the whole thing: bre ingest and bre run are separate
invocations, and the operator carries the DocumentModel from one to the other by file
path. That is stated here rather than smoothed over, because a diagram showing one
button would be a diagram of a different product.
What state each capability is in
Four states and no fifth. Available runs today on a real package. Pilot runs and is being validated with design partners. In Development is being built and is not reachable yet. Planned is specified, with its rule id block reserved, and has no code. The stage cards below carry the same chip.
| Capability | Where it sits | State |
|---|---|---|
| Specification and project-manual cross-check, the Division 08 ↔ Division 28 seam | Stage 09 | Pilot |
| Scope-ownership statements, SB-001 and SB-002 | Stages 03 and 09 | Pilot |
| Seam rules SB-020 to SB-024 | Stage 08 | Pilot |
| Access-control opening rules, AC-001, AC-010 to AC-014, AC-020 to AC-022, AC-030 | Stage 08 | Pilot |
| Deterministic rule evaluation, validators, risk register, report | Stages 06, 08, 10, 11, 12 | Available |
| Video-surveillance rules, VS-001 and VS-002 | Experimental | In Development |
| Schedule-to-specification join, SB-010 to SB-019 | Ids reserved, no rules written | Planned |
| Addendum reconciliation, DC-020 to DC-029 | Nothing parses or diffs an addendum | Planned |
| Cross-document contradiction, DC-001 to DC-019 | No rule in any pack | Planned |
| Dual-vendor extraction agreement | Stage 04, written and unit tested, reached by no command | In Development |
| Plan and drawing extraction | Stage 05, no module exists | Planned |
| Bill of materials as an input | Emitted as an output, never read as an input | Planned |
| Hosted web application, login, chat copilot | In the repository, not deployed | In Development |
-
01
Workspace resolution
No modelAvailableextraction/workspace.py
- Takes in
- A package directory.
- Emits
- A sorted list of the PDFs under
source/. - Deterministic
- Globs recursively and sorts by path string, so ingest order does not follow filesystem enumeration order. The extension is matched case-insensitively.
- Refuses
- Any path with a
schedulecomponent, resolved through symlinks. A published equipment schedule is benchmark truth and may not be an engine input for the package it grades. There is no override flag: moving the file is the override, and moving it leaves a trace. It also refuses a package with nosource/directory, and asource/holding no PDFs.
-
02
PDF ingestion and CSI segmentation
No modelAvailableextraction/documents.py
- Takes in
- The resolved PDF paths.
- Emits
- A DocumentModel holding documents and specification sections, each element carrying document id, page and bounding box.
- Model
- None. No model call, no network call, no clock read anywhere in the module.
- Deterministic
- Document ids are derived from the file's sha256, not from position on the command line, so a citation minted in one run resolves in the next.
Section headings are found by structural rules rather than by a pattern match alone. A 316 page project manual prints section numbers in its table of contents, in a running footer, and in every "related sections" paragraph, and treating those as headings produces phantom sections that split a real section's body away from its heading. Confidence is tiered and honest: a heading announced by the word SECTION scores 0.90; a bare section number inferred from page position scores 0.55, deliberately below the 0.6 review threshold, so every guessed boundary arrives flagged.
Refuses: a PDF that will not open, has no pages, or has no extractable text layer. The message names the likely cause, a scan, and says route it to OCR. Two input files with identical bytes. And it refuses to write a partial model: every document is read first, all failures are reported at once, and the file is written only after every document parsed. A partial model on disk makes "this package has no NIC language" indistinguishable from "the sheets carrying it did not parse".
-
03
Scope boundary detection
No modelAvailableextraction/scope.py
- Takes in
- A PDF and that document's own section list.
- Emits
- ScopeStatements: NIC, by others, furnished by, installed by, OFCI, CFCI, under separate contract, existing to remain, verify in field plus the matches it rejected, each with a reason.
- Model
- None, deliberately. The marker phrases are a small closed vocabulary a document either prints or does not, so a scan over them is reproducible and cannot fabricate.
- Confidence
- Spelled-out phrase 0.92, qualified form 0.85, punctuated abbreviation 0.75, bare three-letter shorthand 0.58, which is below the review threshold on purpose.
- Refuses
- A match whose words do not all come from a single text block. Two stacked schedule rows look exactly like one wrapped cell, and an unguarded assembly reads a phrase across two doors and quotes a string printed nowhere on the page.
Known gap, recorded rather than hidden. On the one real 316 page manual tested, this stage found the Division 28 assignments the manifest records and missed a page stating owner-furnished scope in ordinary English with none of the marker phrases on it. The acceptance test marks that page as a strict expected failure. The detector was deliberately not widened, because a pattern matching "responsible for" would fire on a large fraction of any specification's body text, and a rule that fires too broadly is worse than an absent rule.
-
04
Structured claim extraction
Model callIn Developmentextraction/claims.py
- Takes in
- An open PDF, one section, a response cache, and a model client.
- Emits
- Claims (subject, predicate, object, span, confidence) plus the quotes that could not be located.
- Model
- Yes, and this is the only module in the repository that calls a conversational model for extraction. What it may return is fixed: a subject, a predicate drawn from a closed enum enforced by structured outputs, an object, a verbatim quote, and a confidence. No page. No bounding box. No section id. No conclusion. The prompt tells the model in as many words that if two passages disagree it must emit both and say nothing about the conflict.
- Deterministic
- The locating. Code searches the actual page for the model's quote and builds the span from the rectangle the search returned. A quote that is not on the page returns nothing, the claim is dropped, and the quote is kept as a defect rather than logged and forgotten. A run where the model fabricated forty quotes and a run where it found nothing both produce zero claims, and those are very different situations.
- Refuses
- A cache miss with no client, rather than reaching the network.
Written, unit tested, and reached by no CLI command.
bre ingestnever populates the claims collection, so on any package produced today the seam analysis at stage 09 sees only the scope statements from stage 03. It is described here in the present tense of the code, not of the product, because a pipeline drawing that showed this stage running would be a drawing of something else. -
05
Plan extraction
Plannedextraction/plans.py does not exist
- Would take in
- Drawing sheets.
- Would emit
- A SpatialModel: spaces, openings, infrastructure nodes, sheets with scale provenance.
- State
- Planned Nothing exists. There is no rasterization, no scale determination, no tiled extraction and no review payload. Every SpatialModel in the repository is hand authored, including the one the golden test runs against. Nothing in this system reads a drawing sheet today.
-
06
Loading and the config hash
No modelAvailableengine/loader.py
- Takes in
- An optional SpatialModel, an optional DocumentModel, one or more rule packs, and a seed. At least one of the two models is required.
- Emits
- The validated models, the packs, and a run manifest carrying the config hash.
- Deterministic
- Every input is hashed by raw bytes rather than parsed content, so a reformatted pack is a different input with a different identity. The hash is taken over the sorted input hashes plus the engine version plus the seed, so the order packs appear on the command line does not change run identity. Priority is explicit in the pack, never positional.
- Refuses
- A run with neither model. The same pack loaded twice. A rule id or validator id defined by two packs, because rule ids are permanent and are what a published benchmark result cites, so a collision is a naming error that must fail at load rather than let one pack shadow another. A conflict group whose members disagree about their target set, checked across the whole run rather than within one pack.
-
07
Placement and coverage geometry
No modelAvailableengine/placement.py
- Takes in
- A SpatialModel plus the packs' camera optics. Runs only when a spatial model was supplied.
- Emits
- A placement model and a coverage overlay the rules read. It runs before rule evaluation, because coverage geometry is an input to the coverage-gap rules and depends only on the model and the pack optics, never on rule output.
- Deterministic
- A visibility polygon swept against the wall segment set, intersected with the field-of-view wedge and a disc of the class radius, then clipped to the building envelope. A circular sector was rejected because it passes through walls and overstates coverage exactly where a reviewer cares. Coordinates are snapped to a tolerance grid before and after boolean operations, and arcs use a fixed 72 steps rather than an adaptive count, so floating-point noise cannot leak into a golden file. Every coverage band records the segment set hash and camera config hash it was built from.
- Refuses
- A camera whose catalog device does not state horizontal resolution, field of view, or maximum range. The message says coverage will not be guessed. An entity the model does not locate carries a null position, and a null position renders as a gap rather than as an invented coordinate.
-
08
Rule evaluation
No modelAvailableengine/evaluate.py, engine/expressions.py
- Takes in
- The SpatialModel, the packs, the placement overlay, and the DocumentModel.
- Emits
- Assignments, withheld selections, raised flags, firings, suppressions, attribute writes, and the final overlay.
- Deterministic
- Pure. Neither input model is mutated; effects are written to an evaluation overlay rather than back into the model. Rules run in priority order within a target with ties broken by rule id, so ordering is total. A conflict group admits at most one rule per target, and every later rule in that group that would have matched is recorded as suppressed with the winner named. That record is what lets the report say AC-012 was suppressed because AC-010 won the locking_device group, instead of going quiet.
- Arithmetic
- A pack states it as text, such as
ceil(controlled_openings_at_idf / 4). Code parses that into a restricted syntax tree and walks it against a whitelist, rejecting attribute access, subscripts, lambdas and comprehensions.evalandexecare never called and a separate test asserts it, because rule packs are user-editable data and user-editable data reachingevalis remote code execution in a product intended for sale. - Refuses
- A derived rule when the run carries no DocumentModel. The message is explicit about why silence would be wrong: running them against nothing would report no findings and look identical to a clean package. Also a join target whose id collides with an existing entity, because merged overlay slots would make a rule read a value belonging to something else and cite the wrong thing with full confidence.
-
09
Derived targets: joins and the Division seam
No modelPilotengine/joins.py, engine/seam.py
- Takes in
- The DocumentModel, the SpatialModel where there is one, and pack-supplied parameters.
- Emits
- Derived targets a rule condition can compare, each carrying at least one citation by schema constraint, so a target with no evidence is unconstructable rather than merely invalid.
- Model
- None, and this is the load-bearing one. Whether two sections conflict is a code comparison over structured records, never a model being asked. Which seam aspect a record is about is literal phrase matching against a fixed vocabulary. Which division a sentence names is a pattern that deliberately does not match a bare "28", because on a schedule that is a page number, a door mark or a gauge far more often than a division. The determination itself is four boolean comparisons over sets.
- Refuses
- Any determination at all when the document set carries neither a Division 08 nor a Division 28 section. With neither side present there is no section heading to cite, and a flag citing nothing is worse than an absent flag. A scope statement whose affected work the layout does not determine is not attached to any aspect either; attaching one sentence to three aspects of every opening would report it as thirty six findings.
Two joins exist and the registry is closed, which is the property that lets an auditor enumerate every relation the system can compute by reading two adjacent lists:
scope_statement, consumed by SB-001 and SB-002, anddivision_seam, consumed by SB-020 through SB-024. -
10
Validators
No modelAvailablevalidators/checks.py
- Takes in
- The SpatialModel, the packs, and the evaluation result.
- Emits
- One result per check per target, sorted by validator id then target id.
- The six checks
- Power supply capacity, voltage consistency per branch, controller door capacity, cable distance from the serving IDF, standby battery runtime, and egress locking compliance. The pack supplies parameters such as headroom and required runtime; it never supplies logic, and the check name is a closed enum so a pack cannot name a check that does not exist.
- Deterministic
- Each result carries the computed values, not just a boolean, because the computed value is what makes a finding actionable.
- Refuses
- With no SpatialModel it returns nothing rather than reporting passes. A validator that reported PASS having examined nothing would be indistinguishable from one that looked.
-
11
Register, bill of materials and rule trace
No modelAvailablerisk/, scoring/
- Takes in
- The evaluation result, the validation results, the packs and the run manifest.
- Emits
- The ranked risk register, the validation results, the bill of materials, and the rule trace.
- Where the words come from
- Severity, category, rationale, authority, exposure key and recommended pre-bid action all come from the pack's rule or validator spec. None of them is computed in this stage, and none of them came from a model.
- Deterministic
- Ranking is a five-tuple: severity rank, exposure low bound, exposure high bound, rule id, target entity id. The fifth key exists because one rule firing on twelve openings would otherwise have undefined relative order, and a reordered fixture would produce a different report without a single number changing. Rank is stamped from position after the sort and is never an input to it.
A withheld device selection reaches the deliverable as a zero-quantity BOM line carrying its reason, not as an omission, because an absent line reads as "not needed" and a zero line reads as "deliberately not selected". The BOM carries quantities and device identity only: no unit cost, no extended cost, no labor dollars.
-
12
Rendering
No modelAvailablereport/
- Takes in
- The engine output and the SpatialModel where there is one.
- Emits
report.mdandengine_output.json, plusplacement.jsonwhen a spatial model was supplied.- Order
- Register first, then validation with its computed values, then the BOM as supporting evidence, then the rule trace as the audit record. Every line carries its rule id and its citation.
- Deterministic
- The renderer reads no clock and no environment. On a documents-only run it prints "Document only run" and declines to name the project from a document title, because a document title is the name of a file and not the name of a project.
- Exposure
- Every band passes through one helper that appends "uncalibrated placeholder" whenever the band's calibrated flag is false. Every band shipped today is false. Read every figure in the deliverable as a placeholder chosen so the ranking key has something to sort on, never as a savings figure and never as a quotation.
The one other place a model appears.
bre chat answers questions about a package out of the artifacts a run
already produced. It is the only command that can make a model call, and the
model-authored package is imported inside that command body rather than at the top of
the module, so it is not in the import graph of a deterministic run at all. It
retrieves, cites and explains; it never computes a quantity, ranks an item or
determines compliance, and its retrieval is deterministic code rather than a model
choosing what to read next.
And what is not built at all. Addendum reconciliation is modelled and unimplemented: nothing parses an addendum or diffs it against the base documents, which matters because an addendum is a legally binding pre-bid modification that routinely changes quantities and is routinely missed. Cross-document contradiction detection, the DC family, has no rule in any pack. The schedule-row to specification joins, SB-010 to SB-019, have no rule in any pack either. The benchmark harness is not written and no result exists.
A language model may say what a document says.
Only code may say what that means.
That is the whole rule, and it is stated in the repository's governing contract before any code exists rather than reverse-engineered from what got built. Everything on this page is downstream of it.
Models may
- Extract. Read a section and return structured facts, each one a subject, a predicate from a closed list, an object and a verbatim quote.
- Classify. Say that a sentence looks like a responsibility assignment rather than a general note.
- Locate. Point at the sentence. It reports the text, and code establishes where the text is.
- Transcribe. Return the sentence as printed, not a tidied paraphrase of it.
- Match. Say that this line and that line appear to refer to the same opening.
Models may never
- Compute a quantity. No counts, no per-leaf math, no totals.
- Assign a part number. No device selection, no hardware resolution.
- Produce a risk score. No severity, no exposure, no ranking of the register.
- Determine compliance. No judgment that something does or does not meet code.
- Produce provenance. A citation is a claim about the world, so it is a determination, so a model may not make one.
The enforcement is an import-graph test, not a code review rule
tests/unit/test_architecture.py parses every Python file in
engine/, validators/, scoring/ and
risk/, collects every module each file imports, and fails the build if any
of them can reach a model client. The forbidden set covers three kinds of thing: the
model SDKs by name; anything that could reach one, meaning every HTTP and socket module;
and the quarantine boundary itself, bre.extraction and
bre.narration. That last entry is the load-bearing one. Banning a vendor SDK
alone would be defeated in one line by importing the module that imports it.
The same guard is applied to the benchmark harness, which the directive does not name
because bench/ did not exist as code when the directive was written. The
reasoning applies there with more force than anywhere else: a benchmark is the evidence
that the engine works, and a model deciding whether a flag and a change order describe
the same event would be producing exactly the number the product's credibility rests on.
The quarantine runs in both directions. Extraction and narration are forbidden from importing the engine, the validators, the scoring or the risk package, so a copilot answering a question about a run must read the run's artifact and cannot re-run a rule or re-rank a register. A system that can recompute under pressure will eventually be asked to. The one indirect route worth closing by hand is closed by hand: the CLI is on neither forbidden list and imports the engine, so a separate test asserts that narration does not reach the engine through it.
A documented boundary is a promise that holds until somebody is in a hurry. An enforced one fails the build on the commit that breaks it, whether or not anyone was looking at that diff on that day.
That is the entire difference, and the failure mode it defends against is not malice. It is a reasonable person under deadline with a nearly-working extraction who wants one model call inside a validator to resolve an ambiguity, and who is right that it would work. When the test fails, the assertion message is the instruction: move the offending code into extraction, do not add it to the allowlist.
What the enforcement does not catch, in descending order of how much it should worry you.
A dynamically assembled module name. The detector reads syntax, so a literal string argument is resolved and caught, and a name concatenated at runtime cannot be. This is written down rather than left to be discovered: a test is named for the blind spot and asserts it directly, and the idiom is already in the tree in a place where it is legitimate.
Direct imports, not a transitive closure. If a module in neither forbidden set imported an extraction module, anything importing that module would reach a model client with the suite green. The CLI is the one such route closed by name today; a future module in the same position needs the same treatment.
Correctness. The test proves a number was produced by code rather than by a model. It says nothing about whether the number is right, whether a rule fires on the cases it should, or whether an exposure band means anything. That is the benchmark's job and the benchmark does not exist. The boundary makes the output defensible. It does not make it accurate.
The guard has been repaired more than once against itself. Three of its own tests replaced earlier versions that could not fail: a parametrized check that would have passed on an empty package list, a forbidden set that could be emptied, and a misspelled entry that could never match anything. The docstrings say which and why.
Two readings, and what happens when they disagree.
More than one vendor's models may read the same content, and that permission stops at the edge of the extraction layer. No additional vendor, and no number of models, gains any authority by being added: none of them may compute a quantity, assign a part number, score an item or determine compliance, no matter how many of them agree.
The value is used, at the lower of the two confidences
Agreement is not treated as a bonus. It withholds the review flag that a disagreement would have raised, and that is all it does. The confidence carried forward is the minimum of the two, never a blend and never a lift.
Two readers each 0.8 confident does not become 0.96. That combined figure would be a number this repository invented about its own reliability, which is worse than one a model invented, and it is the exact thing the design exists to prevent.
Agreement does not rescue a bad reading either. Agreeing on an illegible cell is still agreeing on an illegible cell, so a minimum below the 0.6 threshold still raises its own review flag. A test pins that specific interaction: 0.95 reconciled against 0.5 on identical readings files no disagreement, carries 0.5, and stays flagged.
Where the two spelled the same value differently, neither vendor's spelling is stored. The canonical normalized form goes in, and it belongs to neither of them.
No value is produced at all
Not a value with a warning next to it. No value. A record is filed in its place holding every vendor's raw reading, the vendor and model that produced each one, and the span in the document.
The record is shaped so that "the reading that won" is unrepresentable. There is deliberately no field for an adopted value. Its review flag is typed so that a disagreement not needing a human cannot be constructed. A record whose readings are all identical is rejected, because a record that can hold agreement will eventually be filed for every row. Two readings from the same vendor are rejected, because one vendor contradicting itself is sampling noise rather than evidence about the document.
Even the order of the readings is defended. They are sorted by vendor then model id, and the field says what the order is for: stability, never preference, because nothing may read the first entry as the adopted value.
Why there is no tiebreak, and why averaging is the worse answer
A tiebreak is the obvious next feature and it is the one that would quietly destroy the value of having read the row twice. Every scheme for picking a winner is arbitration wearing a different hat, and each one was considered and has a test holding it out.
- A majority is arbitration with a quorum attached. Two vendors reading 14 against one reading 12 is three independent readers who did not agree, not a two-to-one result for 14.
- Confidence weighting is arbitration with a number in front of it. A vendor that is certain and wrong is the case this whole mechanism exists to catch, and the confidence figures are the vendors' own self-reports. Weighting by them means trusting a model's self-assessment to settle a question about a construction document.
- Preferring the longer or more specific reading fails on the domain, not just on principle. "Electric strike" and "electric strike, fail secure" differ by a requirement, and fail-safe against fail-secure on an egress path is a life-safety defect rather than a cost item. Preferring the more specific reading adopts a requirement one reader never saw. Preferring the shorter one drops a requirement the other did.
- Even sorting the readings by confidence is arbitration. Nothing has been decided and no value adopted, but the first entry becomes the answer for any renderer that is careless, in a hurry, or written by somebody who never read the design note. A CSV export, a viewer table or a downstream join reading the first entry "just to have something to show" reproduces confidence-weighted arbitration at one call site, with no code anywhere that looks like a decision. Ordering is a preference the moment position is legible, and position is always legible.
- A value that does not exist cannot be misused. A flag can be ignored by a later step that forgot to check it. An absent value cannot. Emitting nothing is not only the honest choice, it is the mechanically stronger one.
Averaging two contradictory readings of a door schedule invents a third reading that neither reader saw and no page contains.
That is why the disagreement is the output. Two independent readers diverging on a construction document is evidence about the document, not about the models, and the places they diverge are the places the documents are genuinely ambiguous. Resolving it internally throws away the only thing the second reader was there to find.
The failure mode that matters is the silent one, and it is not symmetric with the noisy one. A comparator that is too timid fills the register with disagreements about whitespace; that is bad and it is visible. A comparator that is too aggressive folds a real difference into equality: no flag fires, no error is raised, nothing in the output says anything was suppressed. The register is simply shorter, and a practitioner reads a shorter register as a cleaner package. This is why fuzzy matching, edit distance, prefix matching and substring containment were each rejected, why leading zeros are preserved because in a part number a leading zero is information, and why every folding rule in the normalizer ships with a near miss beside it that must go red if the fold is ever widened.
It is also why a run refuses rather than quietly falling back to a single vendor when the second is not configured. A run that proceeded with one reader would report agreement it never checked, and the register would come out wrong in the direction of false confidence.
In Development The comparator is written, unit tested, and reached by no CLI command. No pack defines the EX-001 rule that would evaluate a disagreement, and nothing populates the disagreement collection on a real package. The block EX-001 to EX-019 is reserved for extraction uncertainty specifically so that disagreements never pollute the precision and recall of the SB and DC families, whose published numbers are meant to be this product's credibility.
A page number is a value read out of a span, not a number a model asserted.
Every output line carries its rule id, its source document with page or sheet, and the specific target it is about. A line without provenance is treated as a defect in the software, not as a formatting preference. The mechanism matters more than the promise, so here it is.
What this system never does
Ask a model where the sentence was, and print what it says. A model asked for a page number will supply a plausible one for a page it read nothing on, and a plausible page number is worse than none because nobody checks it. Under the boundary rule a citation is a claim about the world, which makes it a determination, which makes it something a model may not produce.
What it does instead
The structure the model may return has no page field, no bounding box and no section id. It returns the sentence. Code then searches the actual page for that exact string and builds the span from the rectangle the search returned. A fabricated quote is not locatable, so an invented claim structurally cannot acquire a citation. There is nothing on the page to find.
The same rule, one layer down
- The citation type refuses to exist without a locator. A validator requires at least one. A derived target requires at least one citation, which makes an evidence-free target unconstructable rather than merely invalid, because everything built from it would be an output line without provenance.
- The span is built for a reviewer, not for a database. The page number is 1-based to match what a reviewer types into a viewer. The bounding box is in PDF points rather than pixels, so a citation does not move when somebody re-renders the document at a different resolution. A degenerate rectangle is rejected.
- A seam finding carries every span it consumed plus the section headings it read them from, because "we read section 08 71 00 and section 28 13 00 and neither one assigns this" is only checkable if a reviewer can open both.
- The chat layer is built the same way. A record's readable text and its citation lines are separate fields. Only the text is put in front of a model; the citation lines never leave the process until code prints them. A model that was shown "p. 236" could type "p. 236", and every guarantee here rests on it being unable to. The model supplies a record id and provenance is looked up rather than asserted. An id that does not resolve raises rather than printing the sentence bare.
- Rules that did not fire are recorded too. When two rules could both apply to one target, one wins its conflict group and the other is recorded as suppressed with the winner named. That is not an error log. It is there so a reviewer who expected a different finding can read the rule that displaced it instead of guessing why the software went quiet, and reporting both against a single target would put the same money in the register twice.
A finding is going to be quoted back at the person who wrote the document. The citation is not metadata on that conversation. It is the conversation.
This is the whole game for a document that ends up attached to an RFI. Bring a flag to a pre-bid review and the engineer whose design you are questioning will ask where it came from. "The model concluded this" ends that conversation badly, and it should. A named rule, both sentences quoted verbatim, and the page each one is on does not end it at all. It turns it into a discussion about the documents, which is the discussion worth having. Quote both sides: asking with only one attached invites the answer that it is obviously yours.
The same reasoning runs the other way on plain-English tidying. The sentence is reported as printed, because a tidied reading of a line is what a reviewer ends up arguing with instead of arguing with the document.
Same inputs, same packs, same seed. Byte-identical register.
Not "similar results". The same characters. The config hash on every run is the receipt, and it is printed in the report header and echoed by the command line so it is on the artifact rather than in somebody's memory.
## Run - Engine: 0.1.0 - Config hash: a71e9062fd76a18005c2eb0ef03a4e0863ff5c86c82ccb3af247dcc5e4be8388 - Seed: 0 - Jurisdiction assumed: CA-OSHPD-EXEMPT-DSA - Inputs: - spatial_model: tests/golden/wing_a/spatial_model.json sha256 55d60c1edb873fac - pack: packs/ac_k12_base.json (ac_k12_base 0.1.0) sha256 1941cf79bc2c4136 - pack: packs/vs_k12_base.json (vs_k12_base 0.1.0) sha256 6d59927b83683542 This run is replayable: the same inputs, config hash, and seed produce byte identical output. ## Risk register 7 item(s). 0 blocking. 3 carry an entity flagged for review.
The header of the golden test fixture's report, as the renderer emits it. The test compares the whole file byte for byte.
What the hash is taken over
A sha256 of the engine version, the seed, and one sorted line per input holding its role, pack id, pack version and file hash. Two properties are doing the work. Input hashes are over raw file bytes rather than parsed content, so a whitespace change to a pack changes the run's identity and cannot be passed off as the same run. The list is sorted, so the order the packs appeared on the command line does not change identity, because priority is explicit in the pack rather than positional.
What makes the replay byte-identical
Determinism is engineered in a dozen small places rather than assumed, and each one is the fix for a way a run could differ from itself:
- Rendering reads no clock and no environment.
- Ranking is total: five keys, the last being the target entity id, so one rule firing on twelve openings has a defined order.
- Validators are sorted by validator id then target id.
- Package PDFs are sorted by path string rather than by directory enumeration order.
- Document ids are derived from content hash, never from position on the command line; sections and scope statements sort by a total key ending in position down and across the page.
- Joins sort their output by a total key. The module names this as its own trap: a join iterating a set is a reproducibility break that will not surface until a benchmark rerun disagrees with a published number.
- Placement snaps coordinates to a tolerance grid before and after boolean operations and uses a fixed step count for arcs, so a geometry library's floating-point noise cannot reach a golden file.
- The evaluation overlay is emitted sorted.
- Chat transcript rows carry nothing time-varying: no timestamp, no duration, no served-from-cache flag, because any of those would make a replay differ from the recording it replays.
Model calls are replayed too, by a separate mechanism
The config hash makes the deterministic engine replayable. A response cache makes model calls replayable, and the two are separate. Every call is keyed on four fields: the vendor, the model identifier, the prompt version, and a hash of the request payload. The vendor is in the key because a model id is not globally unique across vendors and a collision would serve one vendor's recording to the other's call, presenting as a correct-looking cached run rather than as an error. The prompt version is in the key because the prompt is code, and forgetting to bump it is the one way to make the cache lie: it would serve answers produced by wording that no longer exists.
The digest is taken over canonical JSON rather than over labelled lines, because labelled lines look injective and are not: a field value containing a newline and a label reproduces another key's material exactly. Entries are written through a temp file and an atomic rename, so an interrupted write cannot leave a truncated file a later read accepts, and a corrupt entry is refused rather than reported as a miss, because a miss triggers a live call and turns a disk problem into an unexplained charge. In sealed mode, which is the CI mode, the cache never computes: an unrecorded call raises instead of reaching the network, and a sealed run pointed at a mistyped path reports empty and misses loudly rather than silently creating a new empty cache.
A run that cannot be replayed cannot be benchmarked, and a system that cannot be benchmarked can only be believed.
Produce a flag, not a guess.
Where the documents do not determine something, the correct output is a flag. A guess is a determination made without evidence, and since code makes the determinations, it is code that has to decline. Every layer has an expressible way to say "not determined", and a low-confidence reading becomes a flagged item rather than a silently included one.
Confidence, and a threshold that bites
Every extracted entity carries a confidence and a review_required
marker, set by code against a 0.6 threshold. The tiers are chosen so the weak cases
land below it on purpose: a section boundary inferred from page position scores 0.55,
a bare three-letter scope abbreviation scores 0.58. Where the page layout does not
determine what work a scope statement applies to, the affected work is left empty and
the statement is flagged regardless of how certain the phrase itself was.
Unknown satisfies nothing
A missing or null field satisfies no positive comparison in a rule condition, including "not equal". Not-equal applied to an unknown value is a guess, so the evaluator refuses it and requires an explicit does-not-exist test instead. Where a determination rests on several records its confidence is the weakest link and its review flag is the union of theirs. Nothing ever multiplies confidences upward.
Withholding is an answer
A rule may withhold a device selection instead of making one. On an aluminum storefront whose stile depth cannot be verified from the documents, withholding the locking device and raising a field-verification flag is the right output, not a fallback. Guessing is a change order. The BOM shows a zero-quantity line carrying the reason, because an absent line reads as "not needed".
The example worth learning: SB-024 exists so silence is not misreported
The Division 08 to Division 28 seam resolves to one of a small number of states, and one of them is not in the obvious list. Both divisions specify the work and require different things. Both carry it and agree. Each assigns it to the other. Neither specifies it. And then the fifth: the documents do not settle it at all.
Neither division specifies the work
A conclusion, reached after reading both sections and finding nothing. It fires only where both divisions are present in the document set and both were read, only on openings the drawings mark as controlled, and only where no third division was named either, because power taken off a Division 26 branch circuit is somebody's work and reporting it as unassigned would be wrong in the most embarrassing direction.
The seam cannot be determined from these documents
An admission, not a conclusion: one side of the seam was not in the document set, or the only sentence covering this work moves it to a party the documents decline to name. Severity is medium rather than high precisely because the finding is about our reading rather than about the documents. It tells a reader where to look, not what they will find there.
Collapsing the second into the first would be the confident output where the honest output is a flag, and it is the specific version of that failure this product can least afford. The rule's own rationale spells out the cost: a bidder shown "nobody specifies the rough-in" on a package whose Division 08 section was never supplied will find the specification, find the assignment, and stop believing the register. One bad finding of that shape is enough to make an estimator ignore the other forty.
The same instinct runs one level higher. If the document set carries neither a Division 08 nor a Division 28 section, the seam analysis emits nothing at all rather than a flag, because with neither side present there is no section heading to cite.
An empty register is not a clearance. A short register means the rules that exist did not fire on the documents that were read. It does not mean the package is clean. Rule coverage is a known limit, there is at least one detection gap recorded and held open on purpose rather than papered over, and the rules that would catch a device appearing in a drawing schedule with no specification reference are allocated ids and not written.
Check it yourself. That is the point of publishing all of this.
What this design buys you
Three things, and they are narrower than an accuracy claim: every output line carries a rule and a citation you can open; runs replay byte for byte, so a change in the register means something changed and you can find out what; and the system refuses rather than guessing where the documents do not settle a question.
What it does not buy you
It does not price work: no unit costs, no labor rates, no extended totals, no quotation. It does not replace an estimator; it performs one review that gets skipped under deadline, the cross-check between documents, and a person resolves what it raises. It does not read drawing sheets yet.
Read every rule
All four packs in full: conditions, rationale, cited authority and severity, for every check the engine performs. Published before you buy anything.
Published rules 02Read the limitations
What Blueprint does not detect, what is written and not wired, and what does not exist at all, stated plainly and kept current.
Limitations 03Open a register
Five findings from one seam, each with the rule that fired, the sentences that triggered it and the page each one is on.
Explore the demoDon't trust a black box. Inspect the rules.
Every check the engine can perform is published in full: conditions, rationale, cited authority and severity, in four packs you can read before you buy anything. This page described the machinery. That page is the machinery.
What we publish is measured. What isn't measured is labeled accordingly.