Dev.to AI 🤖 Ai 👁 0 📖 10 min read

One Fail Signature, Two Runners: The Flake Freeze Citation Rule

A flake freeze may be cited only when a local gate holds two witnesses that share a fixture pin, a property digest, and a bound, carry opposite verdicts, and show one stable fail signature. A remote replay is allowed to

A flake freeze may be cited only when a local gate holds two witnesses that share a fixture pin, a property digest, and a bound, carry opposite verdicts, and show one stable fail signature. A remote replay is allowed to append witnesses. It is not allowed to mint the freeze.

A boolean job status cannot support that rule. The bit drops the pin, the digest, the signature, and the runner role.

Two red builds can still be different bugs. Two opposite builds can be the same bug flickering, or a fixture the patch rewrote. The ledger keeps those fields apart, and the decision function admits nothing else.

Fields the gate reads

Store one JSON object per witness. Do not store a prose explanation in the row the gate reads. If a reviewer needs prose, keep it outside the digest inputs.

Field Decision use
property_id Groups rows that claim the same behavior
property_digest SHA-256 of checked-in spec bytes, never of a model draft
fixture_pin SHA-256 of a schema prefix plus fixture bytes
bound Canonical bound text; a changed bound is not admitted here
seed Replay coordinate; recorded, not a freeze key by itself
verdict pass or fail
signature Exception class or assertion id; fails must share one value
runner local or remote
freeze_writer Must be false on every remote row

A citation, when admitted, names one fail id and one pass id. It does not name a CI job. Expiry is a timestamp the caller supplies from policy. This note does not pick a duration, and it does not refresh an old citation.

Outcomes before any freeze file is written

Read the filter from top to bottom. The first matching reject wins. Do not average the rows into a score.

Scoped inputs Result
Any remote row has freeze_writer true Reject the batch
Malformed runner or verdict Reject the batch
Fewer than two rows share id, digest, pin, and bound Reject
Shared keys, but only pass or only fail Reject
Shared keys, fail signatures differ Reject
Shared keys, opposite verdicts, only one runner role Reject
Shared keys, both roles, opposite verdicts, one fail signature Admit and cite ids

The admit row is narrow on purpose. Every other row is a closed patch, a spec problem, or a collector bug. None of those are freezes.

1. Pin the fixture on the minting machine

Compute the pin before a remote job starts. The collector should receive the pin as an expected value, not as a value it invents.

python - <<'PY'
import hashlib
import pathlib
raw = pathlib.Path('tests/fixtures/order.json').read_bytes()
print(hashlib.sha256(b'fixture-v1\n' + raw).hexdigest())
PY

The fixture path above is illustrative. Use the file your property actually loads.

A pin mismatch stops the pipeline. It is not evidence of a flake. If an agent patch edits fixture bytes, the pin changes, and this gate will not cite a freeze. That patch belongs in review.

2. Digest the checked-in property file

The spec that counts is the file a reviewer can open in the repository. Hash those bytes. A model draft is a side file until someone commits it.

python - <<'PY'
import hashlib
import pathlib
spec = pathlib.Path('props/order_total.md').read_bytes()
print(hashlib.sha256(spec).hexdigest())
PY

Compare a draft digest to the spec digest outside the admit function. If they differ, quarantine the draft. Do not pass the draft hash in as expected_digest.

Acceptance is a commit that changes the spec file. It is not a log line from the collector.

3. Append local and remote rows with the same schema

Both runners write the same fields. Only the local caller may treat an admit result as authority.

A remote row that sets freeze_writer to true fails the batch even when every other field matches. Do not drop the bad row and continue. One authority violation is enough to discard that batch.

{"witness_id":"w-local-1","property_id":"order_total_nonneg","property_digest":"<spec-sha256>","fixture_pin":"<pin-sha256>","bound":"total>=0","seed":17,"verdict":"fail","signature":"AssertionError","runner":"local","freeze_writer":false}
{"witness_id":"w-remote-9","property_id":"order_total_nonneg","property_digest":"<spec-sha256>","fixture_pin":"<pin-sha256>","bound":"total>=0","seed":17,"verdict":"pass","signature":"ok","runner":"remote","freeze_writer":false}

