Skip to content

Feat/runner native decimal slice3#443

Merged
cukas merged 3 commits into
mainfrom
feat/runner-native-decimal-slice3
Jun 16, 2026
Merged

Feat/runner native decimal slice3#443
cukas merged 3 commits into
mainfrom
feat/runner-native-decimal-slice3

Conversation

@cukas

@cukas cukas commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What

Why

How

Checklist

  • tsc -b passes
  • pnpm test passes
  • pnpm test:kern passes
  • pnpm lint passes
  • kern review packages/ --recursive checked

…at-base spec)

The discriminating 3-leg (TS/Python/runner) spec for runner-native Decimal
slice 3 — the PRODUCER / WRITE path. Defines "done" before any impl and keeps
the builder honest: 26 producer value cases (sub/neg/abs/div/mod/pow + nesting)
asserting refOut === tsOut === pyOut byte-exact, plus re-admit fail-close and
abstain regimes.

Expecteds COMPUTED under the pinned prec-28 / ROUND_HALF_EVEN / modulo-ROUND_DOWN
context (never guessed). Killers (each RED-at-base for the right reason — the
runner abstains on the unimplemented producers; the abstain cases already pass):
  - div(1,3)=28 threes (kills host-float), div(2,3)=…667 (kills ROUND_DOWN)
  - mod(5.5,2)=1.5 (kills int-truncation), mod(-5.5,2)=-1.5 & mod(5.5,-2)=1.5
    (sign-of-dividend — kills floored-mod)
  - pow(2,-1)=0.5 (negative integer exp allowed), pow(0,0)=1 (0**0 special-case)
  - pow(0,-1) MUST throw POW_ZERO_NEGATIVE (kills using decimal.js .pow directly,
    which yields Infinity instead of the kernel guard)
  - pow(of("2"), d) with a variable exponent MUST throw POW_NON_INTEGER — THE
    soundness-critical guard-in-seam case: slice-2 variable operands mean a naive
    impl would resolve d and compute a value; the runner must replicate the
    compile-gate and refuse exactly what the emitters refuse
  - literal-zero divisor refuses BEFORE an abstaining sibling operand evaluates
    (forces the syntactic gate into the pre-eval phase)
  - neg(0)/sub(3,3)/mod(-4,2)=0 (signed-zero clamp via kernDecimalStr)

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
…guarded div/mod/pow

Make the ReferenceRunner EXECUTE the Decimal producer/write path natively as the
3rd leg of 3-way parity, computing on a LOCAL pinned context (prec 28,
ROUND_HALF_EVEN, modulo ROUND_DOWN via makeKDecimal() — never global Decimal.set()).

- NEW framework-free packages/core/src/decimal/probe-gates.ts: the SINGLE source of
  the syntactic compile-gates (assertPortableDecimalPow / assertNonZeroDecimalDivisor /
  decimalOfLiteralValue + fail-close messages), generic over a DecimalProbeAccessor.
  codegen/decimal-contract.ts re-exports thin adapters (ts-morph accessor) so emitter
  output stays BYTE-IDENTICAL; the runner imports the same shared impls with a ValueIR
  accessor. One gate source, two typed accessors — drift-safe. Keeps the browser-spine
  pin at exactly 5 typescript importers (probe-gates imports only the framework-free
  kernel contract).
- portable-scalar.ts: RUNNER_DECIMAL_VALUE_METHODS extended to
  {of,add,mul,sub,neg,abs,div,mod,pow}; evalDecimalNode runs 3 phases — (A) syntactic
  gates on raw arg nodes BEFORE eval, (B) L-to-R operand eval with arity dispatch,
  (C) kernel guards kDecimalDiv/Mod/PowInt (never decimal.js .div/.pow). The pow gate
  REFUSES a variable/computed/non-integer-literal exponent and a negative base; a bound
  variable exponent therefore fails closed instead of being silently computed.
- expression-v1.ts: re-admit classifier broadened from the literal-scale fail-close
  only to ALL kernel decimal fail-close families (exact-prefix match), so referenceRun
  surfaces the byte-identical message for div/mod-by-zero and pow refusals while every
  non-fail-close throw still abstains.

Controller corrections on top of the build:
- Fixed an oracle bug: (1/3)*3 under prec-28 is 0.999…9 (28 nines), NOT 1 — corrected
  the frozen fixture + comment. This kills both a host-float impl AND any "saturated
  unity" normalization that would diverge the runner from the two emitted legs.
- Evolved a now-stale slice-1 fixture: Decimal.div is IN-slice as of slice-3, so the
  "out-of-slice method" rejection example moved to Decimal.sqrt.

Gate green: 3-leg producer oracle (26 computed-exact value cases + re-admit/abstain
regimes), all 3 emission byte-identical locks, expression-v1 contract, browser-spine
pin=5.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
…nt parity

Coverage hardening from the full-roster impl-review (no soundness bug found; these
close two gaps the green gate didn't exercise):

- -0 RESIDUE (3-leg value cases): mul/abs can yield a sign-bearing zero (decimal.js
  .s=-1; Python str(Decimal("0")*Decimal("-1")) == "-0"). All three render paths clamp
  to unsigned "0" — decimal.js native .toString, the runner's kernDecimalStr isZero-clamp,
  and Python's _kern_decimal_str is_zero-clamp. mul(0,-1) is the discriminator: Python's
  RAW str() leaks "-0", so the fixture proves the Python clamp is load-bearing (a
  regression dropping it would diverge the Py leg while TS+runner stay "0").

- TRANSPARENT-WRAPPER exponent (runner pow-gate): a wrapped exponent `Decimal.of("3") as
  Decimal` is not a provable integer literal — verified the emitters refuse it
  (POW_NON_INTEGER), and the runner's gate, reading the SAME raw arg node, refuses
  identically. Guards the recurring wrapper operand-bypass class.

Gate still green: slice-3 oracle 55 cases + emission byte-identical locks + expression-v1
+ browser-spine pin=5.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
@cukas cukas merged commit 124a520 into main Jun 16, 2026
4 checks passed
@cukas cukas deleted the feat/runner-native-decimal-slice3 branch June 16, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants