Releases: kjpatel/duly
Release list
v0.4.0 — M4: standards, authoring, and static assurance
M4 complete: the interchange contract now speaks standards, rule authors have a second surface and a solver, and the toolkit is demonstrably consumable from outside.
Ten capabilities across twenty-five pull requests (#6–#30), and the golden corpus was never regenerated once. All 351 receipts replay byte-for-byte from before any of it. Every track's acceptance test was that git diff -- golden/ came back empty, and every one held.
Standards alignment
- PROV-O JSON-LD export (#9–#14) — facts, receipts, and run envelopes expand to W3C PROV triples under external contexts, stored bytes unchanged. The mapping is deliberately partial: bitemporal effective time, confidence, and abstentions have no faithful PROV equivalent. An earlier "lossless mapping" claim was corrected in the process.
- Ontology conformance gate (#16) — a fact's
schemaRefresolves against versioned, immutable LinkML artifacts; a misspelled attribute that previously surfaced only as a rule silently failing to bind now fails ingestion loudly. The hot-path validator interprets a documented LinkML subset rather than importing linkml-runtime, which would drag RDF tooling into the runtime. - Pack-embedded calendars (#17) —
add_business_dayswalks a calendar the pack carries, with a coverage window that raises rather than guessing past its edge. "Last Monday in May" became legal content, versioned and receipt-pinned with the rules that use it. The TILA pack now computes the rescission deadline it could previously only certify.
Authoring
- DMN decision-table compiler (#20) — DMN 1.3+ tables compile to the rule IR; the kernel cannot tell the result from a hand-written pack. It refuses rather than approximates: an uncited row is a compile error, not an invented
TODO(verify). - Pack-owned decision phrasing (#28) — verdict wording moved from
demo/app.pyinto the pack. Every determination the demo can render is byte-identical before and after; the wording moved without a word of it changing. - Rule-ID convention, ids grandfathered (#28) — nothing renamed, because
NY-NR-45sits in 76 golden receipts and a receipt is not editable. A rule id is a handle, not a claim.
Static assurance
- Z3 static pack verifier (#21) —
PROVED-DISJOINT/NOT-PROVEDwith a witness /OUT-OF-FRAGMENTnaming what it declined to encode, plus uncovered input regions. Across six packs: 25 proved disjoint, one genuine overlap (correct — a registered eNote is a promissory note), nine uncovered regions, all intended and now witnessed.validate_packis deliberately not relaxed. - What-if queries (#26) — solve a pack backwards for one freed input. The solver proposes; the kernel disposes: every answer is re-adjudicated, extremals are boundary-verified, and a disagreement raises with both artifacts rather than returning an answer.
- Marker-gated suites now run in CI (#22) —
linkml,z3, anddoclinghad never executed there. Thelinkmlone mattered most: its whole job is checking the ontologies against real tooling, and the pure-Python gate would have gone on passing while the files drifted out of spec.
Reference wiring
- OR-Tools closing scheduler (#29), first resident of
examples/— CP-SAT plans sign → fund → record, where every hard constraint is a table of days an adjudication permitted and each chosen date cites the receipt ids that constrained it. Compliance opens Saturday 2026-02-21 (Saturdays are business days under the precise § 1026.2(a)(6) calendar); the wire desk is closed Saturdays, so the plan says Monday, and the output names which one bound the date.
Three claims the work corrected
- The DMN acceptance criterion demanded "the same decision and receipt", which is unachievable: a receipt pins its pack's name and version, so two packs are two identities.
- That equivalence proof turned out fixture-bounded — a
>→>=perturbation leaves the whole suite green because no fixture reaches the boundary. This is what motivated pack equivalence in the verifier. - "Keep actions outside the kernel" is only half a boundary. An integration can respect it perfectly at the API level and still hold a second copy of the rule ten lines away, invisible at the seam. Detectable only by mutation — which the scheduler now demonstrates rather than asserts.
Numbers at this tag
704 tests passing; 351 golden receipts replaying byte-for-byte; six rule packs across two domains; 1,478 committed facts conforming to their pinned ontologies; 25 same-priority rule pairs proved disjoint.
This tag marks a milestone, not a package release. pyproject.toml deliberately stays at 0.0.1 — nothing is published, and installable distribution is M5 work. The contracts remain v0 and may break until v1.0.
Full notes: CHANGELOG.md
Full diff: v0.3.0...v0.4.0
v0.3.0 — M3: extraction and review
M3 complete: the probabilistic edge of the seam is now real, and the correction loop closes.
M3 (#3)
- Extraction adapters — a protocol derived from two implementations (the honest scripted stub and live Docling), emitting renditions per spec D4 with every span verified against the rendition text on emission
- Run envelopes — content-addressed manifests so a whole extraction run can be verified before ingestion and revoked at once (resolves the batch-envelope question deferred from M0)
- Abstention policy — pack-versioned confidence floors enforced by the kernel: below-floor machine facts are excluded at binding with
low_confidencereceipt entries carrying score, threshold, and provenance; packs without a policy remain byte-identical (pinned-hash proven) - Calibration module — temperature, Platt, and split-conformal math with an ECE/Brier/coverage validation harness; deliberately unfitted until real review labels exist, and explicit that the conformal guarantee is marginal, not error-among-accepted
- Review queue — abstentions become dedup'd queue items; human corrections enter the store as first-class superseding facts, flip decisions on re-adjudication, export
(score, correct)calibration pairs, and freeze as replayablereview-*golden cases - The demo arc — extract → abstain → inline human correction → flipped verdict → golden-case export, end to end in the browser
The commercial extraction adapter (Azure DI / Google Document AI) moved to v1.1 by decision: Docling proved the interface against a real converter, and a second provider is a contribution the seam exists to invite.
Also in this tag's tree (#4, #5)
Four statutorily-researched mortgage-closing rule packs — RON eligibility on real state authorization dates (including California's not-until-2030 statute), eSign/eNote routing, TILA rescission with its documented MODELING BOUNDARY, county recording readiness with its own abstention arc — plus a domain-grouped demo (seven scenarios, two domains) and the roadmap restructure that declared M3 complete (M4 reordered by consumer readiness, the alternate Datalog backend split to M5).
Numbers at this tag: 378 tests passing; 251 golden receipts replaying byte-for-byte; six rule packs across two domains; every citation verified against primary sources or marked TODO(verify).
Full diff: v0.2.0...v0.3.0
v0.2.0 — M2: replay and regression
Decisions were already deterministic in v0.1.0. This milestone makes that property continuously provable, adds the time dimension the architecture was designed around, and turns rule changes into a reviewable event.
What is new
Bitemporal fact store (store/) — append-only event log on SQLite with a Postgres-portable schema. Facts are never mutated; corrections supersede. Projections take both dials: as_of(case, knowledge, effective). The query the architecture exists to answer now runs: what would we have concluded in March, knowing what we knew in March? Ingest a correction recorded later and the old knowledge horizon still replays the original decision byte-for-byte, while the new horizon reflects the correction. Correction-of-a-correction chains are walkable in either direction. The store never reads the wall clock — knowledge time always comes from the facts themselves.
Golden corpus (golden/) — 250 committed synthetic cases with their adjudicated receipts, generated from a seeded deterministic generator (python -m duly_assurance generate). Coverage is deliberate: notice margins crossing each state threshold in both directions, 60+ cases evaluating under superseded rule versions, nonpayment grounds that exercise exception-and-defeat chains, and TRID amounts above, at, and below baseline. Regeneration from the same seed is byte-identical.
Replay verification (python -m duly_assurance verify) — re-adjudicates all 250 cases and asserts byte-identical receipts, catching both body edits and hash edits. Runs in CI on every push, so determinism is proven continuously rather than asserted in a README.
Rule-change impact analysis (python -m duly_assurance impact) — re-adjudicates the corpus under working-tree rule packs and reports every decision that changes. PRs touching rulepacks/ now get a sticky comment: "N of M decisions flip" with before/after receipts, plus reasoning-only changes (same verdict, different rules fired) tracked separately — the distinction an auditor cares about. Rule maintenance becomes a reviewed event with a visible blast radius.
Florida and California rule packs — verified against official statute text, not model recall:
| State | Nonrenewal minimum | Nonpayment | Source |
|---|---|---|---|
| New York | 45 days | 15 days | N.Y. Ins. Law § 3425 |
| Florida | 120 days | 10 days | Fla. Stat. § 627.4133(2)(b) |
| California | 75 days | 10 days | Cal. Ins. Code § 678(c), § 677.4 |
Three jurisdictions, three thresholds, one generic deficiency rule — the long-tail argument made concrete. Sub-cases outside the fact vocabulary carry explicit # SCOPE: comments; unconfirmed enactment dates carry # TODO(verify): markers rather than guesses.
Pack validator: provable disjointness — equal-priority rules are permitted when the validator can prove they never co-apply, via disjoint effective windows (rule versioning) or contradictory equality guards (jurisdiction scoping). It earned its keep immediately: it caught that the NY nonpayment rule lacked a state guard and would have applied its 15-day figure to Florida and California cases.
Verify it yourself
uv sync
uv run pytest kernel/tests demo/tests assurance/tests store/tests
uv run python -m duly_assurance verify124 tests. CI runs the full suite plus replay verification on every push.
Honest labels
Corpus cases are synthetic and use attestation grounding rather than fabricated documents. Extraction remains a scripted stub pending real adapters (M3). One pre-2026 NY rule version is marked DEMO-SYNTHETIC and exists only to demonstrate effective-dated replay. Breaking changes expected until v1.0.
Full changelog: v0.1.0...v0.2.0
🤖 Generated with Claude Code
v0.1.0 — M1: end-to-end vertical slice
First working milestone: the complete perception-proposes-logic-disposes loop, running deterministically end to end.
What works
- Grounded fact contract (spec + JSON Schemas): typed facts with clause-level provenance, calibrated confidence, and content-addressed ids; decision receipts with full derivation trees.
- Defeasible rule IR: YAML rule packs with priority, overrides, legal citations, and effective windows; a typed expression language with decimal-only money.
- Reference kernel: stratified deterministic evaluation with defeat semantics, effective-dated rule selection, human-outranks-machine conflict resolution, and receipt emission. No wall clock, no floats, no randomness.
- Two rule packs: New York termination-notice compliance (N.Y. Ins. Law § 3425) and federal TRID fee tolerance (12 CFR 1026.19(e)), each with declared expected outcomes run in CI.
- Two starters: synthetic documents with span-verified extracted facts for both verticals.
- Interactive demo (
uv run uvicorn demo.app:app --port 8788): grounding-span highlighting, derivation tree, citations, defeated-rule badges, receipt download, and as-of replay — change the evaluation date and watch the outcome flip under the rules then in force. - Deterministic audit reports: layered Markdown + PDF rendered purely from the receipt (byte-identical on re-render), with PII quote redaction. Example report.
80 tests. Breaking changes expected until v1.0.
Honest labels
Demo documents are synthetic; extraction is a scripted stub pending real adapters (M3); one pre-2026 historical rule version is marked DEMO-SYNTHETIC and exists only to demonstrate effective-dated replay.
🤖 Generated with Claude Code