Skip to content

Implement the five stubbed functions in TriProbability, replace their placeholder signatures, and replace their five placeholder tests (specs/tri/math/probability.t27) #3587

Description

@gHashTag

Context

specs/tri/math/probability.t27 is the discrete-and-continuous distribution module of the tri
math family (it sits beside constants.t27, bezier.t27, statistics.t27, polynomial.t27,
matrix.t27, measurement.t27, math.t27). It declares no types at all: it opens with
module TriProbability;, two use lines (base::types, math::constants), one section banner
3. Core Functions, and five functions - bernoulli, binomial, poisson, normal,
exponential. All five are stubs. There is no 2. Types section, which is itself informative:
unlike bezier.t27 (which declares Point and BezierCurve) this module is meant to be pure
scalar arithmetic - f64 in, f64 out, no struct, no allocator, no deinit.

This file has two further problems beyond the empty bodies, and fixing both is part of the task.

Problem one: the signatures are generated placeholders and are wrong. Every one of the five
returns -> void, and every one takes exactly one parameter. A probability function that returns
nothing computes nothing, and binomial(n: usize) cannot be a binomial anything - the binomial
pmf needs a success probability and an outcome, and it has neither. This is the one place where
this work order departs from its sibling work orders for polynomial.t27 and linked_list.t27:
there you were told to keep every signature byte-for-byte. Here you must replace them. The
exact replacements are given below, and the comment line above each function must be updated to
match it.

