Observation / Required / Baseline observed / Corrected observed
Observation Required Baseline observed Corrected observed
Response {"status":"rejected","reason":"unknown_destination"} Matches Matches
Alice after reconnect 10000 8000 10000
Bob after reconnect 2500 2500 2500
All transfer rows after reconnect [] [] []
Transaction open at return No No No

An empty transfer table and a closed transaction did not make the rejection safe. These are retained local observations from a fictional fixture, not a production or customer financial incident. Inspect the baseline capture and corrected capture.

01 / Define the rejection

What should a negative test establish?

A negative case expects rejection or specified failure handling; its test passes when those requirements hold. A failing test is an execution result, not an input classification.

Define the rejection at the layer responsible for it. A parser may reject malformed data before a service sees it; a service may reject a well-formed request because the resource is absent. Calling an internal helper directly can examine that helper, but does not establish that an external caller can reach the same path.

Situation / Decision the test needs
Situation Decision the test needs
Invalid input Which syntax, type, range or domain rule is violated, and which boundary rejects it?
Absent resource Is absence an error, a permitted empty result, or another specified outcome? A valid identifier can name no existing object.
Expected business rejection Which valid request is disallowed in this state, such as an amount exceeding the available balance?
Unexpected fault Did the operation encounter a dependency or implementation failure? Assert the specified fault response and preserved effects; do not relabel every exception as successful validation.
Valid retry Does the contract require the recorded success without another effect? Repetition alone does not make a request negative.
Authorization denial Which actor may perform which action on which object, and can that actor supply the tested value through the supported interface? Well-formed input is not evidence of permission.

A transaction can provide atomicity for its included database changes. That property does not specify the right response, account ownership or authorization policy. SQLite's atomic-commit documentation concerns a database transaction and its storage assumptions; the selected rejection tests here do not establish universal atomicity. SQLite atomic commit.

Write allowed effects as carefully as forbidden ones. A contract may require a denial audit record while prohibiting a business update. If messages or notifications are involved, include their separate observation boundary: a database rollback cannot establish what another system received. AWS transactional outbox guidance.

02 / Start with the record

Free rejection and effects worksheet

Copy the complete record into a document, issue or repository. It works without Proof, signup or JavaScript. Repeat case and observation fields as needed; mark unknown or unperformed work explicitly. Copying the record does not execute a test.

REJECTION AND EFFECTS RECORD
Record ID / revision / preparer:
Evidence status: NOT RUN
Acceptance status: NOT ASSESSED

A. REQUIREMENT AND AUTHORITY
Requirement ID / revision / authoritative source:
Exact rejection or failure-handling rule:
Requirement owner / person authorized to amend it:
Rejection-owning component or layer:
Bounded claim and decision this record must support:
Executor / reviewer / acceptance authority:

B. ENTRY POINT, ACTOR AND DOMAIN
Supported entry point / adapter / actual implementation call path:
Evidence that this request reaches the intended rejection owner:
Actor identity, permissions and relevant context, or explicit exclusion:
Values the caller controls; values controlled only by the test:
Input class: invalid / absent resource / business rejection / fault
  / authorization denial / valid retry or other successful control
Valid domain, types, units and relevant boundaries:
Exact request or ordered sequence; input artifact identity:
Initial durable state and preconditions; reset/isolation arrangement:
Dependencies, access and controlled external-effect destinations:
Test-only fault seam, injection point and evidence it was reached:
Public reachability claimed? Basis, or explicitly not claimed:

C. INDEPENDENT ORACLE
Exact required response or exception and error details:
Allowed durable effects and independent requirement basis for each:
Forbidden durable effects and independent requirement basis for each:
Required before-state and exact after-state, including identities/rows:
Observer/query/assertion code and revision; why independent of implementation:
Observation milestone, completion/deadline rule and timeout classification:
Transaction/resource state required at return:
External intent, dispatch and delivery observations, if applicable:
Unresolved expected behavior and owner:

D. IMPLEMENTATION AND REPRODUCTION
Baseline / corrected candidate identities, revisions or source hashes:
Harness, cases, schema and oracle identities:
Runtime / dependency versions / platform where relevant:
Connection mode, transaction policy and verified settings:
Prerequisites and environment differences:
Working directory / exact command / case selection:
Output destination; preservation of previous evidence:

