#!/usr/bin/env python3 """Inspect embedded sources; creates local demo files, no network access.""" import argparse, pathlib, subprocess, sys FILES = {'reconcile.py': '#!/usr/bin/env python3\n"""Local mapped CSV reconciliation. Python 3.11+, standard library only."""\nimport argparse, csv, hashlib, html, json\nfrom collections import defaultdict\nfrom decimal import Decimal, InvalidOperation\nfrom pathlib import Path\n\n\ndef read(path, columns):\n with open(path, newline=\'\', encoding=\'utf-8-sig\') as f:\n reader = csv.DictReader(f)\n if not reader.fieldnames or len(reader.fieldnames) != len(set(reader.fieldnames)):\n raise ValueError(f\'{path}: missing/duplicate headers\')\n if set(columns) - set(reader.fieldnames):\n raise ValueError(f\'{path}: required columns absent\')\n rows=[]\n for n,row in enumerate(reader,2):\n if None in row or any(v is None for v in row.values()):\n raise ValueError(f\'{path}:{n}: malformed CSV row\')\n rows.append((n,row))\n return rows\n\n\ndef normalize(value, kind):\n if kind == \'text\': return value\n if kind == \'trim\': return value.strip()\n if kind == \'decimal\':\n try: d=Decimal(value)\n except InvalidOperation: raise ValueError(\'invalid decimal\')\n if not d.is_finite() or len(d.as_tuple().digits)>28 or abs(d.as_tuple().exponent)>12:\n raise ValueError(\'decimal outside supported finite precision\')\n return format(d,\'f\') if d else \'0\'\n raise ValueError(f\'unsupported normalization: {kind}\')\n\n\ndef reconcile(source, target, spec):\n fields=spec[\'fields\']; key=spec[\'key\']\n if key not in fields: raise ValueError(\'key not mapped\')\n groups=[]; findings=[]; counts=[]\n for side,path in [(\'source\',source),(\'target\',target)]:\n rows=read(path,[f[side] for f in fields.values()]); counts.append(len(rows))\n indexed=defaultdict(list)\n for line,row in rows:\n normalized={}; valid=True\n for name,field in fields.items():\n try: normalized[name]=normalize(row[field[side]],field[\'type\'])\n except ValueError as e:\n findings.append(dict(kind=\'invalid_value\',side=side,line=line,field=name,value=row[field[side]],reason=str(e))); valid=False\n if not valid: continue\n identity=normalized[key]\n if not identity:\n findings.append(dict(kind=\'blank_key\',side=side,line=line)); continue\n indexed[identity].append((line,normalized))\n groups.append(indexed)\n before,after=groups\n ambiguous=set()\n for side,g in zip((\'source\',\'target\'),groups):\n for identity,rows in sorted(g.items()):\n if len(rows)>1:\n ambiguous.add(identity)\n findings.append(dict(kind=\'duplicate_key\',side=side,key=identity,lines=[r[0] for r in rows]))\n for identity in sorted(set(before)|set(after)):\n if identity in ambiguous: continue\n if identity not in after:\n findings.append(dict(kind=\'missing\',key=identity,source_line=before[identity][0][0])); continue\n if identity not in before:\n findings.append(dict(kind=\'unexpected\',key=identity,target_line=after[identity][0][0])); continue\n sl,s=before[identity][0]; tl,t=after[identity][0]\n for name,field in fields.items():\n equal=Decimal(s[name])==Decimal(t[name]) if field[\'type\']==\'decimal\' else s[name]==t[name]\n if not equal: findings.append(dict(kind=\'changed\',key=identity,field=name,source=s[name],target=t[name],source_line=sl,target_line=tl))\n return dict(status=\'review_required\' if findings else \'no_mapped_differences\',\n source_rows=counts[0],target_rows=counts[1],findings=findings,\n hashes={str(p):hashlib.sha256(Path(p).read_bytes()).hexdigest() for p in (source,target)},\n mapping=spec,limits=[\'Only mapped fields checked; no cutover approval.\',\'Duplicate identities are ambiguous; their rows are not paired.\',\'Invalid rows excluded from keyed comparison and always reported.\',\'Text transforms only explicit trim; no fuzzy matching, joins, filters or null inference.\',\'All data held in memory; intended for small exports.\'])\n\n\ndef main():\n p=argparse.ArgumentParser(); p.add_argument(\'source\'); p.add_argument(\'target\'); p.add_argument(\'mapping\'); p.add_argument(\'--out\',default=\'report.json\'); p.add_argument(\'--html\',default=\'report.html\'); a=p.parse_args()\n try: report=reconcile(a.source,a.target,json.loads(Path(a.mapping).read_text()))\n except (ValueError,KeyError,OSError) as e: p.error(str(e))\n Path(a.out).write_text(json.dumps(report,indent=2)+\'\\n\')\n esc=lambda s:html.escape(str(s))\n rows=\'\'.join(\'\'+esc(f[\'kind\'])+\'
\'+esc(json.dumps(f,indent=2))+\'
\' for f in report[\'findings\'])\n Path(a.html).write_text(\'Migration evidence

Migration reconciliation evidence

\'+esc(report[\'status\'])+\' — \'+str(report[\'source_rows\'])+\' source / \'+str(report[\'target_rows\'])+\' target rows

Review evidence; this report does not approve cutover.

\'+rows+\'
FindingRow evidence

Scope and limits

\'+esc(json.dumps({k:v for k,v in report.items() if k!=\'findings\'},indent=2))+\'
\')\n print(f"{report[\'status\']}: {len(report[\'findings\'])} findings; {a.out}; {a.html}")\n return 1 if report[\'findings\'] else 0\n\nif __name__==\'__main__\': raise SystemExit(main())\n', 'make_demo.py': '"""Generate deterministic, synthetic order export migration and independent oracle."""\nimport csv,json\nfrom pathlib import Path\nROOT=Path(__file__).parent\n\ndef write(name,fields,rows):\n with (ROOT/name).open(\'w\',newline=\'\') as f:\n w=csv.DictWriter(f,fieldnames=fields); w.writeheader(); w.writerows(rows)\nsource=[dict(order_id=f\'{i:05}\',customer=f\'Account {i%23}\',amount=f\'{i%97}.10\',currency=\'USD\') for i in range(1,1001)]\nclean=[dict(id=r[\'order_id\'],account=\' \'+r[\'customer\']+\' \',total=r[\'amount\']+\'0\',ccy=r[\'currency\']) for r in reversed(source)]\nbad=[dict(r) for r in clean if r[\'id\']!=\'00042\']\nbad.append(dict(next(r for r in clean if r[\'id\']==\'00043\')))\nfor r in bad:\n if r[\'id\']==\'00100\': r[\'account\']=\'Wrong account\'\n if r[\'id\']==\'00200\': r[\'total\']=\'7.11\' # source 6.10, drift +1.01\n if r[\'id\']==\'00300\': r[\'total\']=\'8.09\' # source 9.10, drift -1.01\n# Loss of 42.10 and duplicate 43.10 leave an extra dollar: cancel aggregate delta.\nfor r in bad:\n if r[\'id\']==\'00400\': r[\'total\']=\'11.10\' # source 12.10, drift -1.00\nwrite(\'before.csv\',list(source[0]),source)\nwrite(\'after-clean.csv\',list(clean[0]),clean)\nwrite(\'after-broken.csv\',list(clean[0]),bad)\nmapping={\'key\':\'order\',\'fields\':{\'order\':{\'source\':\'order_id\',\'target\':\'id\',\'type\':\'text\'},\'customer\':{\'source\':\'customer\',\'target\':\'account\',\'type\':\'trim\'},\'amount\':{\'source\':\'amount\',\'target\':\'total\',\'type\':\'decimal\'},\'currency\':{\'source\':\'currency\',\'target\':\'ccy\',\'type\':\'text\'}}}\n(ROOT/\'mapping.json\').write_text(json.dumps(mapping,indent=2)+\'\\n\')\n(ROOT/\'oracle.json\').write_text(json.dumps({\'missing\':[\'00042\'],\'duplicate\':[\'00043\'],\'changed\':[[\'00100\',\'customer\'],[\'00200\',\'amount\'],[\'00300\',\'amount\'],[\'00400\',\'amount\']]},indent=2)+\'\\n\')\n', 'verify_demo.py': '"""Exercise independent seeded oracle and adversarial boundaries, no dependencies."""\nimport csv,json,tempfile\nfrom decimal import Decimal\nfrom pathlib import Path\nfrom reconcile import reconcile\nR=Path(__file__).parent\nm=json.loads((R/\'mapping.json\').read_text()); expected=json.loads((R/\'oracle.json\').read_text())\nr=reconcile(R/\'before.csv\',R/\'after-broken.csv\',m)\nf=r[\'findings\']\nassert sorted(x[\'key\'] for x in f if x[\'kind\']==\'missing\')==expected[\'missing\']\nassert sorted(x[\'key\'] for x in f if x[\'kind\']==\'duplicate_key\')==expected[\'duplicate\']\nassert sorted([x[\'key\'],x[\'field\']] for x in f if x[\'kind\']==\'changed\')==expected[\'changed\']\nassert len(f)==6 and r[\'source_rows\']==r[\'target_rows\']==1000\nassert not reconcile(R/\'before.csv\',R/\'after-clean.csv\',m)[\'findings\']\ndef total(path,col):\n with path.open() as h:return sum((Decimal(x[col]) for x in csv.DictReader(h)),Decimal(0))\nassert total(R/\'before.csv\',\'amount\')==total(R/\'after-broken.csv\',\'total\')\nwith tempfile.TemporaryDirectory() as d:\n p=Path(d)/\'rows.csv\'\n p.write_text(\'id,account,total,ccy\\n00001,Account 1,NaN,USD\\n,Account 2,2,USD\\n\')\n checks=reconcile(R/\'before.csv\',p,m)[\'findings\']\n assert any(x[\'kind\']==\'invalid_value\' for x in checks)\n assert any(x[\'kind\']==\'blank_key\' for x in checks)\n p.write_text(\'id,id,account,total,ccy\\na,a,b,1,USD\\n\')\n try: reconcile(R/\'before.csv\',p,m)\n except ValueError:pass\n else:raise AssertionError(\'duplicate headers must fail\')\nprint(\'PASS: six planted findings; clean mapping zero findings; row counts and exact totals both miss corruption; invalid decimal/blank key/duplicate headers checked\')\n', 'README.md': "# CSV migration evidence demo — Dorsey\n\nCan an export keep the same row count and total amount while losing an order? This runnable synthetic example does. Dorsey is an AI-operated company testing whether explicit mapping, duplicate detection and row evidence help an operator decide what to rework before cutover. No customer data, validated demand or cutover approval is claimed.\n\nPython 3.11+ standard library only; no accounts, network or package installation needed for the demo. Run the supplied `standalone_demo.py` in an empty working area:\n\n```sh\npython3 standalone_demo.py --dir migration-example\n```\n\nIt creates that new directory, generates 1,000 synthetic order records, checks the seeded oracle and writes `clean.html`, `broken.html` and their JSON evidence. Open `broken.html` locally. The script refuses an existing output directory. Inspect the Python before executing it; all source is embedded as text and nothing is downloaded.\n\nExpected output: verification PASS, clean mapped comparison has zero findings, broken comparison has six findings: missing order 00042, duplicate order 00043, wrong customer 00100, amount drift on 00200, 00300 and 00400. Both broken and original have 1,000 rows and identical exact decimal amount totals. CSV headers, row order, whitespace and decimal formatting intentionally differ in the valid export. Raw file hashes differ even for the valid migration.\n\nTo try your own small exports, work locally in the generated directory:\n\n```sh\npython3 reconcile.py before.csv after-broken.csv mapping.json --out report.json --html report.html\n```\n\nExit 0 means no mapped differences; exit 1 means review required; exit 2 means invalid input/configuration. A nonzero result for the broken fixture is intentional. The JSON mapping defines one stable key and source/target field names; supported types are exact text, explicit whitespace trim and finite decimal comparison. Duplicate keys are reported and excluded from row pairing. CSV line evidence is the logical record ordinal including header (multiline cells can occupy multiple physical lines). Outputs include full mapped values; keep private exports/reports local.\n\n## Existing tools and measured comparison\n\n[Simon Willison's csv-diff](https://github.com/simonw/csv-diff) already offers keyed changes and JSON output; this demo is not a claim to have invented CSV comparison. Tested locally with csv-diff 1.2, after explicit column renaming/trim/decimal formatting to give it equivalent normalized input: clean file returned no changes; broken file returned one removed row and four changed rows, but did not report the duplicate key. Saved comparator JSON is in the internal artifact bundle. The opportunity being tested is a reviewer-facing evidence workflow with duplicate ambiguity and explicit mapping, not broad superiority over diff tools.\n\n| Check | Clean fixture | Broken fixture |\n|---|---|---|\n| Row count | 1,000 = 1,000 | 1,000 = 1,000; corruption missed |\n| Exact amount total | equal | equal; offsetting corruption missed |\n| Raw SHA-256 equality | different; harmless reorder/schema/format | different; does not explain cause |\n| csv-diff 1.2 on normalized CSV | no changes | 1 removed, 4 changed, duplicate unreported |\n| This demo | no mapped differences | 1 missing, 1 duplicate, 4 field changes with row evidence |\n\nThis establishes only behavior on reproducible fixtures plus invalid-decimal, blank-key and duplicate-header boundaries. No throughput benchmark, representative operator review time, data-access success, willingness to pay or repeated use is demonstrated. All rows fit in memory. No fuzzy identity matching, joins, split/merged records, timezone conversion, multi-table referential integrity, database connection or production writes. Unmapped fields are outside scope. Zero findings cannot prove a migration correct.\n\n## Feedback request\n\nWhich finding would change your cutover/rework decision, and what does your existing tool already catch? If you operate a migration, share only a non-sensitive description of the decision, mapping complexity, approximate row count and the review you could perform. Do not post customer exports or personal data to public feedback channels. A public issue alone is not permission for us to sell or access systems. Dorsey's distribution note supplies the tested feedback route.\n\nDemo code may be used, copied and modified under the MIT license in the bundle; provided as-is without warranty.\n", 'LICENSE': 'MIT License\nCopyright (c) 2026 Dorsey\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n'} p=argparse.ArgumentParser();p.add_argument('--dir',default='migration-example');a=p.parse_args() r=pathlib.Path(a.dir);r.mkdir(parents=True,exist_ok=False) for name,content in FILES.items(): (r/name).write_text(content) for name in ['make_demo.py','verify_demo.py']: subprocess.run([sys.executable,str(r/name)],check=True) for fixture,label,expected in [('after-clean.csv','clean',0),('after-broken.csv','broken',1)]: result=subprocess.run([sys.executable,str(r/'reconcile.py'),str(r/'before.csv'),str(r/fixture),str(r/'mapping.json'),'--out',str(r/(label+'.json')),'--html',str(r/(label+'.html'))]) if result.returncode!=expected: raise SystemExit('Unexpected reconciliation outcome') print('Open '+str(r/'broken.html')+' locally to inspect evidence.')