Problem two: the five test blocks are generated placeholders in the dead test form. Each one
reads given input = default_input() / when result = <fn>(input) / then result != undefined.
default_input() is not declared anywhere in this file - the sibling specs/tri/math/bezier.t27
had to add one at line 122 to make the same generated placeholders name something real, and this
file has no such helper. Worse, these blocks are written in the gherkin form
(test <bare_name> followed by given / when / then lines). That form parses, and the parser
even counts it as a test block, but its body is silently discarded: nothing inside the
given / when / then lines is retained, so the block asserts nothing whatsoever. The reported
upstream figure is 7623 such blocks asserting nothing. Measured over specs/** in this checkout:
845 .t27 files carry 12933 gherkin-form test headers against 1623 brace-form ones, and 560 of
the gherkin blocks are the exact given input = default_input() placeholder shape. Five of those
560 are in this file.

So: a green-looking test count here is worth nothing today. Your tests must be written in the
brace form, which is the only form whose body survives:

test "embedding_is_normalized" {
    const formula = Formula {
        .id = "test",
        .name = "test",
        .sector = .qcd,
        .value = 0.118034,
        .complexity = 3,
    };
    const embedding = embed_formula(formula);
    const norm = l2_norm(&embedding.vector);
    assert(abs(norm - 1.0) < 0.001);
    assert(embedding.normalized == true);
}

That is specs/memory/formula_embed.t27 lines 170-182, verbatim - quoted test name, braces,
const locals, assert(...) calls each ending in a semicolon, and a float compared against a
tolerance rather than with ==. Copy that shape.

.t27 is a hand-authored source language, not compiler output. You are writing source.

Exact current state

$ cd "$(git rev-parse --show-toplevel)"
$ wc -l specs/tri/math/probability.t27
66 specs/tri/math/probability.t27
$ grep -c 'TODO: Implement' specs/tri/math/probability.t27
5
$ grep -cE '^    fn ' specs/tri/math/probability.t27
5
$ grep -c 'default_input' specs/tri/math/probability.t27
5
$ grep -c 'void' specs/tri/math/probability.t27
10

Ten, not five: each function contributes a -> void on its fn line and a second void on the
generated comment line directly above it. Both have to go.

$ grep -cE '^[[:space:]]*(given|and|when|then)[[:space:]]' specs/tri/math/probability.t27
15

Five placeholder blocks, three dead lines each.

$ grep -cE '^[[:space:]]*test "' specs/tri/math/probability.t27
0

Not one surviving test body in the whole file.

$ grep -cE '^[[:space:]]*fn (bernoulli|binomial|poisson|normal|exponential)\(.*\) -> f64 \{' specs/tri/math/probability.t27
0

Sixty-six lines, five declared functions, five stubs, five placeholder tests that assert nothing,
and zero functions that return a number.

Two baselines this work order does not state, because the mirror it was written against
contains only specs/** and t27c could not be run: the three t27c parse counters and the
t27c count node-type total. Run both before you touch anything and record what they print - that
is step zero, and criteria 1d, 7 and 8 are measured against what you record.

What to write

1. Replace the five signatures

Each replacement keeps the function's existing name, keeps its existing first parameter with the
same name and the same type and in the same position, appends only the parameters the distribution
mathematically requires, and turns -> void into -> f64. No new types are introduced: f64 and
usize are the only two types the file already uses. Write these exactly, each with its comment
line directly above it:

    // bernoulli(p: f64, k: usize) -> f64
    fn bernoulli(p: f64, k: usize) -> f64 {

    // binomial(n: usize, k: usize, p: f64) -> f64
    fn binomial(n: usize, k: usize, p: f64) -> f64 {

    // poisson(lambda: f64, k: usize) -> f64
    fn poisson(lambda: f64, k: usize) -> f64 {

    // normal(mean: f64, std_dev: f64, x: f64) -> f64
    fn normal(mean: f64, std_dev: f64, x: f64) -> f64 {

    // exponential(lambda: f64, x: f64) -> f64
    fn exponential(lambda: f64, x: f64) -> f64 {

The comment line is not decoration and not optional. The generated comments in the file today
restate the signature (// bernoulli(p: f64) ...), so a comment left unchanged above a changed
fn line is a comment that lies. Update all five. Use the ASCII -> in the comment, as written
above.

Every one of the five returns a probability mass (bernoulli, binomial, poisson) or a
probability density (normal, exponential) evaluated at one point. All five are pure: no
allocation, no I/O, no mutation of anything outside the function.

2. Fill the five bodies

bernoulli(p, k). Domain: p in [0.0, 1.0], k in {0, 1}. Returns p for k == 1 and
1.0 - p for k == 0. Boundary: k >= 2 is outside the support - return 0.0. p < 0.0 or
p > 1.0 is outside the domain - return 0.0 for both values of k, and do not clamp. The
reason is concrete: without that guard bernoulli(1.5, 0) computes 1.0 - 1.5 and hands the
caller a probability of -0.5.

binomial(n, k, p). Domain: k in 0..=n, p in [0.0, 1.0]. Returns
C(n, k) * p^k * (1 - p)^(n - k). Boundaries: k > n returns 0.0; p outside [0.0, 1.0]
returns 0.0; n == 0 is the empty-input case and is not an error - zero trials produce zero
successes with certainty, so binomial(0, 0, p) is 1.0 (the empty product) and
binomial(0, k, p) for any k >= 1 is 0.0. Compute C(n, k) multiplicatively -
c = 1; for i in 1..=k: c = c * (n - k + i) / i - using min(k, n - k) as the loop bound. Do not
compute it as n! / (k! * (n - k)!): 21! already overflows a 64-bit unsigned integer, and this
function is expected to work for n far above 20.

poisson(lambda, k). Domain: lambda >= 0.0, k any usize. Returns
exp(-lambda) * lambda^k / k!. Boundaries: lambda < 0.0 returns 0.0; lambda == 0.0 is the
degenerate point mass at zero, so poisson(0.0, 0) is 1.0 and poisson(0.0, k) for k >= 1 is
0.0 - and note that this case must be handled before you evaluate lambda^k, so that
0.0^0 never has to be decided by the floating point unit. Accumulate lambda^k / k! as the
running product for i in 1..=k: term = term * lambda / i, never as a power divided by a
factorial: for lambda = 20, k = 25 the numerator and the denominator both overflow while the
quotient is an ordinary number near 0.05.

normal(mean, std_dev, x). Domain: std_dev > 0.0; mean and x range over the whole real
line. Returns (1.0 / (std_dev * sqrt(2 * pi))) * exp(-0.5 * z * z) where z = (x - mean) / std_dev.
Boundary - zero variance: std_dev <= 0.0 returns 0.0, and this must be a guard placed
before any division. A zero-variance normal is a Dirac delta with no finite density; naive code
divides by zero and returns inf at x == mean and NaN everywhere else, because the exponent
becomes 0.0 / 0.0. Neither is an acceptable return value from this function.

exponential(lambda, x). Domain: lambda > 0.0, x >= 0.0. Returns lambda * exp(-lambda * x).
Boundaries: x < 0.0 is below the support - return 0.0; lambda <= 0.0 is outside the domain -
return 0.0. Note that the density at the left edge of the support is lambda itself, not 1.0
and not 0.0, and that it exceeds 1.0 whenever lambda > 1.0: a density is not a
probability and is not capped at one. This is the distribution the module header comment names, so
it is the one to get exactly right.

The one rule that covers all five: any argument outside the stated domain, and any outcome
outside the stated support, returns exactly 0.0. Never a negative number, never inf, never
NaN. The only two returns of 1.0 from a boundary are the two genuine degenerate point masses
named above: binomial(0, 0, p) and poisson(0.0, 0).

Use the builtins @exp and @sqrt for the transcendentals. Both are established in this corpus -
@exp at specs/ml/activation/gelu_activation.t27 line 71, @sqrt at specs/numeric/gf16.t27
line 448 and specs/vsa/vsa_core.t27 lines 234-235. Do not hand-roll a Taylor series unless
you also do range reduction and state the resulting error bound in a comment: the 12-term series in
specs/math/radix_economy.t27 lines 139-151 is the house example of the naive version, and its
next omitted term at x = -2 is 2^13 / 13! = 1.32e-6, which is already coarser than the
tolerance this task asks for.

Body style: specs/tri/math/bezier.t27 lines 28-69 is the house form of a filled -> type { }
body in this exact directory. Constructs measured to parse clean there: if (cond) { },
let and let mut locals, for (0..n) |i| { }, return expr;, field access, indexing, struct
literals, &x, and @as / @floatFromInt / @constCast.

3. Replace the five placeholder tests

Delete all five of these blocks. They are generated stubs, default_input() does not exist in this
file, and their bodies are discarded by the parser anyway:

    test bernoulli_basic_case
        given input = default_input()
        when result = bernoulli(input)
        then result != undefined

(and the identical binomial_basic_case, poisson_basic_case, normal_basic_case,
exponential_basic_case.)

Do not rescue them by adding a default_input() helper the way bezier.t27 did. A helper that
feeds a dead block still produces a test that asserts nothing.

Write brace-form tests in their place. Every float comparison carries an explicit epsilon and uses
@abs, which is the form the two finished siblings in this directory use (@abs appears 7 times
in bezier.t27 and 4 times in constants.t27; the bare abs( spelling also exists in the corpus,
at specs/memory/formula_embed.t27 line 180 and specs/rings/ring_103_phi_sgd.t27 line 52 - match
your directory and use @abs). Never compare a float with ==, not even where the value is exact
in binary.

Two tolerances, and each test must state which it is using by writing the literal:

  • 1e-12 for every value that is exact in binary floating point - all the Bernoulli and
    binomial values, every boundary 0.0, and exponential(2.0, 0.0). No transcendental is involved,
    so there is nothing to give away.
  • 1e-9 for every value that passes through @exp or @sqrt. The expected values below are
    given to 15 significant digits, which is why the tolerance is 1e-9 and not 1e-15.

These are the expected values. Each one must appear in a test, with the arithmetic that produces
it reproduced in a comment above the assert so a reviewer can re-derive it.

Constants used below, to 15 significant digits:
e^-1 = 0.367879441171442, e^-2 = 0.135335283236613, e^-0.5 = 0.606530659712633,
sqrt(2*pi) = 2.506628274631000, 1 / sqrt(2*pi) = 0.398942280401433.

call expected how it is derived eps
bernoulli(0.25, 1) 0.25 p 1e-12
bernoulli(0.25, 0) 0.75 1 - 0.25 1e-12
bernoulli(0.25, 0) + bernoulli(0.25, 1) 1.0 0.75 + 0.25, the pmf sums to one 1e-12
bernoulli(0.25, 2) 0.0 k outside the support {0, 1} 1e-12
bernoulli(1.5, 1) 0.0 p outside [0, 1]; the guard that stops -0.5 1e-12
binomial(5, 2, 0.5) 0.3125 C(5,2) * 0.5^2 * 0.5^3 = 10 * 0.25 * 0.125 = 10/32 1e-12
binomial(0, 0, 0.5) 1.0 C(0,0) = 1, empty product; zero trials, zero successes 1e-12
binomial(0, 1, 0.5) 0.0 one success is impossible in zero trials 1e-12
binomial(5, 6, 0.5) 0.0 k > n 1e-12
binomial(5, 0..5, 0.5) summed 1.0 (1+5+10+10+5+1)/32 = 32/32 1e-12
poisson(2.0, 3) 0.180447044315484 e^-2 * 2^3/3! = 0.135335283236613 * 4/3; 0.135335283236613 * 4 = 0.541341132946452, / 3 1e-9
poisson(1.0, 0) 0.367879441171442 e^-1 * 1^0/0! = e^-1 * 1 1e-9
poisson(0.0, 0) 1.0 degenerate point mass at zero 1e-12
poisson(0.0, 3) 0.0 same degenerate mass, k >= 1 1e-12
poisson(-1.0, 2) 0.0 lambda outside the domain 1e-12
normal(0.0, 1.0, 0.0) 0.398942280401433 1 / (1 * sqrt(2*pi)); 2*pi = 6.28318530717959, sqrt = 2.506628274631000, reciprocal 1e-9
normal(0.0, 1.0, 1.0) 0.241970724519143 0.398942280401433 * e^-0.5 = 0.398942280401433 * 0.606530659712633 1e-9
normal(2.0, 1.0, 2.0) 0.398942280401433 shift invariance: z = (2-2)/1 = 0, same as the peak above 1e-9
normal(0.0, 2.0, 0.0) 0.199471140200716 1 / (2 * sqrt(2*pi)) = 0.398942280401433 / 2; doubling the spread halves the peak 1e-9
normal(0.0, 0.0, 0.0) 0.0 zero variance; the guard that stops inf and NaN 1e-12
exponential(2.0, 0.0) 2.0 lambda * e^0 = 2 * 1; the density at zero is lambda, and it is above one 1e-12
exponential(1.0, 1.0) 0.367879441171442 1 * e^-1 1e-9
exponential(2.0, 0.5) 0.735758882342885 2 * e^(-2*0.5) = 2 * e^-1 = 2 * 0.367879441171442 1e-9
exponential(2.0, -1.0) 0.0 x below the support [0, inf) 1e-12
exponential(0.0, 1.0) 0.0 lambda outside the domain 1e-12

Twenty-four required values. Group them into at least twelve brace-form test blocks - at least two
per function, one for the interior of the domain and one for the boundary, plus the two
sums-to-one invariants. A block looks like this:

test "exponential_density_at_zero_equals_lambda" {
    // lambda * exp(0) = 2.0 * 1.0 = 2.0; a density is not capped at 1.0
    const d = exponential(2.0, 0.0);
    assert(@abs(d - 2.0) < 1e-12);
}

test "poisson_pmf_matches_hand_computed_value" {
    // exp(-2) * 2^3 / 3! = 0.135335283236613 * 8 / 6 = 0.180447044315484
    const p = poisson(2.0, 3);
    assert(@abs(p - 0.180447044315484) < 1e-9);
}

Do not rewrite the module TriProbability; line, the two use lines, the SPDX header, the
module header comment, or the 3. Core Functions / TDD: Tests banners. The module header comment
carries the family identity line; leave it exactly as it is and do not reproduce it in new code.
The file declares no constant for it and you must not invent a cross-module call to fetch one -
there is no resolver check in this pipeline, so a call to a symbol that does not exist here will
parse happily and mean nothing.

Acceptance criteria

Every criterion below is a command with an expected output, and every one of them
fails on the file as it stands today.
The now column was produced by running the
command against this file, except for the two t27c rows, which are marked record it
because the mirror this work order was written against carries only specs/**. Re-run
every one of them before you start; the measured ones will give you the same numbers,
and that is the point - a criterion that already passes before any work is done proves
nothing.

    1. all 5 named functions still exist, all 5 return f64, every function body is non-empty, and t27c parse reports recovery-events 0, declarations-swallowed 0, lexer-discarded-chars 0
    1. grep -c 'TODO: Implement' specs/tri/math/probability.t27 prints 0
    1. grep -c 'void' specs/tri/math/probability.t27 prints 0
    1. grep -c 'default_input' specs/tri/math/probability.t27 prints 0 and the gherkin keyword-line count is 0
    1. grep -cE '^[[:space:]]*test "' specs/tri/math/probability.t27 prints at least 12 and grep -cE 'assert\(' specs/tri/math/probability.t27 prints at least 24
    1. grep -cE '@abs\(.*\) < 1e-' specs/tri/math/probability.t27 prints at least 24
    1. the real-code node count is at least 10
    1. t27c parse specs/tri/math/probability.t27 | grep -c 'kind: TestBlock' prints at least 12 and t27c count specs/tri/math/probability.t27 | head -1 reports at least 11 node types
# check now (before you start) required when you are done
1 names + -> f64 + non-empty bodies + parse names=5, f64=0, fns=5 nonempty=0 names=5, f64=5, fns=n nonempty=n (n>=5), clean
2 stub marker 5 0
3 void returns and void comments 10 0
4 placeholder tests default_input=5, gherkin=15 0, 0
5 brace-form tests / asserts 0 / 0 >= 12 / >= 24
6 epsilon-bearing float comparisons 0 >= 24
7 real-code nodes record it >= 10
8 test blocks / node types record it >= 12 / >= 11

The exact commands (run from the repository root)

$ cd "$(git rev-parse --show-toplevel)"

Criterion 1 is four commands that must all hold at once. It is one criterion because
each part is worthless alone: the name grep passes today, and a file with every function
deleted would pass the body scan.

1a - the 5 names survive. Deleting or renaming a function fails here.

$ grep -cE '^[[:space:]]*(pub[[:space:]]+)?fn (bernoulli|binomial|poisson|normal|exponential)\(' specs/tri/math/probability.t27

Must print 5. It prints 5 today - this part is a guard against regression, not a task.

1b - all 5 signatures were corrected. This is the part that is a task.

$ grep -cE '^[[:space:]]*fn (bernoulli|binomial|poisson|normal|exponential)\(.*\) -> f64 \{' specs/tri/math/probability.t27

Today it prints 0. It must print 5.

1c - every body is real. This counts declared functions and how many have a body that is
more than comments. Deleting a TODO comment and leaving the braces empty does not move it.

$ python3 - specs/tri/math/probability.t27 <<'EOF'
import re,sys
s=open(sys.argv[1],encoding='utf-8').read(); n=e=0
for m in re.finditer(r'^[ \t]*(?:pub[ \t]+)?fn[ \t]+\w+',s,re.M):
    i=s.find('{',m.end())
    if i<0: continue
    d=0
    for j in range(i,len(s)):
        if s[j]=='{': d+=1
        elif s[j]=='}':
            d-=1
            if d==0: break
    n+=1
    if re.sub(r'//[^\n]*','',s[i+1:j]).strip(): e+=1
print('fns=%d nonempty=%d'%(n,e))
EOF

Today it prints fns=5 nonempty=0. The two numbers must be equal, and the first must be
at least 5. A private helper function you add is welcome - it raises both numbers together
and 1a still pins the 5 public names. Note that a helper will also have to satisfy 1b's sibling
rule of honesty: give it a real return type, not void, or criterion 3 will catch it.

1d - the file still parses clean.

$ ./target/release/t27c parse specs/tri/math/probability.t27 2>&1 | grep -E '^(recovery-events|declarations-swallowed|lexer-discarded-chars):'

All three must read 0. Use this grep -E form and never | tail -3: on a file with
recovery events the parser prints discarded: diagnostic lines after the three metrics,
so tail -3 shows diagnostics and makes a broken file look clean.

Criterion 2 - the stub marker is gone.

$ grep -c 'TODO: Implement' specs/tri/math/probability.t27

Today it prints 5. It must print 0.

Criterion 3 - no function returns nothing, and no comment says it does.

$ grep -c 'void' specs/tri/math/probability.t27

Today it prints 10: five -> void return types and five generated comment lines that
restate them. It must print 0. This single grep is the check that the signatures really
changed and that the comments changed with them - a corrected fn line under a stale comment
leaves this at 5, not 0.

Criterion 4 - the five placeholder tests are gone.

$ grep -c 'default_input' specs/tri/math/probability.t27
$ grep -cE '^[[:space:]]*(given|and|when|then)[[:space:]]' specs/tri/math/probability.t27

Today they print 5 and 15. Both must print 0. The first kills the calls to a function
this file never declares; the second kills the gherkin form itself, which is the form whose
body the parser throws away. Both halves are needed: a gherkin block that no longer mentions
default_input is still a block that asserts nothing.

Criterion 5 - real test blocks, with real asserts in them.

$ grep -cE '^[[:space:]]*test "' specs/tri/math/probability.t27
$ grep -cE 'assert\(' specs/tri/math/probability.t27

Today they print 0 and 0. They must print at least 12 and at least 24. The 12 is two
blocks per function (interior and boundary) plus the two sums-to-one invariants; the 24 is
one assert per row of the expected-value table above.

Criterion 6 - every float comparison names its tolerance.

$ grep -cE '@abs\(.*\) < 1e-' specs/tri/math/probability.t27

Today it prints 0. It must print at least 24 - one per row of the table. This is the
criterion that stops assert(poisson(2.0, 3) == 0.180447044315484);, which is the single most
likely way to make this file look finished while pinning a value that no correct implementation
can ever hit exactly.

Criterion 7 - real code, not prose. Comment-only bodies produce no statement or
expression nodes at all. This counts the node kinds only a real body can produce:

$ ./target/release/t27c parse specs/tri/math/probability.t27 2>&1 | grep -cE 'kind: (StmtLocal|ExprReturn|ExprCall|ExprBinary|ExprIndex|ExprFieldAccess)'

Record what it prints before you start; every body in the file is comment-only today, so
expect a very small number. It must end at at least 10. The bar is two nodes per stubbed
function (5 x 2 = 10). For scale, and quoting the measurement made for the sibling work
orders on polynomial.t27 and linked_list.t27: 88.0% of the stub-free, parse-clean files
in specs/ carry at least 2 such nodes per function and the median is 8.8. This is a floor,
not a target - a correct binomial alone will clear it.

Criterion 8 - the tests are counted by the parser, and the file became structurally richer.

$ ./target/release/t27c parse specs/tri/math/probability.t27 2>&1 | grep -c 'kind: TestBlock'
$ ./target/release/t27c count specs/tri/math/probability.t27 | head -1

Record both before you start. The source holds 5 test blocks today (counted above), so the
first is unlikely to surprise you. When you are done the first must print at least 12 and the
second must report at least 11 node types. Counting through t27c parse rather than
grep 'test ' over the source is deliberate: only blocks the parser actually accepted are
counted, so a malformed test block scores nothing. Note the trap this file walks into: the
parser counts a gherkin block too, so criterion 8 alone cannot tell a real test from a dead one.
That is exactly why criteria 4, 5 and 6 exist beside it.

| head -1 is not optional. The rest of the t27c count table is not reproducible:
rows with equal counts print in a different order on every run (measured: 3 distinct
orderings in 8 consecutive runs over the same unchanged file), and the table stops after
15 rows with ... and N more. 14 files in specs/ contain test blocks that
t27c count | grep TestBlock cannot see for exactly that reason - and they are the
richly-implemented ones, which is the state this task is trying to reach. Never read a
number off that table; the first line is the only stable part of the output.

What is not evidence

  • t27c typecheck is not a gate. It returns {"ok":true,"errors":0} on 500 words of
    English prose and on a Python file. Do not cite it for anything.
  • There is no t27 interpreter. Your test blocks will be parsed and counted, never
    executed. Write them - a test block beside a function is how this corpus states what a
    function must do - but never claim you ran one, that one passed, or that you verified a
    numeric value by running the code. The expected values in the table above were computed by
    hand and the arithmetic is shown; if you disagree with one of them, say so in your report
    and show your own arithmetic. Do not silently substitute a different number.
  • A rising kind: TestBlock count is not evidence that the tests assert anything. Five
    blocks in this file already assert nothing while being counted. Criteria 4, 5 and 6 are the
    ones that carry the weight here.
  • ASCII only. Do not introduce Cyrillic anywhere in the file: it fails cargo build in
    bootstrap/ through build.rs. English only in comments and test names.

Your VERDICT

The first thing in your final message must be the block below, with each criterion in the
issue's own words on one line, ending in exactly met, unmet or could-not-check:

## VERDICT
- 1. all 5 named functions still exist, all 5 return f64, every function body is non-empty, and t27c parse reports recovery-events 0, declarations-swallowed 0, lexer-discarded-chars 0: met
- 2. grep -c 'TODO: Implement' specs/tri/math/probability.t27 prints 0: met
- 3. grep -c 'void' specs/tri/math/probability.t27 prints 0: met
- 4. grep -c 'default_input' specs/tri/math/probability.t27 prints 0 and the gherkin keyword-line count is 0: met
- 5. grep -cE '^[[:space:]]*test "' prints at least 12 and grep -cE 'assert\(' prints at least 24: met
- 6. grep -cE '@abs\(.*\) < 1e-' prints at least 24: met
- 7. the real-code node count is at least 10: met
- 8. t27c parse | grep -c 'kind: TestBlock' prints at least 12 and t27c count | head -1 reports at least 11 node types: met

Scope

That file and nothing else. Every other issue in this set owns a different file, and two
agents editing one file is the exact failure the worktree isolation exists to prevent.
specs/tri/math/statistics.t27 is the nearest temptation - it has the identical disease
(6 stubs, 6 -> void signatures, 6 default_input placeholders) and it belongs to a
different issue. Leave it alone. Do not edit any other .t27 file, do not touch compiler/
or bootstrap/, do not add new files, and do not create any .sh or .bash file.

Boundary

specs/tri/math/probability.t27

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions