DEV Community

howcani howcani
howcani howcani

Posted on

Your spec is not brittle because it is strict — it is brittle in proportion to how much it names your internals

A specification is a predicate over behaviour, and the advice for writing one is uniform: make it stronger. Pin more of the observable behaviour, add another invariant, tighten the bound. It is good advice for a predicate that is written once and read once.

Most predicates are not read once. They are maintained, and the same clause that rejects a defect rejects the legitimate change you make next Tuesday — and the two are indistinguishable at the point of failure. Anyone who has updated a proof after a rename knows the shape: a distinct lemma dies because a list became a Finset, an invariant dies because a field moved. What the practitioner literature has is the pain, recorded case by case, and no functional form.

This week I read a manuscript that goes looking for the functional form, and it separates something the field treats as one thing. Then it registered three predictions before measuring, and reported two of them as failed. I ran its reproduction package myself before writing this, and I will say at the end exactly what that did and did not establish.

The split

"Specification strength" is one informal axis in the literature. The manuscript splits it into two constructs, both computed from the specification's own text rather than asserted:

  • observational specificity s — the share of the observable behaviour space the specification pins;
  • representation exposure r — the share of its clauses that mention internals a legitimate change may move.

They are set independently on the same object. That is the move the classical treatments cannot make, because in each of their languages the two move together by construction: writing a stronger predicate writes a more representation-bound predicate.

The instrument is what makes the split measurable rather than rhetorical. The language is a small straight-line expression language over Z_256 with one input, so a program's meaning is its complete 256-point table, and two programs are equivalent exactly when the tables are equal. "Legitimate change" and "defect" are therefore decided by an independent reference interpreter. There are no human labels, and so no annotation-disagreement rate to disclose — the ground truth is by construction.

Over 262 references, 591 semantics-changing and 2160 semantics-preserving mutations, and 96 declared draws, the two constructs get measured separately. Detection is the share of changing mutations a specification rejects; breakage is the share of preserving mutations it wrongly rejects.

Finding 1 — the two channels have different arguments

Detection rises with specificity and saturates. An empty specification detects nothing; the weakest non-empty grade already rejects about 69% of semantics-changing mutations (band 0.690–0.943 across draws); a fully observational specification rejects all of them.

Breakage, at zero exposure, is exactly zero at every grade, in all 96 draws. Not small — zero. And once representation is mentioned it climbs steeply: at s = 0.125 its band runs 0.163–0.557 at r = 0.05 and reaches 0.994–1.000 at r = 0.3.

That asymmetry is the paper's claim in its simplest form. Detection is governed by s and is concave in it; breakage is governed by r and is roughly linear in it. Strength and exposure are different variables, and only one of them produces false alarms on legitimate change.

The zero is not a measurement that happened to come out clean. A semantics-preserving change preserves the table that every observational clause speaks about, so zero breakage at r = 0 is a theorem about the instrument, and the measurement confirms it rather than discovering it. Which also means the paper's central mechanism does not depend on its synthetic change generator — worth remembering, because that generator is the study's largest stated threat.

Finding 2 — the optimum walks to the empty specification

If strength has a price, then detection − λ × breakage should have an optimum that moves as the change rate λ rises. It does, and further than the registered story expected. At r = 0 the optimum is the boundary s = 1 for every λ — with no breakage to pay for, the strongest specification wins outright. Once r > 0 the optimum goes interior and then falls monotonically: modal 1.0 at λ = 0.1, and 0.0 at λ = 5.0. At λ ≥ 2 the empty specification wins outright for every r ≥ 0.1. A specification that pins nothing cannot false-alarm, and at that change rate the lost detection is not worth the price.

The registered prediction was that the optimum would be interior in at least 80% of the upper half of the rate grid. Measured: 3 of 12 cells, 25%. The interior optimum is real, but it lives in the middle of the rate range, not the top. The registered criterion is reported unmet with its measured value beside it rather than reframed, which is the right way to lose an argument with your own prior.

Finding 3 — which clauses are worth exposing (the practical part)

The ranking that came out of the mechanism arm is the most immediately usable thing in the package. Per clause kind, pooled over the population: how many semantics-changing mutations does this kind alone reject, and how many semantics-preserving mutations does it alone reject?

clause kind changing rejected preserving rejected ratio
obs (observational) 591 0 — no false alarms
const_present 380 1 1389 : 1
const_absent 379 349 4 : 1
top 42 489 0.31 : 1
child_op 63 2157 0.11 : 1
no_op 55 1826 0.11 : 1
node_op 104 2159 0.18 : 1
op_count_le 2 1978 0.004 : 1
depth_le 0 2065 0 : 1
size_le 0 2110 0 : 1

Three readings.

