Skip to content

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 15:21
· 72 commits to main since this release
00b55c6

Changelog

Added

  • An agent skill for writing bloomery projects. npx skills add morzecrew/bloomery
    installs bloomery-skills: a routing index from a task to the references it needs,
    covering specs, compiling, planning a change, quality rules, targets and the Python API.
    Every spec example validates against the exported JSON Schema, every Python import is in
    bloomery.__all__, and every bloomery command line names a command and flags the CLI
    accepts. A census, skills/coverage.toml, gives every spec kind, emit target, CLI command
    and quality-rule kind a reference that shows it or a reason it is out of scope, so a new
    one cannot land unrecorded. Retrieval is out of scope until a project outside bloomery's
    own examples declares a RetrievalSpec.

Changed

  • A catalog recipe's operands carry types. A recipe may declare types:, a mapping
    from names in its requires to logical types written as a canonical field's type:
    is. Before a recipe's expr: is evaluated, each operand's extraction is cast to the
    type types: names it, or else to the declared type of the canonical field of the same
    name, so arithmetic runs on declared types rather than on whatever an engine infers
    from a text-shaped extraction. A recipe whose expr: applies +, - (unary too), *
    or / to an operand typed neither way, or to a name requires does not bind, is now
    refused at compile time with UntypedRecipeOperand, naming the recipe, the operand and
    the fix; a types: key that requires does not name is refused at load.

    Upgrade note. This is breaking for a catalog whose recipe arithmetic uses an
    untyped non-canonical operand: its compile is refused until the recipe's types: gains
    the entry the refusal names. Every recipe whose expr: reads a typed operand now casts
    it, canonical ones included, so its silver model and the project's fingerprint move
    even where the catalog needs no edit; recompile and re-plan.

  • A catalog recipe's / is exact. A recipe's expr: is parsed SQL rather than a built
    transform, so its division carried no exactness marker: SQLGlot rendered it as a float
    division — an explicit CAST(x AS DOUBLE PRECISION) on PostgreSQL and CAST(x AS DOUBLE)
    on Trino — narrowed back to the declared decimal, so values needing more than ~15
    significant digits could round. Every / in the recipe, nested ones included, is now
    lowered through the divide transform's marker. Its dividend is cast to a decimal at least
    29 integer digits and 9 fractional wide, so integer operands divide fractionally, a
    dividend wider than the quotient does not overflow, and a nested quotient is not rounded
    before the next division; the result is narrowed to the declared type once. The division
    is the transform's —
    exact on PostgreSQL and Trino and rendered per dialect like any other division. DuckDB,
    which has no exact decimal division, is unchanged in being inexact there.

    Upgrade note. A project whose catalog carries a dividing recipe compiles a different
    silver model, so its fingerprint moves; recompile the project and re-plan against the new
    fingerprint. A project with no dividing recipe is unaffected.

Removed

  • The inexact_division advisory. A recipe's division is now lowered through the
    divide transform's marker, so no advisory stands where the reference's own rule says a
    refusal belongs; AdvisoryCode carries undeclared_audience only.

Fixed

  • A SQL expression SQLGlot cannot convert is refused as an unparseable one. SQLGlot's
    JSON-arrow path parser calls int() on a number token, so a text such as x->1e5 or
    x->>8e makes parse_one raise a bare ValueError rather than a SqlglotError. Every
    handler around a parse caught the latter (and RecursionError), so a step body or a
    sql_macro body carrying one crossed the compile boundary as a raw ValueError
    (#281), and an authored expr: was refused at load with SQLGlot's own text instead of
    bloomery's. The classes are now named once, (SqlglotError, ValueError), in
    spec/common.py, and caught at each of the five parse doors, so such a text is a
    StepError naming the step or a SpecParseError naming the field, as any other
    unparseable text is. DepthCappedSql still passes it on for the later stage that owns
    that field's refusals.

  • A definition's timeline shows an exact division as a division. Its changes printed
    the internal BLM_EXACT_DIV(...) marker wherever a divide transform or a catalog
    recipe's / moved a body. They now print the division it renders as:
    CAST(CAST(total AS DECIMAL(38, 9)) / qty AS DECIMAL(12, 4)) for ecom_basic's
    unit_price. The comparison still reads the IR's own text, so a body that changed
    still reports as changed.

  • A declared tax_basis: unknown is refused beside money. The tax-basis check
    tripped only on an absent tax_basis, so a field that declared tax_basis: unknown
    explicitly passed it: amount + adj compiled and added a figure whose canonical field
    says nothing usable about tax. unknown now poisons additive arithmetic by being
    absent or declared, exactly as the guardrails page states.

  • InvariantViolated reaches the shell as an internal error. It subclasses
    BloomeryError, so the command line's refusal arm claimed it and exited 1 — reading a
    bloomery bug as though the spec were at fault. It is now handled ahead of that arm: exit
    3, the traceback on stderr under the report line, as any other defect is.