E. CONTROLS AND SENSITIVITY
Successful control, expected response and exact effects:
Boundary success control, if relevant:
Known-bad implementation or forbidden-effect challenge:
Expected failure of the strengthened assertion:
Reject-all or other trivial-pass challenge and expected result:
Which controls were actually executed; retained evidence:

F. OBSERVATIONS (repeat per case and gate)
Run identity / source basis / captured runtime:
Case ID / actual request / actual starting state:
Observed response / immediate state / independently observed durable state:
Transaction state at return / evidence of the fault boundary:
Expected versus actual differences and assertion result:
Distinct scenarios / invocations / passed / failed / skipped or blocked:
Raw output, errors, trace and summary locations; hashes where retained:
Failure disposition / implemented correction / replay evidence:

G. DECISION AND RENEWAL
Supported claim and precise acceptance scope:
Failed, missing, unperformed or stale work and its effect on the decision:
Acceptance decision / authority / evidence basis / conditions:
Next action / accountable person or queue / completion or review trigger:
Reassess when requirement, input domain, source, schema, observer, runtime,
transaction policy, dependency, actor context or effect boundary changes:
Initial decision: no acceptance follows from this blank record.

The filled record uses this structure. Resolve expected outcomes before adapting its assertions: generating inputs does not determine correctness. Barr et al., oracle survey.

03 / Inspect a failure and its correction

A rejected transfer that still commits a debit

The authored contract is negative-transfer-rejection-v1. Each invocation starts in S0: [["alice",10000],["bob",2500]], no transfer rows and no active transaction. There are no concurrent callers, prior transfer IDs or triggers.

The runner imports and calls transfer_baseline, transfer_corrected or transfer_reject_all from the same transfer.py. The call uses the case's request fields and separately supplies the internal fault flag. It executes SQLite statements on a fresh on-disk database; no network API or Proof runtime participates.

Declare the cases before inspecting the implementation

These are the seven literal records in cases.json, not a Cartesian product of their values. Every request uses source alice. Every required rejection has status: rejected; both successes require {"status":"accepted","transfer_id":1}.

Case / Destination / amount in cents / Required reason or result / Required persisted accounts / transfer rows
Case Destination / amount in cents Required reason or result Required persisted accounts / transfer rows
missing-destination ghost / 2000 unknown_destination S0 / []
insufficient-balance bob / 10001 insufficient_balance S0 / []
zero-amount bob / 0 invalid_amount S0 / []
negative-amount bob / -1 invalid_amount S0 / []
record-failure-after-debit bob / 2000; internal test fault processing_failed S0 / []
successful-transfer bob / 2000 Accepted, transfer ID 1 Alice 8000, Bob 4500 / [[1,"alice","bob",2000]]
successful-full-balance-transfer bob / 10000 Accepted, transfer ID 1 Alice 0, Bob 12500 / [[1,"alice","bob",10000]]

The implementation's amount check admits exact Python int values, excluding bool, from 1 through 1000000. The executed amounts are only -1, 0, 2000, 10000 and 10001. Unknown sources, other types and the upper limit were not exercised. The supported fixture entry point is the local function; there is no authentication or authorization mechanism in this experiment.

Why the baseline fails

The connection pins LEGACY_TRANSACTION_CONTROL where available and uses isolation_level=None. In that mode, Python does not implicitly open transactions. The baseline issues no BEGIN, so its debit commits before it checks destination existence. Returning unknown_destination cannot undo that completed write. Python transaction control and baseline source.

The second failing case has a valid request but enables an internal test-only seam. Immediately after the real debit, it attempts a zero-amount transfer insert. The real CHECK (typeof(amount_cents) = 'integer' AND amount_cents > 0) raises sqlite3.IntegrityError. SQLite's statement-level ABORT backs out the failing statement; it does not reverse the earlier committed debit. The seam is not a caller-controlled request field or evidence of attacker reachability. CHECK constraints · ABORT semantics.

Both baseline failures return their exact expected response and persist Alice 8000, Bob 2500, with no transfer row. The expected rejected state is Alice 10000, Bob 2500, with no transfer row. Closing and reopening the database exposes the difference. Read both failed observations.

Inspect the implementation or reproduce the retained contrast with the evidence kit.

The implemented correction and replay

transfer_corrected explicitly issues BEGIN IMMEDIATE, validates destination existence before debit, and keeps the debit, credit and transfer record in that transaction. It commits successful writes and explicitly rolls back rejections reached inside the transaction and caught SQLite errors. Invalid amounts return before a transaction opens.

The corrected fault trace records the mechanism, not just the final response:

Event in record-failure-after-debit / Alice / Bob, cents / Transaction active
Event in record-failure-after-debit Alice / Bob, cents Transaction active
debit_executed 8000 / 2500 true
database_error_before_rollback, IntegrityError 8000 / 2500 true
database_error_after_rollback 10000 / 2500 false
Independent read after reconnect 10000 / 2500; no transfer row Operation returned with no open transaction

BEGIN IMMEDIATE occurs outside the protected write body. Its acquisition errors can propagate, and lock contention was not exercised or accepted by this uncontended fixture. SQLite documents that this acquisition can fail with SQLITE_BUSY. SQLite transactions.

The strengthened gate compares exact response data and the test-owned observe_durable_state SQL reads with literal expectations from cases.json. It reads account identifiers and balances and all transfer rows through a new connection, and checks that the operation left no transaction open. It does not use the implementation's arithmetic or diagnostic snapshots as its oracle. A conserved balance sum alone cannot establish which account owns the value or whether the transfer record is correct.

Recorded gate / Passed / invoked / Failed / Child exit
Recorded gate Passed / invoked Failed Child exit
Baseline, exact response only 7/7 0 0
Baseline, response plus effects 5/7 2 1
Corrected, response plus effects 7/7 0 0
Reject-all stub, successful controls only 0/2 2 1

The response gate validates setup and response while only recording post-operation state. The reject-all stub fails both successful controls, showing that refusing every request cannot satisfy this contract. These controls establish sensitivity to the two observed state defects and blanket rejection. Four-gate summary · Reject-all observations.

Reproduce the contrast

Extract the ZIP into a new directory. Use Python 3.10+ with its standard-library sqlite3 module and a writable temporary directory. No package installation, credentials, network service or Proof signup is needed. From the extracted kit root:

python3 reproduce.py

The default rerun-output/ must be absent or empty. To retain an earlier run, use the documented alternative:

python3 reproduce.py --output-dir another-run

Wrapper exit 0 means the expected passing and failing children reproduced; it does not accept the baseline. Keep new observations separate from observed/; the README describes their files. Python 3.10+ is a prerequisite, not a tested version matrix.

Filled rejection and effects record

This record summarizes the actual frozen source and primary observations. Technical evidence is filled; real-project authority and adoption work remain unassigned.

REJECTION AND EFFECTS RECORD
Record basis: negative-testing-sqlite-rejection-v1
Evidence: observed fictional teaching fixture, retained capture
Project acceptance: not assessed; no customer or production decision

A. REQUIREMENT AND AUTHORITY
Requirement: negative-transfer-rejection-v1, contract.json
Basis: authored teaching rule, with literal cases.json expectations.
Rule: the five declared rejections return their exact response, preserve
both account rows and all transfer rows, and leave no transaction open.
The two successes return accepted/transfer_id 1 and exact required effects.
Rejection owner: selected local transfer function in transfer.py.
Requirement/acceptance authority: no actual project owner assigned.
Executor: retained reproduce.py -> run_cases.py campaign.
Real-project reviewer: unassigned; no approval is inferred.

B. ENTRY POINT, ACTOR AND DOMAIN
Call path: reproduce.py starts run_cases.py; IMPLEMENTATIONS selects the
imported transfer_baseline, transfer_corrected or transfer_reject_all.
run_case calls implementation(db, **case["request"],
  fail_after_debit=case["test_fault_after_debit"], events=events).
Actor: local harness; source account alice is not an authenticated actor.
Authorization and public/network reachability: not assessed.
Request fields: source_id, destination_id, amount_cents.
Internal test control: fail_after_debit; never an external request field.
Implementation amount domain: exact Python int, excluding bool, 1..1000000.
Executed domain: precisely the seven cases below; no Cartesian expansion.
Each call starts in its own fresh on-disk database at S0:
  accounts = [["alice",10000],["bob",2500]]; transfers = []
  active transaction = false; no prior IDs, triggers or competing callers.
No external-effect destination exists in this fixture.
Fault boundary: real zero-amount transfer INSERT after real debit;
CHECK constraint raises IntegrityError before credit/normal record write.

C. INDEPENDENT ORACLE
All amounts and balances are integer cents.
Allowed rejection-side durable effects: none in accounts or transfers,
as required by contract.json. Diagnostic trace events are not DB writes.
Forbidden effects: any account-row or transfer-row difference from S0.
Rejection expected response: {"status":"rejected","reason":REASON}
  missing-destination: alice -> ghost, 2000; unknown_destination
  insufficient-balance: alice -> bob, 10001; insufficient_balance
  zero-amount: alice -> bob, 0; invalid_amount
  negative-amount: alice -> bob, -1; invalid_amount
  record-failure-after-debit: alice -> bob, 2000, internal seam;
    processing_failed
Every rejection's exact expected after-state is S0.
Successful controls, each independently from S0, fault disabled:
  successful-transfer: alice -> bob, 2000
    response = {"status":"accepted","transfer_id":1}
    accounts = [["alice",8000],["bob",4500]]
    transfers = [[1,"alice","bob",2000]]
  successful-full-balance-transfer: alice -> bob, 10000
    response = {"status":"accepted","transfer_id":1}
    accounts = [["alice",0],["bob",12500]]
    transfers = [[1,"alice","bob",10000]]
Independent basis: literal cases.json values from the authored contract.
Observer: run_cases.py::observe_durable_state, using its own SQL:
  SELECT account_id, balance_cents FROM accounts ORDER BY account_id
  SELECT transfer_id, source_id, destination_id, amount_cents
    FROM transfers ORDER BY transfer_id
Milestone: capture response/immediate state/transaction flag; close the
operation connection; reopen the same DB and read durable rows.
Gate: setup matches; response matches; reconnected rows match;
in_transaction_after is false. No implementation calculation is reused.
Timeout/delivery policy: not exercised; no external calls or delivery claim.

D. IMPLEMENTATION AND REPRODUCTION
Baseline and corrected functions coexist in frozen transfer.py.
transfer.py SHA-256:
df8d4cf13056eb283edfce4d8791d77fe5d0cf6bc8f0ac53b05fe6f41ed1580d
Harness/schema/case/contract identities: manifest.json and SHA256SUMS under
/assets/negative-testing/research-rerun/.
Primary runtime: Python 3.14.7; SQLite 3.53.4.
Connection: LEGACY_TRANSACTION_CONTROL explicitly pinned when available;
isolation_level=None. Captured autocommit_attribute=-1, foreign_keys=1,
journal_mode=delete, synchronous=2, in_transaction_before=false.
Policy: baseline has no BEGIN; corrected uses BEGIN IMMEDIATE and explicit
COMMIT/ROLLBACK. Invalid amounts return before BEGIN. BEGIN acquisition
failure is outside the accepted boundary.
Prerequisites: Python 3.10+, standard-library sqlite3, writable temporary
storage, extracted kit; no network, package install or Proof dependency.
Portable command, cwd extracted kit root: python3 reproduce.py
Output: rerun-output/ must be absent or empty; preserve observed/ unchanged.
Alternative: python3 reproduce.py --output-dir another-run
Actual primary child commands, cwd recorded as . containing run_cases.py:
/opt/homebrew/opt/python@3.14/bin/python3.14 -u run_cases.py --implementation baseline --gate response
/opt/homebrew/opt/python@3.14/bin/python3.14 -u run_cases.py --implementation baseline --gate effects
/opt/homebrew/opt/python@3.14/bin/python3.14 -u run_cases.py --implementation corrected --gate effects
/opt/homebrew/opt/python@3.14/bin/python3.14 -u run_cases.py --implementation reject-all --gate effects --cases positive

E. CONTROLS AND SENSITIVITY
Both successful controls above ran against baseline and correction.
Known-bad baseline: response gate passes, effects gate detects the two
specified persisted-state mismatches.
Reject-all challenge: both successful controls fail response and state.
Fault trace confirms the real constraint error; corrected trace confirms
debit inside a transaction and an explicit rollback before return.
This checks sensitivity to these selected failures, not all possible faults.

F. OBSERVATIONS
Primary capture: 2026-10-28T10:06:31.421386+00:00 through
2026-10-28T10:06:31.645946+00:00.
Cases missing-destination and record-failure-after-debit:
  baseline response matches; after reconnect accounts are
  [["alice",8000],["bob",2500]], transfers=[]; transaction closed.
  corrected response matches; after reconnect S0; transaction closed.
Corrected fault trace: temporary 8000/2500, transaction true;
  IntegrityError; explicit rollback to 10000/2500, transaction false.
baseline-response: 7 passed, 0 failed, child exit 0.
baseline-effects: 5 passed, 2 failed, child exit 1.
corrected-effects: 7 passed, 0 failed, child exit 0.
reject-all-positive: 0 passed, 2 failed, child exit 1.
Distinct scenarios: 7, comprising 5 rejections and 2 successes.
Fresh-database invocations: 23 = 7+7+7+2. No skipped selected cases.
Wrapper result: expected contrast reproduced; not baseline acceptance.
Raw evidence base: /assets/negative-testing/research-rerun/observed/
  baseline-response.stdout.json; baseline-effects.stdout.json;
  corrected-effects.stdout.json; reject-all-positive.stdout.json;
  matching .stderr.txt files; reproduction-summary.json.
Summary SHA-256:
9dcfe32769035500260fd90406a0d1e8eaf97ff0141aa62e02e0670501259607
Temporary databases were deleted after their reconnected state was captured.

G. DECISION AND RENEWAL
Supported: corrected implementation matches these seven literal cases
from S0 under the recorded uncontended connection policy.
Baseline: rejected by effects gate. Correction: observed, not merely proposed.
Unperformed: concurrency, lock contention, crash/power loss, disk full,
corrupt database, authorization, notification/delivery, restart/idempotency,
Proof/model/solver execution, production behavior and universal proof.
Actual project acceptance and responsible people: unassigned.
Proposed next actions before adopting or extending this example:
  Engineering lead: assign implementation executor and evidence reviewer.
  Requirement owner: approve real rejection, retry and allowed-effect rules.
  Developer: connect the real supported entry point and rerun its assertions.
  Storage/security/integration owners: plan applicable excluded boundaries.
  Acceptance authority: decide only after required current evidence exists.
Renew evidence after changes to rule, domain, implementation, schema,
cases, observer, runtime, transaction policy or proposed deployment context.

Follow the rejection through six steps

Download the complete video transcript.

Read the complete video transcript

Each recorded case starts with Alice at ten thousand cents, Bob at two thousand five hundred, and no transfer rows. The contract specifies five rejections and two successes. This is a fictional teaching fixture calling real SQLite functions. Its rejection rule requires the exact response, unchanged account and transfer rows, and no open transaction.

The baseline returns unknown destination for a two thousand cent request to ghost. It has already committed Alice's debit. After the connection closes and reopens, Alice has eight thousand cents, Bob still has two thousand five hundred, and no transfer row exists. The response matches; the durable state fails.

The second failure uses an internal test-only seam, not a caller-controlled field. After a real debit, a zero-amount transfer insert triggers a real CHECK constraint error. The failing statement aborts, but the baseline's earlier debit remains committed. The same forbidden balances survive reconnecting. This does not demonstrate attacker reachability.

The implemented correction begins an explicit transaction and checks the destination before debiting. For the injected constraint error, the trace shows a temporary debit inside the transaction, followed by explicit rollback. A fresh connection sees the original balances and no transfer row. The corrected gate checking response and effects passes all seven selected cases.

The independent observer compares literal expected account and transfer rows, not just the balance sum. The two successful controls also reject a handler that refuses every request. Four gates perform twenty-three calls across seven scenarios. Wrapper success means the expected passing and failing children reproduced, not that the baseline became acceptable.

For your own operation, copy the worksheet and agree the rejection owner, allowed effects and forbidden effects before running it. Keep the request, revision, raw observations and next decision together. That work is proposed. Authorization, retries, concurrency, crash recovery and notification delivery were not exercised by this teaching fixture.

Read the text walkthrough

1. Fix the starting state

Each recorded case starts with Alice at ten thousand cents, Bob at two thousand five hundred, and no transfer rows. The contract specifies five rejections and two successes. This is a fictional teaching fixture calling real SQLite functions. Its rejection rule requires the exact response, unchanged account and transfer rows, and no open transaction.

2. Read the stored result

The baseline returns unknown destination for a two thousand cent request to ghost. It has already committed Alice's debit. After the connection closes and reopens, Alice has eight thousand cents, Bob still has two thousand five hundred, and no transfer row exists. The response matches; the durable state fails.

3. Identify the fault boundary

The second failure uses an internal test-only seam, not a caller-controlled field. After a real debit, a zero-amount transfer insert triggers a real CHECK constraint error. The failing statement aborts, but the baseline's earlier debit remains committed. The same forbidden balances survive reconnecting. This does not demonstrate attacker reachability.

4. Inspect the executed correction

