rslc
rslc is the correctness-by-construction compiler for Rating Specification
Language. It is a standalone distribution and does not depend on
effectiveai-tools.
The bounded structured frontend for native Python authoring is specified in D-0127. It constructs syntax nodes directly and shares the text frontend's semantic checker and native runtime. See the Relativities example for the supported first slice and its limits.
Module orientation
| Path | Role |
|---|---|
crates/rslc-core/ |
Lexer/parser, CST, elaborator, typed Core IR, numeric kernel, reference evaluator |
crates/rslc-backends/ |
Python emitter and table CSV loader |
crates/rslc-py/ |
PyO3 bindings |
python/rslc/ |
CLI / Python API; std symlink for editable installs |
std/ |
RSL stdlib (wheel-bundled at rslc/std via build.rs + maturin include) |
lean/ |
Lean 4 numeric/eval model and extracted conformance vectors |
docs/decisions/ |
Binding design decisions cited by stable ID |
docs/field-guide.md |
Implementation surprises (worker-owned) |
docs/rating-outcomes.md |
Opt-in public rating outcomes and liveness diagnostics |
corpus/ |
Gitignored production fixtures (SHA-256 pinned via corpus/MANIFEST.json) |
rslc is the standalone compiler CLI. Effective agent sandboxes expose the
same five commands through the registered rsl toolkit, which delegates
directly to this parser and these handlers.
rslc owns source-to-artifact work only. Published-release operations use
raters list|show|download|rate|deploy|rollback in an Effective session or the
matching effective raters commands from a host shell:
RSL source -> rslc export -> bundle -> raters deploy -> raters rate/download
Strict rater publication contract
rslc check requires the source entry point to declare its RSL version, the
four catalog fields rendered by /raters, and an explicit public rating
outcome contract:
spec {
rsl: "0.3",
carrier: "Acme Insurance",
state: CA,
product: "Homeowners",
effective_date: 2026-08-28,
}
RatingOutcomes = {
written_premium: Policy.written_premium,
}
rsl names the RSL version the source is written for (D-0120). It is the
first field; rslc fmt prints it there. A missing or older version fails
rslc check (E-RSL-VERSION-MISSING, E-RSL-VERSION-OLD); a newer version
stops every command with E-RSL-VERSION-NEWER, which asks for a newer rslc.
rslc fmt <dir> --migrate runs the language steps newer than the declaration
and then declares the compiler's version. The bundle manifest records
rsl_version. Each required spec value must be non-empty. The catalog remains an open map,
so raters may carry additional metadata such as version. product is the
customer-facing rater label. The compiler rejects the retired name field.
Artifact inspection remains independent of source compilation. The strict
check guarantees a deployable rater can populate the list page.
Public rating outcomes
An RSL declares its explicit public outcome contract with a non-empty top-level
RatingOutcomes block:
RatingOutcomes = {
written_premium: Policy.written_premium,
technical_premium: Policy.technical_premium,
}
Each name is stable public metadata. Each target must resolve to a distinct,
computed attribute whose type is Premium, Num, Rate, Factor, Pct,
Bool, or an enum. At least one outcome must be a Premium (D-0098); a
Bool or enum outcome publishes a fact, such as a disclosure flag, next to
the premium. Multiple premiums are valid. Historical sources without the block remain parseable and
inspectable, but do not pass strict rslc check. See
rating outcomes for validation, the
E-ORPHAN-COMPUTED error (RSL 0.4), and the catalog/4 field.
An RSL 0.4 Rater.rate() evaluates only what the declared outcomes demand,
one map cell at a time (D-0113): admission first, then each outcome's
calculation. Calculations are total (D-0114), so this changes the work, never
the result. rate_detailed() runs the same evaluation and reports what it
computed; values and cells that nothing demanded are omitted. An RSL 0.3
source keeps the eager schedule, so a reject inside any calculation still
fires.
Sources and citations
A Source has a closed schema (D-0106):
Sources = {
manual: Source = {
artifact: artifact("019f819b-1674-7046-bc54-6f18cb149d6d"),
kind: pdf
}
}
[cite: source:region] is the only citation spelling. It can follow a table,
constant, step, attribute, input, or reject(...). The region must match the
Source's kind: p33 or pp20-24 (pdf), 'Sheet'!A2:AQH2 (xlsx), row 12 or
rows 12-20 (csv), and anchor "fragment" (html). The compiler checks the
region syntax and never opens the artifact.
Provenance is never a runtime value. Source-typed values, enums of artifact
IDs, _source*/__cite/source_part/source_*page* CSV columns,
*_verified-style Bool inputs that gate a reject or that nothing reads, and
*_complete/acknowledge_* Bool inputs that gate a reject fail with
E-PROVENANCE-RUNTIME. A table can
name one of its declared citations per row in an optional cite CSV column.
rslc fmt path/to/rater.rsl --migrate applies the mechanical rewrites: page
spellings, the two Source fields where the mapping is mechanical (other
Source fields move to a comment above the Source; a Source with no artifact ID
stays as written), __cite to cite, legacy artifact cite cells
([^a:<uuid>:p.N], [label](/artifacts/<uuid>?page=N)) to the one Source
with that artifact, and deletion of unread
*_source/*_fidelity fields and provenance CSV columns. It reports what
still needs an agent.
A failed rslc check, rslc eval or rslc export on a rater that declares
an older RSL, or no RSL version, prints one N-LEGACY-RSL note before the
diagnostics (notes in --json). The note names the migration path:
rslc fmt <dir> --migrate, then the remaining diagnostics.
python/rslc/legacy.py owns the note.
Exact numerics (D-0109)
Every runtime number is an exact rational. +, -, *, / and avg are
exact, and no operation rounds implicitly. Only round(x, scale, mode) and
root(x, n, scale, mode) narrow a value. The mode is required:
step premium [cite: manual:p12] {
base_premium: Premium = round(self.base_rate * self.factor, 0, half_up)
}
The modes are half_up (ties away from zero, the former R-0001 rule),
half_down, half_even, up, down, ceiling and floor. scale is an
integer and can be negative (-3 rounds to thousands). pow(x, n) takes an
integer n; root is the only fractional power.
A declared input precision (int or a power of ten such as 0.01) is an
admission contract: an input off its grid fails before rating. Strict
rslc check also requires every numeric input to declare a precision and a
range (E-INPUT-DOMAIN) and every rating outcome to pass through an
explicit round in an attribute that cites the filed rounding rule
(E-OUTCOME-ROUND). precision: exact admits any finite decimal inside a
required range, for caller values that no filing gives a scale for.
A stepwise fold rounds after every step, as filings state it:
product(self.operators as o by o.operator_number, o.factor, round_each: (4, half_up)).
The key orders the fold and must be unique within the list that the fold
reads; admission rejects a repeated key in that list (E-INPUT-FOLD-KEY).
In a source that declares rsl: "0.4", a selector (find, argmax,
argmin, topn) breaks a remaining tie by its list's keyed by key
(D-0123): numbers and dates by value, enum members by declaration order,
codes by UTF-8 bytes. A filed priority goes first,
argmax(self.drivers as d, d.factor, then [cite: manual:p43]: d.rank), and
find returns the first match in key order, so the order of the request
never decides a rating (E-SELECT-TIE over an unkeyed list). A computed
attribute declared with a precision type (Premium = Num {precision: 0.01})
must be proven on that grid, usually by an explicit round
(E-COMPUTED-RANGE).
rslc.debug.migrate_numerics(path, write=True) rewrites the
removed round(x, 0.01) form to
round(x, 2, half_up) and round(pow(x, 1 / n), 0.01) to
root(x, n, 2, half_up). It flags, and leaves unchanged, sites that need a
decision from the filing.
Entity input constraints
A record refinement over the record's own scalar inputs (RSL 0.4,
R = { … } where P; in RSL 0.3 a requires {} block) is an exact entity
constraint (D-0105, D-0114) when the exact plan represents it; an RSL 0.4
rule it cannot represent (if, a date difference, rounding) is a dynamic
refinement that only admission decides. The parser rejects the removed
requires exact {} spelling with a diagnostic that names the rewrite;
rslc fmt --migrate deletes exact. Every numeric input referenced by such a
constraint needs an authored or inherited grid: precision: int,
precision: 1, or a positive base-ten quantum such as precision: 0.01.
Numeric bounds may be absent or open. Authored endpoints are converted exactly;
each omitted side uses ±2^50 grid units at that scale. Date bounds may also be
absent or open; omitted sides use the calendar envelope 0001-01-01 through
9999-12-31.
The grid scale is bounded to 15 decimal places, and scaled units are bounded to
±2^50, so every shipped backend uses the same exact unit projection.
Large exact finite numeric domains belong in captured source data rather than
giant RSL membership literals. Declare a direct numeric alias such as
CensusBlockGroup = Num {precision: int, values: Csv} and provide
enums/CensusBlockGroup.csv with a value column. A scalar input whose type is
that alias implicitly carries the exact ordered membership constraint. The
compiler rejects missing, malformed, off-grid, duplicate, wrapped, indexed, or
computed uses. The CSV is captured in bundle provenance; rating never reads it.
The exact percentage form is selected_limit <= 70% of coverage_limit, where
70% is exactly 7/10. Ordinary Pct semantics remain fractional, so 0.70
means 70 percent. Exact constraints allow Boolean and closed-enum values,
dates, memberships and comparisons, addition and subtraction, static integer
multiplication, exact percentage ratios, and and/or/not. They reject
bare fractional factors, division, dynamic multiplication, computed
attributes, Code values, relations, collections, lookups, and folds. Rewrite
0.70 * x as 70% of x.
Refinements, record unions and admission (D-0114, RSL 0.4)
A source that declares rsl: "0.4" states every input constraint as a
refinement, T where P [code: "…", message: "…", cite: source:region], and
every alternative input shape as a union of named record types. Admission
parses a rate() payload against these types. It is the only place where a
rating can fail: it either builds the instance graph that rating reads, or it
refuses the request with every violation it finds.
spec { rsl: "0.4", … }
Types = {
Score = Num {precision: int} where self in [200, 999]
[code: "SCORE_OUT_OF_RANGE", message: "Score is outside the filed range.", cite: manual:p17]
}
Entities = {
Policy = {
inputs {
term: Num {precision: int} where self in [1, 12],
credit: ScoreAvailable | NoInsuranceScore | NotOrdered,
drivers: List(Driver) [children]
where len(self) in [1, 4] [code: "DRIVER_COUNT", message: "One to four drivers."]
where unique(self as d, d.number),
home: Home,
garage: Option(Garage)
},
step rating {
credit_factor: Num = match self.credit {
ScoreAvailable(s) => if s.policy_adapted_credit >= 700 then 0.9 else 1.1,
NoInsuranceScore(n) => 1.15,
NotOrdered => 1.2
}
}
} where self.term in [6, _] [code: "TERM_TOO_SHORT", message: "…", cite: manual:p4],
ScoreAvailable = { inputs { policy_adapted_credit: Score } },
NoInsuranceScore = { inputs { named_insured_age: Num {precision: int} where self in [16, 120] } },
NotOrdered = {}
}
| Form | Meaning |
|---|---|
x: T where P |
a field refinement; self is the field's value (an Option's present value) |
xs: List(X) where P |
a list refinement; self is the list (len(self), unique(self as x, x.k)) |
R = { … } where P |
a record refinement; self.f reads the record's inputs and its descendants' |
Name = T where P |
a refined type alias; every field of type Name inherits the clause |
credit: A | B |
a union of record types; the payload is {"tag": "A", …A's fields} |
x: R, x: Option(R) |
exactly one child record, at most one (null or absent) |
x in [a, b], (a, b], [a, _] |
interval membership (D-0117), also in calculations |
P is any total Bool expression over the refined value and its
descendants' inputs, in the calculation language: arithmetic and division,
dates (diff_years), rounding (floor, round), min/max, if,
match (on an Option child record too), folds and queries over the
value's own lists, constants and lookup! (D-0114). Admission evaluates it
with the calculation evaluator, so a lookup, a division or a fold in a
refinement carries the same D-0110 obligations as in a calculation
(E-KEY-RANGE, E-LOOKUP-GAP, E-DIV-ZERO, …), proved from the facts
admission has checked before it runs. It never reads an ancestor, a sibling
(through a Ref) or a calculation (E-REFINE-UPWARD): a rule that relates
a child to its ancestor is written on the ancestor over its children,
Policy = { … } where all(self.drivers as d, d.points <= self.term). A bound
has one spelling, self in [a, b] (E-REFINE-CANONICAL teaches the rewrite
of self >= a). A refinement on a calculation's type is a proof obligation
(E-COMPUTED-RANGE), only self in [a, b], with no annotation. Numeric
precision stays part of the type ({precision: 0.01}); a range is a
refinement. A union arm record holds inputs and refinements only; it is read
only by an exhaustive match that names every arm by type (E-UNION-USE,
no _).
The compiler sorts the conjuncts of each clause. Static ones (a value
interval, a number set, an enum member set, a Bool value, a list length
bound, and a record clause over the record's own scalar inputs, D-0115) are
D-0110 facts; the interval and the length bound are also JSON Schema
keywords (minimum, maximum, minItems, maxItems). Every other conjunct
is dynamic: admission checks it and the schema lists its clause under
x-rsl-refinements with its code and message. Admission checks every
clause before any calculation runs, so what a dynamic clause implies
(exists(self as d, d.rated = 1) makes the matching filter non-empty) is
a fact in the record's calculations too, and a union arm's clauses are
facts on that arm (D-0115). The same facts decide match exhaustion in a
calculation (E-MATCH, D-0050).
A violated clause with a code is a business violation; any business
violation makes the refusal a BusinessRejection, otherwise it is
InvalidInputs. Uncoded clauses take a code derived from the first failing
conjunct: INPUT-RANGE, INPUT-ENUM (a number set), INPUT-LEN,
INPUT-UNIQUE, INPUT-REFINEMENT. Shape and type errors are always
InvalidInputs: INPUT-TYPE, INPUT-VARIANT, INPUT-PRECISION,
INPUT-ENUM, INPUT-PATTERN. Both exceptions carry violations and
refusal_json, the canonical bytes that the Rust engine
(rslc.debug.admit_debug) also writes:
{
"error": "BusinessRejection",
"truncated": false,
"violations": [
{
"cite": null,
"code": "DRIVER_COUNT",
"constraint": "drivers: where len(self) in [1, 4]",
"message": "One to four drivers.",
"path": "Policy.drivers",
"value": 0
}
]
}
The order is fixed (D-0121). First the JSON shape and field types, walking
the payload in declaration order (a union's tag before its fields, unknown
keys after the declared ones in code-point order); an enum member, a Code
pattern and a precision grid are part of a field's type. Then refinements, in
a post-order walk of the value tree: every child value is checked before its
parent; a record's fields run in declaration order (a list's elements before
the list's clauses, a union's chosen arm with its record refinements), and
the record's own refinements run last. A value's clauses stop at the first
that fails, and a clause runs only when every value it reads passed its own
checks. A refusal reports at most 256 violations; its JSON stays under
60,000 bytes (strings in a violation keep 128 code points, a value keeps 32
nodes), so it fits the hosted worker's 64 KiB row.
A source declaring RSL 0.3 still compiles with requires {} and range
metadata (OLDEST_READABLE_RSL); rslc fmt <dir> --migrate rewrites both
(the RSL 0.4 step, pass constraints) and declares rsl: "0.4". In an RSL
0.4 source they are E-REQUIRES-REMOVED and E-RANGE-REMOVED, and RSL 0.4
syntax in an older source is E-RSL-SYNTAX.
rslc inputs sample covers every union arm and generates one instance per
entity; rows that admission refuses are skipped. --invalid (with
--representation rate) breaks one admission form per row and records the
refusal.
Records and maps (RSL 0.4, D-0121)
A source that declares rsl: "0.4" writes records instead of entities and
maps over enums instead of axes. A source that declares "0.3" keeps its
entities and axes and rates exactly as before.
Types = {
Coverage = { BI, PD, COLL, COMP },
Liability = Coverage where self in {@Coverage.BI, @Coverage.PD},
}
Records = {
Policy = {
inputs { zone: Zone, dwelling: Dwelling, vehicles: List(Vehicle) }
step totals [cite: manual:p9] {
total: Premium = sum(self.vehicles as v, v.total),
}
},
Dwelling = { inputs { cov_a: Money } },
Vehicle = {
inputs { symbol: Symbol }
step rating [cite: manual:p4] {
base[c: Coverage]: Rate = lookup!(BaseRates, Policy.zone, self.symbol).rate[c],
ilf[c: Liability]: Factor = lookup!(Ilf, self.symbol).factor[c],
premium[c: Coverage]: Premium =
if c in Liability then round(self.base[c] * self.ilf[c], 2, half_up)
else self.base[c],
multi_car: Factor = if index(self) = 0 then 1 else 0.9,
total: Premium = round(sum(Coverage as c, self.premium[c]) * self.multi_car, 2, half_up),
}
},
}
- A field typed
R,Option(R)orList(R)holds records; the record types form one tree with one root (E-RECORD-ROOT), each type at one field position (E-RECORD-POSITION) and none containing itself (E-RECORD-RECURSIVE). A calculation reads the enclosing record of typeAasA.x(E-ANCESTOR-READwhenAdoes not enclose it).index(self)andindex(v)give a record's position in its list, as sent. drivers: List(Driver) keyed by numbermakesnumberunique in the list (INPUT-KEYat each repeat). A fieldprincipal: Ref(Driver)orOption(Ref(Driver))on a record below the list's owner is sent as a driver'snumber; admission resolves it to that driver (INPUT-REFwhen none has it) andself.principal.agereads it.x[c: E]is a map over the enumEor a refined enumSub = E where self in {…};cholds anEmember. A read names its key:x[c],x[@E.m]orx[self.k]. A key outside the map's domain isE-MAP-DOMAIN, proved under the enclosingif k in Subandmatchguards; nothing defaults to 1.0. A map-valued column isF[E]and is read at a map binder or a member.sum(E as c, …),product,min,max,avg,count,existsandallfold over an enum or a subset. A CSV-backed enum cannot key a map (E-MAP-DATA-SIZED).Axes, per-element declarations,overfolds (E-AXES-REMOVED),Entities,[children],[parent]andEntityId(E-RELATION-REMOVED) are removed in 0.4.- An RSL 0.4 bundle publishes
rslc.catalog/5:recordswith each record's instance path (Policy.vehicles[]) and container, relation fields as{kind: record | option | list, record}, and map fields as{key, keys}. The nestedrate()payload is unchanged.
rslc fmt <dir> --migrate runs the records-maps step for RSL 0.4 on a
single-file rater: Entities to Records, parent fields deleted,
List(EntityId(R)) [children] to List(R), self.policy.x to Policy.x,
each axis to an enum named after it, static reads to member reads, bare reads
under a binder to keyed reads, over folds to enum folds, auto columns to
named columns, and per-subset declarations to one declaration with an
if c in {…} chain over a generated <Calculation>Domain refined enum. A
read that relied on two dimensions sharing element names is reported for an
agent: merge the two enums.
Refinement types (D-0110)
The compiler proves every partial operation before a program can run, so a
runtime error on an admissible input is a compiler defect. It infers a
non-relational fact for every value (an interval over exact rationals, a
decimal grid, a small finite set, non-zero, non-empty, Some, enum and
axis-element sets) from input contracts, table data, arithmetic and guards
(if, match, and/or, else reject(…)), requires constraints and the
differences that comparisons spell (D-0115).
Inference is one pass per attribute body and needs no solver.
| Obligation | Code |
|---|---|
| lookup key inside its dimension | E-KEY-RANGE |
| divisor non-zero | E-DIV-ZERO |
min/max/avg collection non-empty |
E-EMPTY-FOLD |
| indexed read names a defined element | E-AXIS-INDEX |
| RSL 0.4 map read names a key in the map's domain | E-MAP-DOMAIN |
topn limit is an Int in 0.. |
E-TOPN-LIMIT |
pow exponent whole (base non-zero if negative) |
E-POW-EXPONENT |
root degree >= 1 (radicand >= 0 if even) |
E-ROOT |
declared computed range proven |
E-COMPUTED-RANGE |
| one acyclic attribute graph over all entities | E-CYCLE |
| computed band key on the cut grid (D-0123) | E-KEY-HOLE |
selector reads a keyed by list (D-0123) |
E-SELECT-TIE |
| declared computed precision proven (D-0123) | E-COMPUTED-RANGE |
In RSL 0.3 the only escape is checked(x, T, reject("CODE", "message")), a
declared business rejection that lowers to an ordinary guard in both engines.
RSL 0.4 has no escape (see "Total calculations"): the author narrows the
inputs, writes the filed clamp, or deletes the declared range. Computed
ranges are never checked at runtime. The RSL 0.3 step of rslc fmt --migrate
wraps an unproven declared computed range in checked and deletes an
otherwise the facts prove dead; it lists every other obligation for an
agent. The API is rslc_core::refine::Refinements.
Total calculations (D-0121, RSL 0.4)
An RSL 0.4 calculation cannot reject: admission is the only place a rating can fail (D-0114), and a business rule that refuses a request is a coded refinement on the inputs it reads. Three forms are removed, each an error that names its rejection census class and the rewrite:
| Code | Removed form | Rewrite |
|---|---|---|
E-REJECT-REMOVED |
reject("CODE", "message") |
input-domain rule: a refinement on the input; cross-level: a record refinement on the ancestor; computed boundary: a total construct (D-0123); … |
E-OTHERWISE-REJECT-REMOVED |
otherwise: reject(…) |
narrow the key type to the filed rows (refined or identity enum), unfiled cells, or the cited filed all-other row |
E-CHECKED-REMOVED |
checked(x, T, reject(…)) |
narrow the inputs x reads, write the filed clamp, or delete the declared range |
error[E-REJECT-REMOVED]: `reject("LIMIT_HIGH", …)` in `Policy.limit_factor` was removed in RSL 0.4: a calculation cannot reject. This is an input-domain rule (rejection census class A1)
= because: RSL 0.4 calculations are total: admission is the only place a rating can fail, so a rule that refuses a request is a refinement on the inputs it reads (D-0114, D-0121)
= help: delete the rejecting branch and state the rule as a refinement of `Policy`: `Policy = { … } where not (self.limit > 80) [code: "LIMIT_HIGH", message: "limit above 80"]` (add `cite: <source:region>` to the annotation). …
The forms still lower, so rslc fmt --migrate can analyse them, and they
keep the program's meaning, so the other checks still run beside them. No
engine runs such a program: structural verification refuses a 0.4 program
that holds a reject or otherwise: reject, and the reference evaluator
treats one as an invalid program, never as a business rejection. RSL 0.3
sources keep all three forms and rate as before.
The rejects pass of rslc fmt --migrate (the RSL 0.4 step) rewrites only
what provably changes no rated value: it deletes every site the refinement
facts over the bundle's tables prove dead (census D), and lifts if <rule on the record's own inputs> then reject("C", "m") else e, reached through if
branches and strict operands, to e plus where not (<rule>) [code: "C", message: "m"] (census A1, B/A4, A2). Deadness is proved in RSL 0.4 meaning:
the proof assumes no other removed form (a reject is an unknown value, a
table that keeps otherwise: reject has unknown outputs) and drops a
declared computed range that the facts do not prove. A lifted rule refuses
the same requests at admission, with its code among the violations; when
several rules fail, the first violation can differ from the reject RSL 0.3
reached first. Every other site stays, and the strict check lists it with
its class and rewrite. The classifier is rslc_core::reject_sites.
Quality rules (D-0124)
RSL has no warnings. Every quality rule is an error that teaches its
canonical rewrite: rslc check prints the rule and the rewrite under the
error, and --json adds because, help and applicability (machine,
placeholders or manual) to the diagnostic.
The rules apply to a source that declares rsl: "0.4" (the version
rslc fmt --migrate targets). A source that declares rsl: "0.3" compiles
as before until it is migrated.
| Code | Rule |
|---|---|
E-HOIST |
a child's computed value that reads only ancestor data is defined on the ancestor |
E-ORPHAN-COMPUTED |
every computed attribute reaches a declared rating outcome |
E-DERIVABLE-INPUT |
an input that must equal a function of other inputs is computed instead |
E-TABLE-UNUSED |
every table and every output column is read by a lookup |
E-TABLE-UNUSED-DIM, E-TABLE-SINGLE-MEMBER-KEY |
every dimension changes an output |
E-TABLE-DUPLICATE |
one output column is not a copy of another table's |
E-TABLE-CONSTANT-COLUMN |
a column with one value is a constant |
E-TABLE-FORMULA |
a column that is a * key + b over 32 or more keys is a formula |
E-IF-AS-TABLE, E-ENUM-NUMERIC |
a classification of a number is a band dimension and a table |
E-PROVENANCE-COMMENT |
evidence lives in [cite: …], not in comments |
E-PROVENANCE |
tables, constants and rounding steps cite their source, or carry [reason: "…"] |
A program whose only errors are quality rules still runs every semantic
check, and rslc check reports its strict-check and table errors in the
same run. See D-0124 for the exact rules and the retired invariants.
Static cost (D-0125)
rslc check computes the work of one rate() as a polynomial in list sizes,
with no solver: attribute evaluations, axis cells, table lookups and fold
iterations, where |Policy.drivers| is the longest such list in one
instance. It prints one line per rating outcome; --json reports cost
(the whole rate(), each outcome, the 20 costliest computed attributes,
and every super-linear fold), and the catalog carries each outcome's cost. Branches
cost their most expensive arm, so lookups and iterations are upper bounds;
attribute evaluations and axis cells are exact for the generated runtime when
every list of one relation has the same length.
A fold that walks one list once per element of that same list makes the
work quadratic. When the walk does not depend on the repeated element,
E-QUADRATIC names the rewrite: compute it once on the ancestor (or in its
own attribute) and read it. The rule applies to sources that declare
rsl: "0.4"; the cost report applies to every version. A walk that depends
on the repeated element (a rank) is reported in cost.superlinear only; a
declared list bound will discharge it once lists carry bounds. The catalog
cost is outside the bundle identity.
Dimension types
A Types entry can declare a table key dimension (D-0107):
Types = {
AgeBand = IntBands(..<16, 16, 18, 25, 85..),
AmountBand = DecimalBands(scale: 2, unit: USD; ..<100.00, 100.00, 1000.00..),
BiLimit = IntValues(25000, 50000, 100000),
Zip = Code(pattern: "[0-9]{5}")
}
A band column cell holds the band's lower cut or ..; the key selects its
band by binary search. A value-set cell holds one listed value; the key
matches by membership. A Code cell holds one code; the key matches by exact
identity. A value-set type can type an input (admission enforces membership),
and a Code type can type inputs and attributes (admission enforces the
pattern; both engines run the compiler's Thompson NFA, so matching is linear).
A band dimension's number kind is the precision its bands are filed at. In a
source that declares rsl: "0.4", a computed key must be on that grid: write the filed rounding,
round(score, 0, half_up), in a cited step. When the filing defines the bands
over the unrounded value (interpolation segments, "up to and including x"),
declare it: IntBands(key: unrounded; 10, 15, 20..). Otherwise a key between
two filed values would land in a band by the spelling alone (E-KEY-HOLE,
D-0123). otherwise: reject(…) does not cover a computed key outside the
bands: an open band or a filed otherwise row does.
Excel and model-JSON export reject programs that declare dimensions.
Tables
RSL has one table form (D-0108):
Tables = {
DriverClass [cite: manual:p22] :
table dims (age: AgeBand, marital: MaritalStatus) -> {
factor: Factor[coverage]
}
columns: { age: "age_band" }
ZipFactor [cite: manual:p40] :
table dims (zip: Zip) -> { factor: Factor }
otherwise: reject("ZIP_UNRATED", "zip is not in the filed list")
}
Every key cell is exact: no _, no ranges, no sets. Rows are unique and
their order has no meaning. otherwise: { … } or (RSL 0.3 only)
otherwise: reject("CODE", "message") covers every key tuple that no row
names; RSL 0.4 removes the rejecting form (E-OTHERWISE-REJECT-REMOVED).
rslc check runs one linear checker on every table. It packs each row into
one mixed-radix key and reports repeated keys (E-LOOKUP-OVERLAP). It has
no budget or cache. Entity requires constraints do not take part.
E-TABLE-UNUSED-DIM and the other table quality rules are RSL 0.4 errors
(see "Quality rules"). Runtime lookup in Rust and generated Python is the
same packed key and one hash probe.
Totality depends on the declared language (D-0120):
- RSL 0.3 (D-0108): every tuple of the dims has a row or
otherwise; the checker compares the distinct-key count with the product of the dimension sizes (E-LOOKUP-GAPon the table, with the smallest missing tuple) and reports anotherwisethat covers nothing (E-OTHERWISE-DEAD). ACodedim needsotherwise, because its domain is open. - RSL 0.4 (
spec { rsl: "0.4", … }, D-0122): an output cellunfiledmarks a cell the filing leaves blank; a row whose outputs are allunfiledis absent (not indexed, not stored in SQLite), and a row with only some isE-TABLE-COL(split the table per output). Totality is an obligation of eachlookup!call site: the key tuples the site's keys can take, by their inferred facts (narrowed and sibling enums, guards,requires, ranges, mapping-table outputs), must all have a filed row, or the table needsotherwise(E-LOOKUP-GAPat the lookup, with the smallest absent tuple and the rewrites). Anotherwise: reject(…)(itselfE-OTHERWISE-REJECT-REMOVED) that no call site needs is alsoE-OTHERWISE-DEAD.otherwise [cite: source:region]: { … }is the filed all-other row and must cite it.
match: specific, match: range with its boundary modes, NumRange pairs,
[type: …] column annotations and _ wildcard cells are removed
(E-TABLE-MODE, E-TABLE-COL, E-TABLE-WILDCARD). The bundle-level
migration rewrites legacy tables and their CSVs in place:
import rslc.debug
report = rslc.debug.migrate_tables("path/to/rater.rsl", write=True)
It reuses the D-0107 migrate_dimensions planner for NumRange pairs. It is
idempotent, and it reports each table it cannot rewrite, with a reason for an
agent. See D-0108 for the exact passes.
No extern tables
RSL has no extern tables (D-0111). [extern: …], bare [extern],
implementation:, path:, sqlite_table: and reject: on a table fail
with E-EXTERN-REMOVED. A large identifier lookup is an ordinary table with a
Code key and otherwise, with its rows in tables/<Name>.csv. The compiler
checks every row at build time and stores a large table as SQLite in the
bundle; no authoring action is needed. The externs pass of
rslc fmt --migrate converts a CSV or SQLite extern. A Text key whose
codes have no simple pattern (3+, <=50%) becomes an enum in
enums/<Type>.csv whose external values are the codes.
No Text type (D-0112)
RSL has no Text type. Declaring it in any position is E-TEXT-REMOVED: an
identifier is a Code(pattern: "…") type and a closed set of labels is an
enum. The text pass of rslc fmt --migrate deletes each Text input that
no rule or scenario reads, and each Text alias that nothing else names. A
# comment records each deleted declaration in its place. Reads come from the
elaborated program, so a name in a string or comment is not a read. The pass
lists every other Text for an agent. Admission is strict, so a caller that still
sends a deleted input is rejected once the migrated rater is deployed.
Large tables in the bundle
Storage is a bundle concern, not an authoring choice (D-0111). The canonical
CSV stays the source of truth: rslc check checks it and the Rust evaluator
reads it. At export, a table with at least 100,000 CSV cells (data rows ×
header columns) is also written as tables/<Name>.sqlite, which the
generated Python reads; the bundle keeps the CSV as a source file that rating
never reads. The file is one WITHOUT ROWID table
keyed by the lookup key tuple (a Code key as its text, every other key as
its dimension coordinate), with each output as its exact CSV text. The
compiler writes the SQLite format itself, so the same source gives the same
bytes on every machine.
The manifest entry declares format: "rslc.sqlite-table/1", the row count,
and the path and hash of the canonical CSV it came from. The manifest
identity check re-derives the file from that CSV. Rater.load() copies the
file into a private snapshot while hashing it, opens the snapshot read-only,
checks its layout and row count, and on Linux unlinks it at once. Each lookup is one
primary-key query; the generated Python needs only the standard library.
A table that the index cannot represent exactly stays CSV, and export reports
it in csv_fallbacks (see D-0111).
What you have when this lands
Each claim is present-tense. A claim without a passing proof command does not belong here.
You have a compiler-backed language, not a grammar plus accidents. Binding
semantics live in docs/decisions/ (numeric model, Core IR, formula subset,
eval schedule), the Lean 4 theorems under lean/, and the closed Core IR ADTs
in rslc-core. A separate prose docs/spec/ document is not checked in yet
(see status table). Every construct exercised by the golden corpus parses
equivalently to the recorded Lark oracle, elaborates, and evaluates — or is
rejected with a spanned diagnostic.
Proof: pytest tests/golden/ — zero parse divergence vs. committed recorded
trees (in-repo) / corpus/PARSE-GOLDENS.json hashes (optional corpus);
IR / elaborator cite decision IDs. The recorded trees are frozen output of
the retired legacy Lark parser (see tests/golden/test_parse_equivalence.py).
You have a compiler whose acceptance is a guarantee. Elaborating a rater
(via the rslc.debug APIs program_json / compile_debug, or rslc export)
either fails with a
spanned diagnostic — source span, explanation, the invariant ID it violated —
or succeeds, and success means something: every reference resolves, every
lookup is total, every type checks against a closed ADT that cannot represent
an illegal state, every aggregation has a pinned order, every nl stub is a
tracked typed hole. There is no "compiles but crashes on a real applicant."
Proof: cargo test -p rslc-core — every obligation has accept + reject cases;
all 4 corpus raters elaborate clean.
You have machine-checked mathematics under the money path. The rounding and
evaluation semantics aren't documented intentions — they're Lean 4 theorems
(determinism, totality, idempotence, monotonicity and within-one-increment
correctness for all seven rounding modes, half_up equal to the former
half-away rule, integer nth-root bounds, exact-semantics fold invariance) that
a proof checker verifies on every CI run, with zero sorry. Lean extracts the
adversarial cases (ties for every mode and sign, negative scales,
non-terminating rationals, roots) as exact conformance vectors that the Rust
kernel, the Rust evaluator and the generated Python runtime must reproduce
exactly.
Proof: lake build + lean/scripts/regen_conformance.sh (idempotent vs
conformance/) + cargo test -p rslc-core conformance.
You have two execution paths that are checked to be the same rater. The
Rust reference evaluator owns the semantics. One IR projects to a coupled,
zero-dependency Python artifact with explicit Rater.load(), exact
rater.evaluate(), and ergonomic nested rater.rate() (D-0090/D-0091); this
generated Python bundle is the production artifact. The reference evaluator and generated Python are compared on
every returned tagged value, with exact rational values. Authored business
rejections must also agree. This is bounded differential evidence, not a proof
of business correctness or complete axis-lane coverage. RSLC has no Excel
backend (D-0104).
Run pytest tests/differential/ for the committed synthetic corpus, generated
cases with independent checkpoints, and tests of the comparison harness itself.
Every declared case must execute. Preparation errors, missing outputs, and
engine failures fail the campaign. See compiler testing
for private fleet runs and the precise comparison contract.
You have the insurance domain knowledge as a living, compounding registry.
Every one of the ~30 legacy rsl_ast checks has an explicit disposition
(ported / subsumed by the type system / dropped with rationale) — the dimension
analysis, the domain-coverage family, the table-extraction forensics all survive
as first-class invariants with stable citable IDs, severities, and named
profiles selectable at compile time. Adding a learned rule is a
one-registration-point, visitor-API, template-documented change with a mandatory
mutant kill — the mechanism by which the language grows as agents learn.
rslc_core::invariants::explain documents each I-NNNN for library and
compiler development use; it is not a public CLI verb.
Proof: cargo test -p rslc-core registry_completeness — CI fails on any legacy
check without a disposition or any invariant without a killing mutant; see
docs/invariants.md and invariants::disposition::LEGACY_DISPOSITIONS.
You have native scenario execution. A scenario .rsl (optionally
importing a rater) elaborates and runs without the legacy toolkit:
instance graph bootstrap (empty [children] → []), evaluate targets,
assertions, instance provenance, and hole-blocked attrs for mid-build agents.
CLI: rslc eval path.rsl; use --partial only for incomplete authoring
inspection.
Bundle helper: PreparedRater(bundle_dir); bound invariant checks use
PreparedRater.check_invariants().
emit_python(source_path, allow_holes=False, bundle_dir=None) is an in-place
complete-bundle export (D-0091). It loads the existing exact-case
CSVs, rejects rows wider than their headers, validates and proves D-0054
totality, then writes rater.py and rater-manifest.json beside the
source bundle, plus tables/<Name>.sqlite for each large table (D-0111).
CSV tables are never copied or rewritten. bundle_dir defaults to
the source parent. The bundle ID binds the compiled program, ordered exact
tables, root/imported RSL, and CSV-backed enum sources. Rater.load() checks
containment and raw hashes for every listed source and table, parses tables
once, and privately owns them; reused rate() / evaluate() calls perform no
hashing. A large table is written once as indexed SQLite and read per lookup
from a verified private snapshot (D-0111).
Proof: rslc.run_scenario on MGT combined-sample-scenario.rsl →
_premium=11052 (legacy oracle); pytest tests/differential/.
You have calibrated invariant profiles. build keeps holes informational;
verify treats unresolved holes and bare referenced [skip] as Obligations
while I-16/I-4 ignore collection/__coll_* folds — so accepted Farmowners is
Obligation-clean under verify (D-0034).
Proof: Registry::check(..., ProfileName::Verify) on mgt-ca-farmowners.
Mutation replay is supplemental evidence and remains under oracle revision.
Mutation and differential runs provide supplemental evidence. Committed reproducer and summary artifacts are historical and unavailable private corpus rows are reported as skips; conformance remains the acceptance gate.
You have speed that changes what's affordable. Published benchmarks vs. the
old toolchain — compile time and batch evaluation throughput — with
order-of-magnitude wins on compile/MGT eval and no-regression batch eval
in the historical persistent-evaluation samples (D-0030); rerun benchmarks for
the current PreparedRater.eval_batch implementation. Exact rational
arithmetic (D-0109) costs about 1.1–1.7× per evaluated row against binary64 in
the Rust evaluator; see docs/benchmarks.md.
Proof: ../../.venv/bin/python tests/benchmarks/run.py --corpus corpus/ →
tests/benchmarks/reports/benchmark-summary.json + docs/benchmarks.md
(D-0029 / D-0030).
You have a product, not a prototype. pip install of the wheel from a clean
environment gives you the rslc CLI (export to python/bundle;
--version) and the checked Python API (PreparedRater,
emitters, and input/scenario helpers) on any machine with nothing else
installed — including the RSL stdlib under rslc/std (so import "std/…"
resolves without the source checkout). Raw program/table inspection hooks are
available explicitly under rslc.debug. Path-filtered CI jobs
cover rust / lean / wheel smoke when effectiveai-tools/raters/rslc changes.
Differential, mutation, and benchmarks remain local corpus-gated pytest.
Proof: fresh-venv wheel install + CLI/API smoke (the rslc-wheel CI job
runs exactly this), including tests/test_bundled_std.py.
CLI
The public command surface is deliberately small:
rslc check path/to/rater.rsl
rslc check path/to/rater.rsl --allow-incomplete
rslc eval path/to/scenario.rsl
rslc eval path/to/scenario.rsl --partial
rslc export path/to/rater.rsl --format python
rslc export path/to/rater.rsl --format bundle --out path/to/rater.zip
rslc fmt path/to/rater.rsl
rslc fmt path/to/rater.rsl --check
rslc fmt path/to/rater.rsl --migrate
rslc inputs sample path/to/rater.rsl --n 100 --seed 7
rslc inputs validate path/to/rater.rsl input.json
Commands print concise human-readable output by default. Pass --json to emit
exactly one JSON value on stdout. Failed checks, evaluations, input validation,
and format checks return a nonzero status. check is strict by default;
--allow-incomplete only softens unresolved authoring holes.
check reports each diagnostic at file:line:column in the file that
contributed it, including imported files. In --json output, each located
diagnostic has a location object: file, one-based line, column,
end_line, and end_column (byte columns), and start and end (byte
offsets in that file). span indexes the merged import buffer, which differs
from every input file; use location to find the source.
One spelling per concept
Each concept has one source spelling (D-0105). The parser rejects a removed spelling with a diagnostic that names the canonical form:
| Removed | Canonical |
|---|---|
-- and // comments |
# |
× and ÷ |
* and / |
Int, Integer, Decimal |
Num |
Percent |
Pct |
Str, String |
Text, then E-TEXT-REMOVED (D-0112) |
requires exact {} |
requires {} |
Option(Num {…}) |
Option(Num) {…} |
These are the spelling stage of rslc fmt --migrate (see "Migrating a
rater bundle to RSL 0.3"). The source passes live in
rslc_core::syntax::migrate::PASSES; each pass is independent and
idempotent, and the strict parser rejects every finding of every pass.
Migrating a rater bundle
One command runs every migration step newer than the bundle's declared RSL
over a whole rater bundle: the entry source (rater.rsl or batch.rsl),
every local import, tables/*.csv and enums/.
rslc fmt path/to/rater-dir --migrate --json # writes the bundle
rslc fmt path/to/rater-dir --migrate --check --json # plans, writes nothing
A path to any source inside the bundle selects the same bundle. The passes
run in this order (rslc.migrate_bundle.PASSES); each is mechanical and
idempotent:
| Pass | Decision | Rewrites |
|---|---|---|
spelling |
D-0105 | removed spellings (every later pass parses strictly) |
numerics |
D-0109 | round(x, 0.01) and round(pow(x, 1 / n), …) |
citations |
D-0106 | page spellings, closed Sources, __cite, runtime provenance |
dimensions |
D-0107 | sentinel tails, degenerate values, NumRange bands |
tables |
D-0108 | match: tables to table dims with otherwise |
externs |
D-0111 | CSV or SQLite extern tables to Code-key tables with otherwise |
text |
D-0112 | unread Text inputs deleted |
refinements |
D-0110 | unproven computed ranges to checked, dead otherwise deleted |
constraints |
D-0114 | RSL 0.4: requires and range to refinements |
rejects |
D-0121 | RSL 0.4: dead rejections deleted, input-only guards lifted |
Then every source is formatted and the bundle is strictly checked. The run
uses a private copy of the bundle; files are copied back only when it
finishes, and never with --check. The JSON report holds passes (one
summary per pass), files (changed paths, relative to the bundle), agent,
check_ok and complete. agent lists every
item that still needs an agent, as
{pass, code, file, line, column, message}: strict-check errors located in
their source file (1-based line and character column), tables that the
tables pass left in a removed form with its reason, externs the externs
pass could not convert (or whose callers must now send a string code), and
numerics sites that need a decision. complete is true when agent is empty and the check
passes. The exit status is 1 only when the run stops (an unparsable entry) or
when --check finds a change.
The per-pass entry points (rslc.debug.migrate_numerics,
migrate_dimensions and migrate_tables) remain for debugging one pass.
Generate input-contract cases
Use the rater's .rsl source file:
rslc inputs sample path/to/rater.rsl --n 100 --seed 7 --json-lines samples.jsonl
rslc inputs sample path/to/rater.rsl --n 100 --seed 7 --representation rate
rslc inputs validate path/to/rater.rsl input.json --json
The compiler proves that each input contract has a constructive witness.
Generation covers constraint literals, field bounds, Boolean and enum members,
and then seeded interior values. It emits exactly n distinct rows or a
structured insufficient_distinct_samples error. The same generator version,
contract hash, seed, and row index produce the same row.
Sampling does not load rating tables or execute the rater. A positive row
satisfies required Core inputs, entity graph rules, field domains, and every
top-level requires rule. Deliberate business rejection is a valid result for
such an input, not an input-contract failure. Optional inputs are omitted.
The default evaluator representation keeps named instance graphs for
evaluate(). The opt-in rate representation emits
rslc.rate-inputs.batch/1 and rslc.rate-inputs.row/1 envelopes whose input
is the nested public Rater.rate() payload. It nests child instances, removes
relation fields, and converts enum source names to external values. Negative
samples cannot use this representation because malformed instance graphs do
not have a lossless nested payload form.
For evaluator representation, stdout is one rslc.inputs.batch/1 JSON object.
--json-lines OUT also writes only rslc.inputs.row/1 records to OUT. Each
row keeps the named instance graph under input. generation records the
generator version, seed, row index, coverage target, program identity, and
contract hash. Bundle provenance uses an adjacent rater-manifest.json for the
bundle ID and program fingerprint when available. Sampling does not depend on
the rest of the bundle format.
Sampling always compiles the current source. rslc export --format python
writes rater.py and rater-manifest.json beside the source, so an edit to
the source or its tables after an export leaves them stale. Sampling ignores a
stale manifest: one that is intact but records other source, table, extern, or
Core program bytes, or a table or extern file that is gone. The program identity is then core_ir. Export again to record
manifest provenance. A manifest that is forged, malformed, or from an older
manifest format still fails sampling.
Validation checks any named graph without loading rating tables or running the rater. Deliberate negative generation remains available through the Python API for compiler testing, but is not an agent-facing CLI operation.
Generated Python result selection
Rater.rate(payload) evaluates and returns declared RatingOutcomes at their
canonical nested paths (D-0113). Rater.rate_detailed(payload) runs the same
evaluation and returns every value it computed (only the read cells of an
indexed attribute) for calculation inspection. The corresponding schemas are
output_schema and detailed_output_schema. A source without outcome
declarations must use the detailed method, which then evaluates everything.
evaluate() returns the same trace as a flat wire map. Existing
immutable bundles need rebuilding and releasing to adopt the new default.
Development
cargo fmt --check
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo test --locked --workspace
cd lean
lake build
# optional: bash lean/scripts/regen_conformance.sh # must be byte-identical
python -m pip install -e '.[dev]' # or: maturin develop --release -m crates/rslc-py/Cargo.toml
pytest tests/
rslc --version
rslc check path/to/rater.rsl
rslc export path/to/rater.rsl --format python
rslc eval path/to/combined-sample-scenario.rsl
rslc eval path/to/holey.rsl --partial
The design contract is that the typed Core IR owns semantics. The Rust reference evaluator and generated Python must agree bit-for-bit.
See docs/decisions/ for binding design decisions and docs/field-guide.md
for durable operational notes. The production facade contract is documented
next to the facade itself in
effectiveai-tools/src/toolkit/toolkits/rsl/facade.md.
Metadata
Release files for rslc 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| rslc-0.3.0-cp312-abi3-win_amd64.whl | CPython 3.12 | abi3 | Windows x86-64 | Details |
| rslc-0.3.0-cp312-abi3-manylinux_2_28_x86_64.whl | CPython 3.12 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| rslc-0.3.0-cp312-abi3-manylinux_2_28_aarch64.whl | CPython 3.12 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| rslc-0.3.0-cp312-abi3-macosx_11_0_arm64.whl | CPython 3.12 | abi3 | macOS 11.0+ ARM64 | Details |
| rslc-0.3.0-cp312-abi3-macosx_10_15_x86_64.whl | CPython 3.12 | abi3 | macOS 10.15+ x86-64 | Details |
Total release size: 34.4 MB
Release files / rslc-0.3.0-cp312-abi3-win_amd64.whl
| Download URL | rslc-0.3.0-cp312-abi3-win_amd64.whl |
|---|---|
| Size | 7.2 MB |
| Tags | CPython 3.12 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
92dd9c8266b2b51113610239cb9eb73d7a61ec6d8a2a2353a3c8ca617976726a
|
|
BLAKE2b-256 checksum How to use checksums |
afddf739e2385b145feb51b3f6f98fdb680679a84b18f3d26f940842c763b924
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / rslc-0.3.0-cp312-abi3-manylinux_2_28_x86_64.whl
| Download URL | rslc-0.3.0-cp312-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 7.2 MB |
| Tags | CPython 3.12 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
58f361fb8d9da93435c8a778d6aa45abc5c0eb1b046bca920637a274c149107d
|
|
BLAKE2b-256 checksum How to use checksums |
fbb783ac5f546a400956c84f2d3c573e3c37b0d6d7fd670d45bb8f263346ce38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / rslc-0.3.0-cp312-abi3-manylinux_2_28_aarch64.whl
| Download URL | rslc-0.3.0-cp312-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 6.6 MB |
| Tags | CPython 3.12 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
b9993e6ebfa33ac387bf90ece9d786081f28f8361544cc49d30c367654c3a39b
|
|
BLAKE2b-256 checksum How to use checksums |
6d8dea9b4791195f2e1e0565acc8f3ee017c9ce1d7eca573867bc45a393f3aeb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / rslc-0.3.0-cp312-abi3-macosx_11_0_arm64.whl
| Download URL | rslc-0.3.0-cp312-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 6.4 MB |
| Tags | CPython 3.12 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
6b6df85b1966c6ee7406b6b1bb7e1948e84c021193027c42961b451320f21eb2
|
|
BLAKE2b-256 checksum How to use checksums |
7f291412874c82a46d42138a9a172b2a7f289b07ef400e82c5239e69b4fd067c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / rslc-0.3.0-cp312-abi3-macosx_10_15_x86_64.whl
| Download URL | rslc-0.3.0-cp312-abi3-macosx_10_15_x86_64.whl |
|---|---|
| Size | 7.0 MB |
| Tags | CPython 3.12 abi3 macOS 10.15+ x86-64 |
|
SHA-256 checksum How to use checksums |
6525a52aaa0f4cdc61c671ba49793cfc4d101445e26bceb03175a66c25d8c554
|
|
BLAKE2b-256 checksum How to use checksums |
17b9d112517376e94996e7da2968c2609c4ffd3329427173f8672da04888ac13
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log