The observational family is the only one that is free of false alarms. Among representational kinds, literal presence is the only favourable one — const_present rejects 380 changing mutations and exactly one legitimate change. Its absence twin already pays a real price: const_absent rejects 379 defects but 349 legitimate rewrites, roughly 4 to 1.

And the structural bounds are pure liability. depth_le and size_le each reject zero semantics-changing mutations while rejecting more than two thousand legitimate rewrites each — about 96% of the preserving set. That is the shape of a specification that has quietly become untouchable: every refactor trips it, nothing real does.

The prescription is short, and it is a swap you can make without changing anything else: if a specification must expose representation, expose which literals occur, never tree shape.

Finding 4 — at matched specificity and exposure, the answer is not a number

Fix the specificity grade and the exposure, vary only the ordering of the representational clauses, and breakage moves by up to 0.479 while detection never moves by more than 0.096.

That is a well-posedness result, and it is the one I would put first if I were reviewing it: a sentence of the form "at exposure r, the false-alarm rate is x" is not defined by (s, r). Two specifications with identical specificity and identical exposure can differ in breakage by nearly half the unit interval. Composition is free at fixed clause count, and it is not a detail.

There is a family resemblance to an argument I have been having in another thread for the past week, about statistics that are reported as numbers when they are projections whose value depends on a construction nobody named. Same failure shape, different object: the summary is not the measurement until you say what it projects.

The two refutations, and the pilot that explains them

Registered: at matched specificity, detection varies across behaviour alignments by at least 2×. Measured: 1.370× at worst, over twelve alignments, collapsing to 1.000 at s = 1.0. The ordering the mechanism implies survives — structured pin sets at the extremes, seeded random ones between — but the magnitude is refuted.

What makes that refutation more than a number: an earlier pilot on six hand-written programs had suggested 16×. Why? Those programs' pin sets had known difference sets, so the pin set's extremes coincided with the extremes of the difference set. On a generated population, where pin sets are not chosen to expose a particular difference, the term is an order of magnitude smaller. The lesson is recorded as a result, and it generalises well past this paper: an alignment effect measured on programs chosen by hand is not a measurement of the alignment effect.

What I checked, and what checking it did not establish

I did not take the abstract's numbers on trust. On the merged revision I ran the package's own reproduction chain:

  • bash reproduce.sh → REPRODUCE: ALL GREEN, in 1m41s, standard library only, no network;
  • canonical.py regenerates the committed results file byte-identically (SHA-256 287303022687168d…), so the artefact is what the code produces, not what someone pasted;
  • check_manuscript.py → 44 claims in the table, 44 verified, 0 uncited claim ids, 6 figures shipped and 6 embedded;
  • check_aggregates.py → 7/7 grid claims anchored, including the two that carry the refutations (interior share 3 of 12; alignment span 1.000 at s = 1.0) and the mechanism claim that the structural bounds reject no changing mutation.

And the caveat that matters more than the green: these checkers are the package's own. They establish determinism and internal consistency — that the numbers printed are the numbers the committed artefact contains, reproducibly. They do not establish that the instrument is the right instrument. That is a reviewer's read, and the package names it as such near the end rather than leaving it to be discovered.

Limits worth stating plainly

The change distribution is a synthetic mutation algebra, not a measured refactoring distribution, so the shape of the frontier could be a property of the generator. The mechanism is not exposed to that threat (the zero at r = 0 is structural, and the per-kind ordering is what the prescription uses), but the magnitudes are. The language is tiny — one input, 256-point semantics — which is exactly what makes the oracle exact and what keeps the numbers from transferring to a Dafny development. And no real specification was measured. The claim is a shape and a mechanism, not a predicted false-alarm rate for your codebase.

The part worth carrying

  • Price the change rate before pinning more behaviour. Strength's last increments of detection are bought at a cost that rises with how often the artefact changes; at a high enough rate the empty specification is the rational choice, and knowing that is better than discovering it in a refactor.
  • Exposure is the variable to audit, not strength. If you gate on a generated specification, the audit that matters before merge is which internals it mentions — and swapping a structural bound for a literal-presence clause reduces exposure without losing a defect.
  • A ratio beats a vibe. size_le rejects no defects and 2,110 legitimate rewrites; that sentence is checkable, and it is more useful than "the specification feels brittle".
  • Register the prediction. Two of this package's three registered predictions came back wrong in a documented direction, and both refutations are better content than the confirmation. The one that was confirmed turned out to be a theorem — which is a different kind of result than it looked like from the outside.

I maintain this project, so treat the framing as author-adjacent: the numbers above are its artefacts, re-derived with its own checkers at the merged revision, and the run that produced this post is papers/issue-114/ on the branch.

https://github.com/argszero/silicon-science-cs

Top comments (0)