The implemented correction begins an explicit transaction and checks the destination before debiting. For the injected constraint error, the trace shows a temporary debit inside the transaction, followed by explicit rollback. A fresh connection sees the original balances and no transfer row. The corrected gate checking response and effects passes all seven selected cases.

5. Challenge a trivial pass

The independent observer compares literal expected account and transfer rows, not just the balance sum. The two successful controls also reject a handler that refuses every request. Four gates perform twenty-three calls across seven scenarios. Wrapper success means the expected passing and failing children reproduced, not that the baseline became acceptable.

6. Define your next check

For your own operation, copy the worksheet and agree the rejection owner, allowed effects and forbidden effects before running it. Keep the request, revision, raw observations and next decision together. That work is proposed. Authorization, retries, concurrency, crash recovery and notification delivery were not exercised by this teaching fixture.

04 / Work in a useful order

How to design and assess a negative test

  1. Agree the rule and owning boundary. Identify who decides acceptable behavior, which component rejects, and whether the case is invalid input, absent data, a state-dependent refusal or a fault. Resolve competing error reasons and valid retry behavior before writing assertions.
  2. Choose inputs and a successful neighbor. Select a concrete representative of each relevant rejection class and the nearby valid behavior it must preserve. Record actor context and caller control. Keep internal fault injection separate from ordinary request fields.
  3. Prepare identifiable state and configuration. Capture the initial rows or objects, pin transaction and dependency settings, and isolate independent cases. For a sequence requirement, retain the whole sequence instead of resetting away the history that matters.
  4. Write the oracle independently. Specify exact response semantics and every allowed or forbidden effect. Name the observer, fields and completion milestone. Derive expected values from the requirement, not by calling the implementation's helper.
  5. Execute the supported path and retain observations. Record the revision, command, actual input, response and post-operation state. Verify that the intended layer or injected boundary ran. Setup failure or an unrelated exception is not a successful negative test.
  6. Challenge, repair and replay. Make the check reject a named forbidden effect and a trivial implementation that refuses valid operations. Correct the responsible code, replay the failure and run successful controls and affected cases. Preserve the failing record beside the correction.
  7. Make the scoped decision. Report distinct cases separately from repeated invocations. Assign missing work and an acceptance authority. Reassess evidence when its requirement, implementation, observer or environment changes.

05 / Check before relying on the result

Negative-testing checklist

Ready to run

  • The rejection rule, owner, input class and supported entry point are identified.
  • Actor permissions and public versus test-only controls are explicit.
  • Initial state, environment and transaction settings are reproducible.
  • Response and effect expectations have an independent requirement basis.
  • Successful controls, a sensitivity challenge and an evidence destination exist.

Ready for a decision

  • The intended implementation and rejection or fault boundary were reached.
  • Observations include the required identities, rows and completion milestone.
  • The asserted response and durable effects both match the approved rule.
  • Selected bad controls fail for the intended reason; valid controls succeed.
  • Failed, unperformed and stale work has owners; acceptance has a recorded scope and authority.

06 / Avoid misleading evidence

Mistakes that hide a rejected operation's effects

Mistake / What it conceals / Repair
Mistake What it conceals Repair
Accept any exception A setup error or unrelated crash can satisfy the test. Check the specified exception or response details, then assert effects separately. Python assertRaises .
Observe after cleanup restores the fixture Cleanup can erase the forbidden write. Capture the operation's result before cleanup; preserve transaction state as well as reconnected rows.
Read a cache or stale replica without accounting for it A delayed view can appear unchanged while the authoritative store differs. Identify the observation source and consistency/completion condition.
Reuse state across supposedly independent cases An earlier call changes the conditions or masks the later result. Reset deliberately, or declare and check the sequence as a sequence.
Ignore every field labeled “telemetry” Required audit effects or leaked business changes disappear from the oracle. Authorize specific allowed changes and assert their own requirements.
Treat a missing row as an SQL exception SQLite permits an UPDATE to affect zero rows without error. Define absence at the application boundary and verify its specified handling. SQLite UPDATE.

07 / Keep intent and observed evidence connected

Use Proof without confusing a witness with an assertion

Inspect the annotation, configured result and connection to implementation code separately. Then review whether the actual assertions protect the specified rejection response and allowed/forbidden effects; these checks do not supply that review or an acceptance decision. The implementation basis was inspected on 28 October 2026.