Keep the seed equal while you test this citation rule. A different seed can matter for other investigations. It neither blocks nor justifies admission in the function below.

4. Require one fail signature across opposite verdicts

Call the reference function only after the pin and digest checks have passed. Admission then requires four predicates.

  1. No remote witness tried to mint.
  2. At least one local row and one remote row share property id, digest, pin, and bound.
  3. That scope contains both a pass and a fail.
  4. Every fail in scope carries the same signature, and that signature is not ok.

A change from AssertionError to KeyError is a different failure mode. It is not flicker. Close the patch and inspect the fixture or the spec.

The reason string should appear in the CI log. The next reader should not have to reconstruct which predicate failed.

5. Let the local caller write the citation

The function returns identifiers. It does not open a socket, write a freeze store, or extend an older citation.

The local job may write one citation that names fail_id and pass_id, plus an expiry taken from policy. A later green run is not an argument to this function. There is no refill input, because refill would be a different policy reviewed on its own.

Reference function

The module below is a self-contained proposal. It uses the standard library only. It is not a vendor client.

The listing is unexecuted in this draft. The assertions that follow are expected results of the function, not measurements from a shared runner.

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional

@dataclass(frozen=True)
class Witness:
    witness_id: str
    property_id: str
    property_digest: str
    fixture_pin: str
    bound: str
    seed: int
    verdict: str
    signature: str
    runner: str
    freeze_writer: bool

@dataclass(frozen=True)
class FreezeDecision:
    admit: bool
    reason: str
    fail_id: Optional[str] = None
    pass_id: Optional[str] = None

def decide_freeze(witnesses, *, expected_digest, expected_pin, expected_bound, property_id):
    for item in witnesses:
        if item.runner == 'remote' and item.freeze_writer:
            return FreezeDecision(False, f'remote witness {item.witness_id} attempted to mint')
        if item.runner not in {'local', 'remote'} or item.verdict not in {'pass', 'fail'}:
            return FreezeDecision(False, f'malformed witness {item.witness_id}')

    scoped = [
        w for w in witnesses
        if w.property_id == property_id
        and w.property_digest == expected_digest
        and w.fixture_pin == expected_pin
        and w.bound == expected_bound
    ]
    if len(scoped) < 2:
        return FreezeDecision(False, 'need two witnesses on the same pin, digest, and bound')

    fails = [w for w in scoped if w.verdict == 'fail']
    passes = [w for w in scoped if w.verdict == 'pass']
    if not fails or not passes:
        return FreezeDecision(False, 'opposite verdicts required')
    if len({w.signature for w in fails}) != 1:
        return FreezeDecision(False, 'fail signature is not stable')
    if not any(w.runner == 'local' for w in scoped):
        return FreezeDecision(False, 'need both a local witness and a remote witness')
    if not any(w.runner == 'remote' for w in scoped):
        return FreezeDecision(False, 'need both a local witness and a remote witness')
    if fails[0].signature == 'ok':
        return FreezeDecision(False, 'fail witness cannot carry an ok signature')
    return FreezeDecision(
        True,
        'stable signature across opposite verdicts',
        fails[0].witness_id,
        passes[0].witness_id,
    )

Save it as witness_gate.py. Then run the three checks. Python 3.9 or newer is enough. No third-party packages are required.

python - <<'PY'
from witness_gate import Witness, decide_freeze

common = dict(
    property_id='order_total_nonneg',
    property_digest='d' * 64,
    fixture_pin='p' * 64,
    bound='total>=0',
    seed=17,
    freeze_writer=False,
)
ok_rows = [
    Witness('w-local-1', verdict='fail', signature='AssertionError', runner='local', **common),
    Witness('w-remote-9', verdict='pass', signature='ok', runner='remote', **common),
]
decision = decide_freeze(
    ok_rows,
    expected_digest='d' * 64,
    expected_pin='p' * 64,
    expected_bound='total>=0',
    property_id='order_total_nonneg',
)
assert decision.admit and decision.fail_id == 'w-local-1'

