# Append-Only Audit > Audit the event store and the projections behind one Python service before they ship - can a stream fork, can an append land half-way, can an event change after it was written, does every reader see every event in order, does every event reach the read model exactly once in effect across crashes, restarts and rebuilds - then fix them. The free check runs in the browser and uploads nothing: it lexes the pasted Python (one or more files), rebuilds its indentation structure, pulls every SQL statement out of the .sql files and the Python strings (following module constants to the execute and fetch calls that run them), reads the Postgres schema, works out which table holds the events, which the checkpoints and which are read models, finds every projector loop and runs 21 deterministic checks drawn from event-store and projection practice. The paid store audit rates the schema, the appends and the reads; the paid projection audit rates delivery, replay safety, ordering and rebuilds; both confirm or dismiss every flag of their lane and give exact before/after edits per finding. The paid fix lane writes the whole fixed files and a changes list tied to the flags. The page checks every quote and line against the paste, applies every edit and file back into the paste and re-runs its checks, and calls out a reply that repeats a credential from the paste. App: https://append-only-audit.skillsafe.ai/ API tutorial: https://append-only-audit.skillsafe.ai/api.html ## Input - The code pasted into one box, each file introduced by a `# file: ` line (`-- file: ` for SQL); dropped files or a dropped package folder are joined that way automatically, SQL first. A paste with no file line is read as one file (main.py, or schema.sql when it is DDL). - Nothing is run: every flag is a reading of the text, with the file and line it points at. Python strings, f-strings, triple-quoted strings and comments are lexed out first, and SQL comments, quoted text and dollar-quoted bodies, so a comment that says UPDATE events is never a finding. - The events table is found from its columns (a stream id, a per-stream version, an event type or payload) or its name; its global position is the column that is a BIGSERIAL, an identity or a nextval default. Checkpoint tables are found by name (checkpoint, subscription, projection_state ...); read models are the other tables written from Python by code that does not append events. - Transactions are read from `async with .transaction()` and `begin()` blocks and explicit `start()`/`commit()`; a statement's connection is its own (taken from the pool in that function, or run on the pool), a parameter, or an attribute of self. - What a store or a schema that is not pasted does, the isolation level, and how many projector instances run are outside the check (EV-00 says so); tests against a real Postgres stay the ground truth. ## Flags the free check raises Store (the store audit and the fix lane): - ES-01 (high): an events table whose CREATE TABLE has no PRIMARY KEY or UNIQUE on exactly (stream, version) and no unique index on it - or no per-stream version column at all. - ES-02 (medium): the functions that insert events (and their pasted callers) take no expected version and never compare one. - ES-03 (high): UPDATE, DELETE or TRUNCATE on the events table, in Python or in SQL. - ES-04 (high): an append that inserts events in a loop or with several INSERTs, on its own connection, not inside a transaction. - ES-05 (medium): SELECTs of the events table by position (> or >=) when the position is a sequence, with no pg_advisory_xact_lock or LOCK TABLE in the appends and no snapshot guard in the read. - ES-06 (medium): a SELECT of event rows with no ORDER BY (aggregates such as MAX(version) excepted). - ES-07 (low): a global read by position with no LIMIT. - ES-08 (low): no unique client event id (a UUID or text event_id with PRIMARY KEY or UNIQUE). - ES-09 (low): no metadata column (metadata, headers, correlation_id, causation_id ...). - ES-10 (low): no schema version column (schema_version, event_version ...). Projections (the projection audit and the fix lane): - PJ-01 (medium): a projector loop whose checkpoint save is not inside the same transaction block as the handler calls, or whose handlers or checkpoint saver take their own pool connection. - PJ-02 (medium, low when the checkpoint commits with the writes): a read-model write that sets a column from its own value (+, -, ||, array_append, COALESCE(x, 0) +) or an INSERT with no ON CONFLICT, with no position or version comparison in the statement and no processed-events table in the function; one flag per function. - PJ-03 (high): a checkpoint saved before the handler call in the loop, or after the read and before the loop. - PJ-04 (medium): a broad except (bare, Exception, BaseException) around the handler inside the loop that neither raises nor parks the event, or contextlib.suppress around it. - PJ-05 (medium): asyncio.gather, create_task or ensure_future over handler calls, or inside the projector loop. - PJ-06 (medium, low when every write is replay-safe): a rebuild/reset/replay function that deletes or zeroes the checkpoint and never deletes or truncates a read-model table. - PJ-07 (medium): a handler with two or more read-model writes on its own connection, not all in one transaction. - PJ-08 (medium): a `while True` (or while running) loop that applies or reads events and never sleeps or waits (directly or in the function it calls). - PJ-09 (medium): a projector loop that reads events when the paste saves no checkpoint anywhere. - PJ-10 (low): handlers looked up as `handlers[event.event_type]` or `getattr(self, f"...")` with no default. Both: EV-01 (medium) a password in a connection string or a secret-named keyword or key with a literal value; EV-00 (info) context - counts, the events table, what is outside the check. ## Free extras - Every table and the role the page gives it; every SQL statement with its function, connection, transaction, loop, ORDER BY, LIMIT, ON CONFLICT and increments; every projector loop with its handler calls, where it saves the checkpoint and whether that is one transaction with the writes. - Put any audit finding (or all of them, or the fixed files) back into the paste and re-run the check; undo puts the original back. - Exports: the check as Markdown, the flags as CSV, the edited or fixed code, the findings or changes as CSV, the whole result as JSON. ## Paid lanes (the `task` field) - `store` and `projection`: `{task, verdict: blocked|needs_work|ready, headline, system, areas: [{area, status: sound|fragile|broken, note}], findings: [{id, flags, file, line, severity, area, title, evidence, why, edits: [{before, after}]}], flag_answers: [{flag, status: confirmed|dismissed, reason}], plan, questions, summary}`. Store areas: schema, appends, reads. Projection areas: delivery, idempotency, ordering, rebuild. `before` is an exact slice of the file, applied in order. - `fix`: `{task, verdict: fixed|partial, headline, files: [{path, content}], changes: [{file, function, flags, change, why}], verify, open: [{item, flags, reason}], questions, summary}`. `content` is the whole file; every function keeps its name, and a public one its parameters and async or not. - Run input: `{task, code, facts, note?, audit_handoff?, retry_note?}` - all strings; `facts` is the page's analysis as a JSON string, cut for the lane (the store audit gets the store flags, the projection audit the projection flags, the fix lane all of them). Either audit hands its findings to the fix lane with every edit applied to `code`; the store audit can also hand its edited code to the projection audit. - Model: gpt-terra, 10% publisher markup. A run needs a signed-in account; the reservation shown before a run is a hold, and only what the run uses is charged. ## Sources - event-store-design by Seth Hobson, wshobson/agents (github.com/wshobson/agents, plugins/backend-development/skills/event-store-design), MIT - https://skillsafe.ai/skill/@wshobson/event-store-design - projection-patterns by Seth Hobson, wshobson/agents (github.com/wshobson/agents, plugins/backend-development/skills/projection-patterns), MIT - https://skillsafe.ai/skill/@wshobson/projection-patterns - The checks and the reconciliation are original to this app. PostgreSQL is a trademark of the PostgreSQL Community Association of Canada; this app is not affiliated with it or with the asyncpg project. "Event store" here names the general append-only pattern; this app is not affiliated with or endorsed by Kurrent (formerly Event Store Ltd) or its KurrentDB / EventStoreDB products.