Proof record or check / What it establishes / What still needs examination
Proof record or check What it establishes What still needs examination
Negative evidence polarity The annotated scenario concerns rejection of invalid input; its test may pass. Whether it reaches the intended boundary and asserts the forbidden effects.
Is the required rejection annotation present? negative_path_witness_required For each active requirement with security obligations, it looks for an eligible REQ-ID:security-class:negative annotation on that requirement or a qualifying child. Any one remaining security class is enough; bare Verifies is insufficient. Inspect the annotation and any exception below. Presence neither executes the test nor establishes coverage of every class or rejection path.
What does the configured current test result say? witness_observed_pass It matches tests credited as requirement evidence to configured reports without starting them. Whether results are current depends on the run/input record. With no configured sources, or none present yet, evaluation is skipped, not a test pass. Distinguish failed, stale, missing-file and not-run/unreported observations. See the warning/error and missing-result rules below; a passing headline need not mean every credited test ran.
What connects the test to implementation code? obligation_witness_grounded A recognized implementation-symbol reference in test text can suffice without execution, even in a comment or string. Otherwise, current suite data attributing implementation execution to that test, or further coverage checking, can supply the connection; even one implementation line can suffice. Inspect text references, execution evidence, exemptions and unavailable coverage separately. An annotated item recognized as non-test can fail this check without execution.

Technical policy details at this revision. A child can credit its parent only if active, explicitly linked by Satisfies, and carrying an overlapping security obligation. Inapplicable suppressed classes are removed; suppressing all excludes the requirement. A check-level audit_ignore waiver is counted separately. A valid deferral with a reason and tracking reference records outstanding work instead of a missing-annotation error. Ordinary deferred work warns; work classified as acceptedKnownIssue is listed separately and need not warn. Invalid deferrals do not excuse absence. None creates a test or passing result.

For result checking, findings normally warn; they become errors for a test file within the source or target paths of a migration/mirror record marked complete. This “complete mirror” does not mean complete test coverage. Certain not-run tests become findings when other credited tests in the same file have observed results; missing suite declarations and test files also matter. Other not-run cases can remain counts.

For code connection, no implementation target makes the check inapplicable. An annotation naming a catalog evidence type marked coverage_trackable=false is exempt; the pinned help names integration, fuzz and differential evidence. This does not verify whether the test ran in another process. Unsupported language, unresolved scope or missing build/run/profile information may be reported as an advisory limitation rather than a confirmed finding; a passing headline can include those gaps.

Run tests in your repository or CI and retain outputs against the candidate and requirement. The SQLite fixture ran no Proof checks.

For a written eligibility predicate, plan cases in Spec Lab, review the interpretation and add the state/effect obligations here. Its hosted analysis produces planned checks; it does not run the repository or SQLite harness.

08 / Choose the missing observation

Which method fits the rejection rule?

Your immediate need / Useful method / Decision boundary
Your immediate need Useful method Decision boundary
Check a specified request and stored result Exact example through the real component plus an independent state observer The fixture runner demonstrates this for selected SQLite cases; choose the actual store for a different claim.
Check an interface that raises an exception A specific exception assertion plus separate effect assertions assertRaises checks the exception, not database state.
Explore more values or malformed inputs Generated cases or fuzzing with the same explicit acceptance oracle Generation expands exercised inputs; it does not determine the correct answer. Oracle survey.
Reach a write failure after progress A controlled fault seam with a trace of the actual failure boundary The injected path's reachability and fault class bound the conclusion. The recorded seam is internal only.
Check whether a green gate can detect a selected mistake A deliberately incorrect implementation or corrupted-effect control Mutation research motivates sensitivity checks; one detected alteration is not a measured mutation campaign.

Use positive controls to challenge trivial satisfaction. This practical check is not formal non-vacuity analysis, which the IBM abstract discusses for temporal models.

09 / Inspect the basis and reproduce it

Primary sources and reproducible evidence

Source, oracle and capture

The portable evidence ZIP contains 18 unchanged files, including source, literal cases, contract, raw stdout/stderr and provenance. It is 22587 bytes. Its SHA-256 is:

17f5964f96e7a82e8b40feeb8395f482228939db92e8478961336773766cb231

Use the ZIP checksum, inner manifest and SHA256SUMS to compare bytes. The inner manifest lists 16 files because it excludes itself and SHA256SUMS; the checksum list has 17 entries because it excludes itself. These counts have different units. A matching digest identifies bytes, not an independently authenticated execution.

The primary provenance and raw summary bind the capture to Python 3.14.7, SQLite 3.53.4, exact source hashes and child commands. Foreign keys are enabled and observed as 1 per connection before a transaction. SQLite's foreign-key documentation explains why enforcement must be established per connection.

Primary references and their scope

Source / Use in this guide / Boundary
Source Use in this guide Boundary
SQLite UPDATE Zero matching rows need not raise an error. Application rejection semantics remain separate.
SQLite ON CONFLICT and CHECK constraints Distinguish statement ABORT from transaction rollback. A constraint does not establish the full rejection contract.
Python sqlite3 and SQLite transactions Explain the pinned connection mode and explicit SQL boundaries. No contention or acquisition-failure acceptance follows from this run.
SQLite atomic commit Defines the transaction/storage boundary. No crash, power-loss, disk-full or corrupt-database experiment occurred.
AWS transactional outbox Separates a stored event intention from later delivery. The teaching fixture has no outbox or notifications.
Python unittest Specific exception assertions. State observations require additional assertions.
Barr et al., oracle survey Distinguishes generated inputs from judging behavior. General testing research, not an assessment of this fixture.
Beer et al., IBM vacuity abstract Motivates checking whether satisfaction is trivial. The inspected abstract concerns temporal model checking.
Just et al., mutation study Motivates challenging tests with selected incorrect implementations. No mutation score is reported here.

Proof's implementation basis is revision b566a01085f0233c0be2c71508a2d3caac0cf47a: docs/help/topics/obligation-evidence.md, pkg/workflow/workflow_negative_path_witness.go, pkg/workflow/workflow_witness_observed_pass.go and docs/help/checks/obligation_witness_grounded.md. Spec Lab describes its planning boundary; the engagement page describes scoped work and evidence handoff. Product-source behavior and the standalone SQLite observations have separate evidence bases.

10 / Apply the record to another decision

Two examples beyond the transfer fixture

1. A repeated request that should succeed

Illustrative requirement and proposed checks; unperformed here. Suppose the contract says that the same request key and payload must return the previously recorded success without another business effect. The repeat is a valid positive case, even if it arrives after a client timeout.

Prepare the completed operation and its recorded response. Submit the same key and payload through the supported interface. Compare the returned outcome with that record, and assert no additional debit, transfer row or event intent. A matching response alone leaves duplicate effects unresolved.

The same key with a different payload is a separate policy decision. Have the owner specify its outcome before classifying the case. Also plan expiry, restart and simultaneous retries if those belong to the product's contract. None of those mechanisms exists in the SQLite teaching fixture.

2. Authorization denial with a required audit event

Illustrative requirement and proposed checks; unperformed here. Suppose a tenant may update only its own document, and a denied update must preserve the document while appending a denial audit record. “Every table stays unchanged” would reject correct behavior under that chosen contract.

Prepare an authenticated actor from another tenant, an existing protected document and the supported update request. Require the specified denial; compare the document's identity, owner, contents and version with the before-state; separately check the permitted audit record's actor, target and reason. Run an owner's successful update as a control.

Establish that the actor can supply the tested document identifier through the real entry point. A direct helper call with a fabricated internal identity does not demonstrate that public path. The transfer fixture supplies no authorization evidence for this example.

11 / Questions that affect the test

Negative-testing questions, answered

How many negative tests are enough?

There is no useful universal count. Inventory the rejection rules, owning layers, relevant starting states and failure boundaries; select cases that can expose different violations. Keep valid controls alongside them. The fixture's seven scenarios support its declared observations, not a recommended suite size or all-input claim.

What if several rejection reasons apply at once?

Specify precedence if callers depend on an exact reason. If the contract permits several equivalent responses, define that set before the run and justify it with the owner. A test should not accept any error simply because it cannot decide which one is right. Compound-invalid cases may need their own coverage beyond single-rule cases.

Can mocked storage be enough?

A mock can examine a component's interactions with a dependency under the mock's assumptions. A claim about committed database state needs observations at that storage boundary. Keep the mock-based claim and the implementation/storage claim distinct, and retain an integration check when the promised rejection depends on real transaction behavior.

How can I check that an asynchronous effect did not happen?

Name the completion signal or bounded observation window and the controlled sink being monitored. An immediate empty result is inconclusive if relevant work is still pending. Inspect event intent, dispatch and delivery separately where the contract requires them. An outbox row is not a delivered notification. AWS's outbox guidance explains those separate operations.