Skip to content

Repository files navigation

On Record

A public ledger of requests that can't be quietly edited

Every entry is signed. Every AI transform is disclosed. The page verifies
itself in your browser — offline, with zero network requests.

CI Pages OpenSSF Scorecard TypeScript License: MIT ORCID

Promises are cheap. Proof is checkable.
A map of San Diego where each request carries a cryptographic seal you can break yourself.
Live demo → (no login, no backend — composite data)

Important

Every story in this repository is a composite. No entry describes a real, identifiable individual, and dollar figures shown against institutions are illustrative placeholders, not sourced claims about any real agency or budget. See Ethics.

On Record is two things that fit together. The map (web/index.html) is one self-contained HTML file: it renders San Diego by neighborhood, shows what people have asked for, and lets any visitor check the signature on every entry — then deliberately break one and watch the seal fail. The CLI (src/) is the pipeline that produces those entries: it takes an advocate's raw notes, passes them through a disclosed Claude transform that is forbidden from inventing anything, signs the result with ECDSA P-256, and writes a C2PA-style provenance manifest alongside it.

The point is a system where the claim and the evidence for the claim travel together. An entry that has been altered says so. An entry shaped by a model says that too, in the record, permanently.

Quick start

git clone https://github.com/writerslogic/onrecord.git
cd onrecord
python3 -m http.server 8080 --directory web

Open http://localhost:8080/. Nothing else is required — no build, no dependencies, no API key. The map generates its own signing key in the browser on load.

To work with the signing pipeline instead:

npm install
npm run build
npm run seed          # write the composite sample set to data/
npm run verify        # independently re-check every signature

What you can do on the map

  • Pick a neighborhood, then a pin. Each pin is one request: an ID replacement, a shelter bed, a prescription, a work document.
  • Read it two ways. Every story shows their words (the raw note) and the AI-shaped version side by side. You are never shown the shaped text without access to what it came from.
  • Verify. The key chip turns green when the browser's key is sealed; hitting verify re-computes the SHA-256 of the canonical entry and checks the ECDSA signature — in your browser, with WebCrypto.
  • Tamper. Change a stored entry and the pin turns red, the seal breaks, and the failure is legible. Restore puts it back.
  • Guide me. A self-driving walkthrough runs the whole arc and ends on the non-response rate.

How the proof works

The scheme is deliberately small so a browser can re-verify it with crypto.subtle and nothing else:

canonical = canonicalize(entry without its provenance block)   # fixed key order
digest    = SHA-256(utf8(canonical))                           # 32 bytes
signature = ECDSA-P256-SHA256(digest)                          # over the digest bytes

contentHash is the lowercase hex of that digest. signature is base64 of the raw 64-byte IEEE P-1363 r||s pair — what WebCrypto emits, not DER. pubKey is base64 SPKI DER. A verifier therefore answers two independent questions: did the content change (the hash), and was this key the signer (the signature).

New entries also carry a protocol-v2 envelope: deterministic CBOR, detached COSE_Sign1 ES256, and a publisher DID (did:web when ONRECORD_ISSUER_DID is configured, otherwise a self-contained did:key). Existing v1 JSON/ECDSA entries remain readable and verifiable.

The CLI verifier is authoritative for protocol-v2 records. The self-contained demo viewer currently renders its embedded composite demo dataset and uses its own legacy interaction seals; it is not a network client and does not silently claim to validate the CLI’s v2 envelope.

Key order in src/schema.ts is load-bearing. canonicalize() serializes fields in exactly that order, so reordering a field there invalidates every signature ever produced. Full format, manifest structure, and threat model: docs/protocol.md.

The AI step, and its limits

on-record add sends the advocate's raw note to Claude with a system prompt (src/transform.ts) whose entire job is subtraction, not addition. It may fix transcription noise, drop identifying detail, and remove an advocate's editorializing. It may not add a fact, soften the truth, add hope the person did not express, or make a claim about any organization. If the raw note is too sparse to shape without inventing, the correct output is the raw note nearly unchanged.

Both raw and shaped are stored and both are published, and the transform is recorded as an assertion in the provenance manifest. The model is a participant in the record, not a ghostwriter hiding behind it.

CLI

on-record add    [--file <path>] --zone <zone> --category <cat> --summary <text>
                 [--amount <usd>] --advocate <id> --consent-method <text>
                 [--consent-at <iso>] [--status <status>] [--id <id>]
                 [--org-claim <text>] [--source <text>] [--json]
                 (raw story is read from stdin when --file is omitted)

on-record verify [<file>] [--did-doc <path>] [--json]    independently re-check every signature
on-record seed   [--force] [--ai]     write the composite sample set
on-record serve  [--port <n>]         serve the local viewer
on-record keys                        show the signing key in use

--advocate and --consent-method are required and have no default. An entry with no named advocate and no record of how consent was given is refused, because consent is not optional and a system that lets you skip it will be used to skip it.

Environment: ANTHROPIC_API_KEY is required for add; ONRECORD_MODEL overrides the default model. Copy .env.example to .env to set them.

Project layout

onrecord/
├── web/index.html      the map — one self-contained file, no build, no network
├── src/                the on-record CLI
│   ├── schema.ts       entry contract + canonicalization (key order is load-bearing)
│   ├── sign.ts         ECDSA P-256 signing and C2PA-style manifests
│   ├── transform.ts    the disclosed Claude story-shaping step
│   ├── verify.ts       independent signature re-check
│   ├── seed.ts         composite sample set
│   └── cli.ts          command dispatch
├── data/               signed entries + provenance manifests
└── docs/protocol.md    signing format, manifest structure, threat model

keys/ is generated on first run and is git-ignored. The private key never leaves the machine that made it; committing one would let anyone forge entries under your identity.

Deployment

The map is deployed to GitHub Pages from web/ by .github/workflows/pages.yml on every push to main. Enable it once under Settings ▸ Pages ▸ Source: GitHub Actions; after that it is automatic. Forks get a working demo without edits — the workflow derives no repository-specific paths.

Hosting it anywhere else is equally simple: the map is a single static file with no build step and no backend, so any static host works. HTTPS is required, because WebCrypto is unavailable on insecure origins.

Verifying the "no network requests" claim

The claim is enforced, not just asserted. web/index.html carries a Content-Security-Policy meta tag with default-src 'none', so the browser blocks any fetch, font, tile, remote image, or analytics beacon the page could ever attempt. WebCrypto is unaffected by CSP, so signing and verification still work.

To confirm it yourself: open DevTools ▸ Network and reload. Only the HTML document should appear.

Ethics, and the data in this repo

This project is about people who cannot correct the record about themselves, which sets the rules:

  • The sample entries are composites. They are marked as such in data/entries.json. They do not describe real individuals and must not be cited as though they do.
  • Institutional figures are placeholders. Any dollar amount shown against an agency or budget is an illustrative round number, attributed to no real entity, present only to demonstrate the promise-versus-proof mechanic.
  • Consent is structural, not procedural. The schema has nowhere to put an entry that lacks a named advocate and a recorded consent method, so there is no path that quietly omits it.
  • The raw text always ships with the shaped text. A reader who distrusts the model can always read around it.

If you deploy this with real people's accounts, those obligations become yours, and they are heavier than the code.

Contributing

See CONTRIBUTING.md. Security issues: SECURITY.md — please do not open a public issue for a vulnerability.

License

MIT © David Condrey

About

Claude Hackathon Project

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages