FA-7981 / Form lifecycle / Open access
Form dirty-field bookkeeping: Typing rewrites the baseline instead of the draft · case 01
Typing rewrites the baseline instead of the draft.
ROOT CAUSE
The input operation uses `initial[name] = value` where the contract requires `current[name] = value`.
VERIFIED REPAIR
Implement the input operation as `current[name] = value`.
Unsuccessful approach: Setdefault ignores edits to existing fields.
Case contract
Edits change draft only; reset restores baseline; commit accepts draft; unmount removal clears both; dirty comparison includes additions and removals.
Why this case matters
A deterministic model of form dirty-field bookkeeping; this isolates one interface invariant without requiring a browser.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(initial, current, event, name, value):
initial, current = initial.copy(), current.copy()
if event == 'input': initial[name] = value
elif event == 'reset': current = dict(initial)
elif event == 'commit': initial = dict(current)
elif event == 'remove': current.pop(name, None); initial.pop(name, None)
dirty = sorted(k for k in set(initial) | set(current) if initial.get(k, object()) != current.get(k, object()))
return (initial, current, dirty)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('edit', solve({'a':1}, {'a':1}, 'input', 'a', 2), ({'a': 1}, {'a': 2}, ['a']))
check('reset', solve({'a':1}, {'a':2}, 'reset', None, None), ({'a': 1}, {'a': 1}, []))
check('commit', solve({'a':1}, {'a':2}, 'commit', None, None), ({'a': 2}, {'a': 2}, []))
check('remove', solve({'a':1,'b':2}, {'a':3,'b':2}, 'remove', 'a', None), ({'b': 2}, {'b': 2}, []))
check('new', solve({}, {'new':0}, 'read', None, None), ({}, {'new': 0}, ['new']))
check('removed', solve({'a':1}, {}, 'read', None, None), ({'a': 1}, {}, ['a']))
check('parameterized dirty value', solve({'a':N}, {'a':N}, 'input', 'a', N+1), ({'a':N}, {'a':N+1}, ['a']))
for repetition in range(N):
check('repeat removed', solve({'a':1}, {}, 'read', None, None), ({'a': 1}, {}, ['a']))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| edit | [{'a': 2}, {'a': 1}, ['a']] | [{'a': 1}, {'a': 2}, ['a']] | Failed |
| reset | [{'a': 1}, {'a': 1}, []] | [{'a': 1}, {'a': 1}, []] | Passed |
| commit | [{'a': 2}, {'a': 2}, []] | [{'a': 2}, {'a': 2}, []] | Passed |
| remove | [{'b': 2}, {'b': 2}, []] | [{'b': 2}, {'b': 2}, []] | Passed |
| new | [{}, {'new': 0}, ['new']] | [{}, {'new': 0}, ['new']] | Passed |
| removed | [{'a': 1}, {}, ['a']] | [{'a': 1}, {}, ['a']] | Passed |
| parameterized dirty value | [{'a': 2}, {'a': 1}, ['a']] | [{'a': 1}, {'a': 2}, ['a']] | Failed |
| repeat removed | [{'a': 1}, {}, ['a']] | [{'a': 1}, {}, ['a']] | Passed |
SHA-256 / 22aeab6fb9b954d433157b4c8894f0ee6245a30a6fdb5e26251eef89605822e3
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(initial, current, event, name, value):
initial, current = initial.copy(), current.copy()
if event == 'input': current.setdefault(name, value)
elif event == 'reset': current = dict(initial)
elif event == 'commit': initial = dict(current)
elif event == 'remove': current.pop(name, None); initial.pop(name, None)
dirty = sorted(k for k in set(initial) | set(current) if initial.get(k, object()) != current.get(k, object()))
return (initial, current, dirty)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('edit', solve({'a':1}, {'a':1}, 'input', 'a', 2), ({'a': 1}, {'a': 2}, ['a']))
check('reset', solve({'a':1}, {'a':2}, 'reset', None, None), ({'a': 1}, {'a': 1}, []))
check('commit', solve({'a':1}, {'a':2}, 'commit', None, None), ({'a': 2}, {'a': 2}, []))
check('remove', solve({'a':1,'b':2}, {'a':3,'b':2}, 'remove', 'a', None), ({'b': 2}, {'b': 2}, []))
check('new', solve({}, {'new':0}, 'read', None, None), ({}, {'new': 0}, ['new']))
check('removed', solve({'a':1}, {}, 'read', None, None), ({'a': 1}, {}, ['a']))
check('parameterized dirty value', solve({'a':N}, {'a':N}, 'input', 'a', N+1), ({'a':N}, {'a':N+1}, ['a']))
for repetition in range(N):
check('repeat removed', solve({'a':1}, {}, 'read', None, None), ({'a': 1}, {}, ['a']))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| edit | [{'a': 1}, {'a': 1}, []] | [{'a': 1}, {'a': 2}, ['a']] | Failed |
| reset | [{'a': 1}, {'a': 1}, []] | [{'a': 1}, {'a': 1}, []] | Passed |
| commit | [{'a': 2}, {'a': 2}, []] | [{'a': 2}, {'a': 2}, []] | Passed |
| remove | [{'b': 2}, {'b': 2}, []] | [{'b': 2}, {'b': 2}, []] | Passed |
| new | [{}, {'new': 0}, ['new']] | [{}, {'new': 0}, ['new']] | Passed |
| removed | [{'a': 1}, {}, ['a']] | [{'a': 1}, {}, ['a']] | Passed |
| parameterized dirty value | [{'a': 1}, {'a': 1}, []] | [{'a': 1}, {'a': 2}, ['a']] | Failed |
| repeat removed | [{'a': 1}, {}, ['a']] | [{'a': 1}, {}, ['a']] | Passed |
SHA-256 / 7505b7a289e0601cd816f5f11d60e2b9133d39a5bb430d73c9a9e929e9a8883a
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(initial, current, event, name, value):
initial, current = initial.copy(), current.copy()
if event == 'input': current[name] = value
elif event == 'reset': current = dict(initial)
elif event == 'commit': initial = dict(current)
elif event == 'remove': current.pop(name, None); initial.pop(name, None)
dirty = sorted(k for k in set(initial) | set(current) if initial.get(k, object()) != current.get(k, object()))
return (initial, current, dirty)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('edit', solve({'a':1}, {'a':1}, 'input', 'a', 2), ({'a': 1}, {'a': 2}, ['a']))
check('reset', solve({'a':1}, {'a':2}, 'reset', None, None), ({'a': 1}, {'a': 1}, []))
check('commit', solve({'a':1}, {'a':2}, 'commit', None, None), ({'a': 2}, {'a': 2}, []))
check('remove', solve({'a':1,'b':2}, {'a':3,'b':2}, 'remove', 'a', None), ({'b': 2}, {'b': 2}, []))
check('new', solve({}, {'new':0}, 'read', None, None), ({}, {'new': 0}, ['new']))
check('removed', solve({'a':1}, {}, 'read', None, None), ({'a': 1}, {}, ['a']))
check('parameterized dirty value', solve({'a':N}, {'a':N}, 'input', 'a', N+1), ({'a':N}, {'a':N+1}, ['a']))
for repetition in range(N):
check('repeat removed', solve({'a':1}, {}, 'read', None, None), ({'a': 1}, {}, ['a']))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| edit | [{'a': 1}, {'a': 2}, ['a']] | [{'a': 1}, {'a': 2}, ['a']] | Passed |
| reset | [{'a': 1}, {'a': 1}, []] | [{'a': 1}, {'a': 1}, []] | Passed |
| commit | [{'a': 2}, {'a': 2}, []] | [{'a': 2}, {'a': 2}, []] | Passed |
| remove | [{'b': 2}, {'b': 2}, []] | [{'b': 2}, {'b': 2}, []] | Passed |
| new | [{}, {'new': 0}, ['new']] | [{}, {'new': 0}, ['new']] | Passed |
| removed | [{'a': 1}, {}, ['a']] | [{'a': 1}, {}, ['a']] | Passed |
| parameterized dirty value | [{'a': 1}, {'a': 2}, ['a']] | [{'a': 1}, {'a': 2}, ['a']] | Passed |
| repeat removed | [{'a': 1}, {}, ['a']] | [{'a': 1}, {}, ['a']] | Passed |
SHA-256 / b63e8e7931c4bdb9300dcd6b9f00fae2852e95c78e3e5bd58ac37e930f722a94
Verification & scope
This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:38:17.094770+00:00.
Case digest / ed5e7c975f444235e546903608211fb5cc7032245e3fc33f40221f35f8a47668