bad_fields = dict(common)
bad_fields['freeze_writer'] = True
mint_attempt = [
    ok_rows[0],
    Witness('w-remote-bad', verdict='pass', signature='ok', runner='remote', **bad_fields),
]
denied = decide_freeze(
    mint_attempt,
    expected_digest='d' * 64,
    expected_pin='p' * 64,
    expected_bound='total>=0',
    property_id='order_total_nonneg',
)
assert not denied.admit

split_sig = ok_rows + [
    Witness('w-local-2', verdict='fail', signature='KeyError', runner='local', **common),
]
unstable = decide_freeze(
    split_sig,
    expected_digest='d' * 64,
    expected_pin='p' * 64,
    expected_bound='total>=0',
    property_id='order_total_nonneg',
)
assert not unstable.admit
print('citation rule checks passed')
PY

If an assertion fires, the admit path or one of the two reject paths has drifted. Fix the function before wiring it to a job.

How to act on each reason

Use the reason string as the next step, not as a score.

  1. attempted to mint means the remote job configuration is wrong. Remove write access. Do not cite a freeze from that batch.
  2. malformed witness means the collector schema drifted. Stop the job and fix the writer.
  3. need two witnesses means the pin, digest, or bound did not line up. Recompute both hashes locally.
  4. opposite verdicts required means you have a consistent pass or a consistent fail. A consistent fail is a defect until a passing witness on the same keys appears.
  5. fail signature is not stable means at least two failure modes share the pin. Investigate before any freeze discussion.
  6. need both a local witness and a remote witness means evidence is single-sourced. Do not invent the missing role.
  7. stable signature across opposite verdicts is the only reason that permits a citation, and only the local caller may record it.

Where a free model and a free server fit

MonkeyCode's free model access can draft a candidate property, and its free server option can run the remote replay that appends runner=remote rows. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Those two options cover draft text and remote collection only.

The draft stays quarantined until the spec file changes in git. The remote row must keep freeze_writer false. Neither option is an input to the admit predicate.

If the free model is down, skip drafts and keep the checked-in spec. If the free server is down, do not let the local runner impersonate both roles. Missing remote evidence means no citation.

That default is stricter than a single-runner freeze. It is also the behavior this gate is for. Quotas, hardware, and retention are outside this note, so do not encode guesses about them into the function.

When the pin script and the spec file are already in the tree, the split is small enough to trial: draft offline, collect remotely, cite locally. The decision function stays the same if you later swap the collector.

Limitations

Signature equality is coarse. Two AssertionError rows can hide different messages. If message identity matters, hash a normalized message into signature before the call. This note does not define that normalizer.

The pair rule does not require equal seeds. It will cite a pass and a fail that used different seeds when the other keys match. If the seed is your noise source, add that predicate in a separate review. Do not fold it into this function without a new table.

A valid citation is not a safety proof. The fixture can be too narrow. Inputs outside the pin can still fail the property. Broader generators and input shrinking stay separate work. This gate only decides whether a freeze citation is allowed.

Expiry remains a policy input. A citation with no expiry field should be rejected by the writer even if decide_freeze returned admit. The function shown here does not check the clock.

Who should not use this

Skip the gate when failures have no stable signature, including timeouts that record an empty error string. Skip it when the only property text lives in a prompt. Skip it when local and remote jobs share a writable freeze store and you cannot force freeze_writer false on the remote path.

In that last layout the ledger does not constrain authority. Collecting rows would only add noise.

Also skip it for a fail you reproduced twice on the same pin, with the same signature, and with no passing witness. That case needs a code or spec change. The function already refuses it, because a pass is absent. Leave the refusal in place.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.