SafetyClaw

Technical architecture review

How SafetyClaw keeps AI helpful without making it an accountant with a write token.

This document is for engineers reviewing the codebase. It explains the trust boundary, tool layers, data flow, state transitions, review targets, safety invariants, and the places most likely to deserve careful attention before wider release.

Executive summary

The core boundary is proposal first, human publish last.

SafetyClaw is a Python and SQLite control plane between AI agents and QuickBooks Online. Its central rule is simple: AI may read bounded context and create draft work, but it may not approve, publish, roll back, enable live writes, or obtain unilateral QuickBooks write authority.

Every agent, upload, parser, model connection, and OpenClaw integration enters the same safety path: structured work becomes a SafetyClaw sandbox proposal. The proposal is visible in Review & Publish, where a human can reject it, approve it, and then perform a separate final publish step. The publish step is browser-session owned and rechecks policy, proposal freshness, QuickBooks realm, and live QuickBooks drift before writing.

The latest handoff build is 20260525-personal-topn-readback-scope. The latest independent live-Chrome canonical measurement scored 9.5/10 raw and calibrated to 10/10 actionable after excluding sandbox-data precondition rows. That is good evidence for limited sandbox human testing, not a claim of production readiness.

Primary guarantee AI-generated work cannot directly write to QuickBooks through SafetyClaw.
Primary review risk The app can still draft accounting work that is technically safe but semantically wrong.
Strongest engineering feature State-changing work is forced through typed proposals, deterministic validators, policy gates, and human publish.
Highest-value next proof Long endurance and chaos testing on the final build, plus human bookkeeper and security review.

Review scope

What a code reviewer should be trying to prove.

The code review should not only ask whether the app works. It should ask whether the safety boundary remains true under messy inputs, retries, stale browser tabs, missing QuickBooks entities, provider errors, and ambiguous human language.

  1. The agent can read only the bounded context it is supposed to read.
  2. The agent can create proposals but cannot publish or approve them.
  3. Every proposal contains enough evidence for a human to review it.
  4. Duplicate retries collapse instead of creating noisy repeated work.
  5. Policy is checked at creation, approval, and publish time.
  6. Publish fails closed if the approved proposal changed underneath the human.
  7. Publish fails closed if QuickBooks live state drifted after proposal creation.
  8. Rendered chat answers do not invent proposal IDs, queue contents, or publish status.
  9. High-risk and unsupported workflows remain proposal-only or out of scope.

Repository map

The codebase is a modular package with a compatibility shell.

The original MVP concentrated much of the product in app/server.py. The current codebase has been decomposed into app/safetyclaw/. server.py remains the HTTP entrypoint and compatibility surface, but most behavior lives in named subpackages.

app/
  server.py                    entrypoint and legacy compatibility shell
  assistant.py                 bounded upload and parser helpers
  qbo_action_catalog.py        action catalog constants
  qbo_hold_reasons.py          hold reason taxonomy
  safetyclaw/
    api/                       HTTP routes and state assembly
    agent/                     persona and agent runtime glue
    audit/                     audit event writer
    auth/                      user and session lifecycle
    llm/                       classifier prompts, schemas, provider adapters
    mcp/                       MCP adapter
    parsers/                   deterministic invoice, bill, payment parsers
    qbo/                       QuickBooks OAuth, sync, resolver, secrets
    render/                    human-readable labels and prose helpers
    runtime/                   environment helper primitives
    safety/                    preflight, policy, capability registry
    sandbox/                   proposals, queue, publish, rollback, work orders
    skills/                    skill package registry
    storage/                   SQLite connection and DDL helpers
    workflows/                 dispatch executors and deterministic readbacks

app/static/
  index.html                   single-page app shell and Help page
  app.js                       browser behavior
  styles.css                   app and document styles
  whitepaper.html              product/safety white paper
  technical-architecture-review.html  this document

qa/
  architecture_fitness_safetyclaw.py  architecture gate
  safety_kernel_guards.py             safety kernel regression gate
  *.py                                focused deterministic regression gates
  tester_kit/                         live Chrome trust kit definitions

Four tool layers

Tool power increases one layer at a time.

SafetyClaw makes the data-manipulation boundary visible by splitting tools into layers. The names guide callers, and backend policy enforces the boundary even if a caller ignores the names.

Layer 1: read Can inspect SafetyClaw state and synced QuickBooks snapshots. Cannot mutate SafetyClaw or QuickBooks.
Layer 2: propose Can create sandbox proposals in Review & Publish. Cannot approve, publish, or write to QuickBooks.
Layer 3: work order Can track multi-row or multi-turn batches so rows are not silently dropped. Still proposal-bound.
Layer 4: publish Can write to QuickBooks only through the authenticated web UI, after approval and final confirmation.

Layer 1: read tools

Read tools answer questions from SafetyClaw state and the synced QuickBooks snapshot. Examples include snapshot summaries, aging reports, customer search, vendor search, item search, and recent invoice metadata. These tools are safe to expose to agents because they have no write path.

Examples:
  safetyclaw.read.qbo_snapshot_summary
  safetyclaw.read.report_aging
  safetyclaw.read.report_pl
  safetyclaw.read.search_customers
  safetyclaw.read.search_vendors
  safetyclaw.read.search_items
  safetyclaw.read.list_recent_invoices

Allowed:
  read synced snapshot rows
  read queue summaries that belong to the user
  answer accounting context questions

Forbidden:
  create proposal rows
  approve or reject proposals
  publish to QuickBooks
  alter policy or credentials

Layer 2: propose tools

Propose tools turn structured intent into a sandbox proposal. A proposal is SafetyClaw state, not a QuickBooks write. Proposals carry action type, source metadata, before and after state, amount, validation checks, source key, and the canonical write plan hash used later at approval and publish.

Examples:
  safetyclaw.propose.invoice
  safetyclaw.propose.bill
  safetyclaw.propose.customer_payment
  safetyclaw.propose.customer_create
  safetyclaw.propose.vendor_create
  safetyclaw.propose.delete_or_void

Allowed:
  create sandbox_changes rows
  attach source labels and evidence
  hold rows that need clarification
  deduplicate active proposals by source key

Forbidden:
  write to QuickBooks
  approve the proposal
  publish the proposal
  bypass Review & Publish

Layer 3: work-order tools

Work-order tools exist for messy work: a pasted invoice batch, a multi-row CSV, a long follow-up chain, or a batch with some complete rows and some held rows. The work-order ledger records expected rows, parsed rows, created proposals, held rows, duplicates, math holds, skipped rows, and unaccounted rows.

This layer is important because an AI assistant can otherwise say "done" after processing only part of a batch. The work-order ledger gives the renderer a durable truth source for counts and proposal IDs.

Examples:
  safetyclaw.work_order.status
  safetyclaw.work_order.list_records
  safetyclaw.propose.work_order_append_records

Allowed:
  record batch progress
  link records to proposal IDs
  report missing or held rows
  detect unaccounted rows

Forbidden:
  silently mark missing work as complete
  publish any row directly
  treat a work order as human approval

Layer 4: publish tools

Publish tools are not agent tools in practice. They are the live QuickBooks write boundary and are only reachable through the authenticated browser UI after a human has approved proposals and prepared a one-time final publish confirmation. Agent tokens, MCP callers, OpenClaw, and chat routes are refused.

Examples:
  safetyclaw.publish.invoice
  safetyclaw.publish.bill
  safetyclaw.publish.customer_payment_exact
  safetyclaw.publish.purchase_category_cleanup
  safetyclaw.publish.customer_create_unique
  safetyclaw.publish.vendor_create_unique
  safetyclaw.publish.item_create_unique

Required before write:
  authenticated web session
  approved proposal
  one-time final publish confirmation
  current policy still allows it
  write_plan_hash still matches
  QuickBooks realm still matches
  live QuickBooks state has not drifted unexpectedly

Forbidden:
  direct publish by AI
  direct publish by MCP or OpenClaw
  publish without final browser confirmation
  publish unsupported action types

Request flow

One user message becomes either an answer, a hold, or proposals.

The agent path is intentionally staged. The earliest layer catches unsafe direct-write instructions before an LLM call. The LLM classifier decides the broad route. Deterministic executors create proposals, answer readback questions, or ask for clarification.

Browser / external agent
  -> HTTP route in app/safetyclaw/api/
  -> authenticate user or bearer token
  -> load user state, policy, snapshot, connection metadata
  -> safety preflight
       direct publish / approve / rollback bypass attempts stop here
  -> dispatch classifier
       bills_batch
       payments_batch
       invoice_batch
       workflow_dispatch
       verify_creation
       state_query
       general_answer
       decline
  -> executor
       create proposals, answer readbacks, ask for more info, or decline
  -> renderer
       final answer plus streaming events and proposal chips
  -> audit log

The important review point is that the LLM chooses an intent route, but it does not directly mutate QuickBooks. The business payload still passes through proposal creation, canonicalization, validation, source key idempotency, policy review, and Review & Publish.

Proposal lifecycle

The proposal is the data-manipulation surface.

SafetyClaw treats proposals as durable work objects. The proposal row stores what the source wanted to do, what action type it maps to, what references it depends on, what validation found, and what a human would be approving.

Normal lifecycle:

  proposed
    -> approved
    -> publishing
    -> committed

Other terminal or holding states:

  blocked       proposal exists, but depends on missing required information
  rejected      human rejected it
  undone        local queue undo, not a QuickBooks rollback
  stale         no longer publishable under current expected state

A held workflow should still become visible work where possible. For example, if an invoice depends on a missing customer, the customer creation proposal and the blocked invoice proposal can both be visible in the queue, with a dependency between them. That is better than hiding the financial work in chat memory.

Source metadata Records whether work came from OpenClaw, a model provider, an upload, local rules, or the UI.
Source key Collapses duplicate active proposals caused by retries or repeated setup prompts.
Validation JSON Stores hold reasons and checks in a list-shaped contract consumed by the frontend.
Write plan hash Binds what was reviewed to what can later be published.

Publish boundary

Approval is not publishing, and publishing is not an agent capability.

Publishing is deliberately the narrowest part of the system. A human first approves one or more proposals. Then the browser prepares a final publish confirmation. Then the browser submits that one-time token to perform the write. The backend rechecks everything before it calls QuickBooks.

Human browser only:

  approve selected proposals
    -> prepare final publish confirmation
       stores selected IDs, expiration, and one-time token
    -> submit final publish token
       recheck user session
       recheck policy
       recheck write_plan_hash
       recheck realm_id
       re-read live QuickBooks state
       claim row as publishing
       call action-specific publisher
       audit result
       commit or fail closed

Reviewers should verify that no external agent surface exposes this boundary. External bearer-token tools can create proposals. They cannot approve them, publish them, roll them back, or enable live writes.

Account and authentication

The publish boundary assumes a real, verified human; here is how that human is authenticated.

The account layer lives in app/safetyclaw/auth/ and is standard-library only — no third-party authentication, JWT, or cryptography packages. Sessions, password hashes, verification and reset tokens, and Google bindings are ordinary SQLite rows, inspectable the same way as the rest of the control plane.

Passwords PBKDF2-SHA256 at 210,000 iterations in auth/__init__.py; a shared validate_new_password enforces a 12-character floor and an HIBP k-anonymity breach check (auth/breach.py, 5-char SHA-1 prefix, fail-open with an auth.breach_check_unavailable audit).
Verified-user gate require_verified_user() gates connect, propose, and publish across nine routes; login itself stays open. Email verification uses single-use, hash-only tokens (auth/tokens.py).
Password reset Reset-request always returns 200 and hands the send to a background thread, so the response does not reveal whether an account exists; reset-confirm validates the new password before it burns the token, swaps the hash, then revokes every session. Reset has a dedicated throttle bucket so it cannot lock a victim's login.
Google sign-in auth/google.py: OAuth2 + PKCE/S256, a single-use state table. Identity is read from Google's UserInfo endpoint over TLS using an access token this client minted through its own authenticated code exchange (client secret + PKCE) — so the token is bound to this client, rather than verifying a local id_token signature (the no-crypto-dependency choice). Linking refuses an unverified-email match to close pre-registration takeover.
Throttle auth/throttle.py applies per-IP and per-account backoff ahead of PBKDF2 on login and signup. Client IP comes only from nginx's X-Real-IP / the rightmost X-Forwarded-For hop; client-supplied (leftmost) values are never trusted. State is in-memory per process (see Known risks).
Token hygiene Verification and reset tokens are stored as hashes only; request-line scrubbing keeps ?token= and OAuth code= values out of journald logs.

Reviewers and penetration testers: the design choices above — UserInfo-over-TLS instead of local JWT verification, HIBP as the only outbound dependency, fail-open breach checking — are deliberate and documented in docs/ACCOUNT_SYSTEM_V2_PLAN.md. The closed response-header-injection finding in the Google ?next= handler is recorded in docs/KNOWN_FINDINGS.md; the in-memory throttle limitation is listed under Known risks below.

Safety invariants

These rules should survive every refactor.

  1. Proposal boundary: every state-changing accounting action must exist as a SafetyClaw proposal before human approval.
  2. No agent publish: LLM callers, MCP clients, OpenClaw, and bearer-token API callers cannot acquire approve, publish, or rollback capabilities.
  3. Hash binding: approval binds the reviewed write_plan_hash; publish recomputes it and fails closed if it changed.
  4. Realm binding: the proposal realm must match the user's currently connected QuickBooks realm before publish.
  5. SyncToken drift check: supported publish and rollback adapters re-read live QuickBooks records and refuse unexpected drift.
  6. Policy recheck: policy is not just creation-time metadata; it is checked again at approval and publish.
  7. Queue truth: assistant answers must cite real active proposal IDs and must not report hidden stale or rejected rows as current queue work.
  8. One renderer contract: user-facing proposal prose should come through shared humanizers and render helpers, not duplicated string fragments.

Policy and capabilities

Safety is enforced by capabilities, not by trusting model behavior.

SafetyClaw has a capability registry and policy review layer. The policy layer distinguishes read, propose, approve, publish, rollback, and human-only capabilities. Agents can get read and propose surfaces. Human-only transitions require the authenticated browser path.

Review points:

  app/safetyclaw/safety/preflight.py
    direct unsafe prompt interception

  app/safetyclaw/safety/capabilities.py
    capability names and human_only flags

  app/safetyclaw/safety/policy.py
    user policy, live-write toggle, thresholds

  app/safetyclaw/safety/policy_review.py
    policy decisions and block details

  app/safetyclaw/sandbox/commit.py
    final publish confirmation and commit orchestration

  app/safetyclaw/sandbox/publishers.py
    action-specific live QuickBooks adapters

A useful review technique is to search for any path that reaches a publisher without going through commit.py, final confirmation, policy recheck, freshness check, and human session ownership. There should not be one.

Entity resolution

Names are resolved conservatively because accounting ambiguity is expensive.

Many failures in accounting agents come from mapping a casual name to the wrong QuickBooks entity. SafetyClaw uses deterministic resolution against synced QuickBooks snapshots and active pending proposals. It can resolve unique shorthand in some cases, but it should hold and ask when the target is missing, ambiguous, or unsafe.

Snapshot entities Customers, vendors, items, accounts, invoices, bills, purchases, and related rows synced from QuickBooks.
Pending entities Active SafetyClaw proposals such as a newly drafted customer or vendor in the same workflow.
Canonicalization Proposal-time code can rewrite a shorthand to a proven entity name before validation.
Publish-time strictness Publish resolves references again and refuses stale, missing, or drifted targets.

The paired-invariant to review is proposal-time canonicalization versus publish-time resolution. Proposal creation can be helpful and forgiving. Publish must be strict.

Readback system

Readbacks are deterministic because users audit the assistant through them.

A major recovery theme was readback honesty. Users ask questions like "what did I draft?", "did that invoice go through?", "what's in my queue?", or "top vendors I billed this year." These answers must be built from actual SafetyClaw and QuickBooks state. The system now has deterministic readback helpers for queue state, session state, calendar periods, entity activity, vendor history, customer AR, open AP, tax-season summaries, and broad SafetyClaw history.

Review points:

  app/safetyclaw/workflows/state_query.py
    queue/session/calendar/top-N/current-history rendering

  app/safetyclaw/workflows/verify_creation.py
    "did it happen?" answer path and ambiguity safety net

  app/safetyclaw/workflows/ar_readback.py
    customer AR details and payment/refund context

  app/safetyclaw/workflows/vendor_history.py
    vendor-side history and held bill context

  app/safetyclaw/workflows/snapshot_aggregates.py
    synced-QBO aggregate readbacks

  app/safetyclaw/workflows/tax_summary.py
    multi-query tax-season readback

The most important rule is that cited proposal IDs must be real and relevant. Hidden stale, rejected, committed, or unrelated rows should not appear as current queue work.

Test strategy

The tests mix unit-style gates with live browser trust measurement.

SafetyClaw uses several kinds of testing because AI accounting systems fail in several ways. Unit-style tests catch deterministic regressions. Architecture fitness catches forbidden implementation patterns. Browser tests catch real UI and conversation behavior. Live sandbox tests catch QuickBooks integration behavior.

Architecture fitness gate Scans app code for forbidden regex/intent shortcuts and required architectural anchors.
Safety kernel guards Verify agents cannot publish, approve, roll back, or bypass policy.
Focused regression tests Each shipped bug fix adds a small targeted test so the bug does not return.
Live Chrome trust kit Runs realistic prompts in the live app and checks the actual Review & Publish state.

The current canonical evidence is strong for limited human testing: two recent canonical runs above 9/10, no P0/P1 clusters in run-54, no forbidden behavior, clean cited-ID checks, and final queue cleanup. The remaining proof gap is fresh year-long endurance and chaos testing on the final build.

Useful local checks:

  python3 qa/architecture_fitness_safetyclaw.py
  python3 qa/safety_kernel_guards.py
  python3 -m py_compile app/server.py app/assistant.py
  node --check app/static/app.js

Useful live checks:

  curl -sS https://aifdoggydog.openclawsystems.com:8443/api/health
  curl -sS https://ziosec.openclawsystems.com/api/health
  verify Review & Publish shows 0 pending / 0 approved in Chrome

Highest-value review targets

Where to spend review time first.

  1. app/safetyclaw/llm/prompts.py: the dispatch classifier prompt is the planner. Review for overlapping rules, prompt accretion, and unclear schema obligations.
  2. app/safetyclaw/workflows/workflow_dispatch.py: maps structured action intent to proposal creation. Review for correct action splitting and safe follow-up behavior.
  3. app/safetyclaw/sandbox/proposals.py: proposal creation, canonicalization, source-key idempotency, and reference checks. This is a load-bearing boundary.
  4. app/safetyclaw/sandbox/canonical.py: write-plan hash and freshness logic. Review for stability and symmetry with publish.
  5. app/safetyclaw/sandbox/commit.py: approval-to-publish orchestration, final confirmation, stale guard, and publish claim behavior.
  6. app/safetyclaw/sandbox/publishers.py: QuickBooks write adapters. Review for drift checks, strict resolution, rollback metadata, and idempotent request IDs.
  7. app/safetyclaw/auth/: the Account System v2 layer and the imminent pentest target — google.py (OAuth/PKCE and the UserInfo identity step), tokens.py (single-use verification and reset tokens), throttle.py (brute-force limits), and the require_verified_user gate. Review the account-linking matrix, token replay, and per-route gate coverage.
  8. app/safetyclaw/workflows/state_query.py: deterministic readback rendering. Review for scope drift and helper proliferation.
  9. app/safetyclaw/qbo/resolver.py: customer/vendor/item/account matching. Review for unsafe fuzzy matches and missing ambiguity holds.
  10. app/safetyclaw/safety/: preflight, policy, and capabilities. Review for any accidental grant of human-only capabilities to agents.
  11. scripts/deploy_vps.sh: deploy gate. Each test explains a past failure class. Review whether the gate still reflects the current risk profile.

Known risks

The architecture is strong, but not magic.

Semantic accounting correctness The system can prevent unsafe writes, but it cannot guarantee every approved proposal is accounting-correct. A bookkeeper or CPA should review common workflows.
Helper complexity Readback and follow-up behavior has many focused helpers. Continue consolidating repeated date, period, entity, and scope logic gradually.
Long-session behavior Canonical trust is strong, but the final build still needs fresh year-long and chaos-arc endurance proof before broad release.
Fixture dependence Some test rows depend on seeded vendors or open AP data. Use explicit fixtures or mark rows data-precondition instead of scoring absent data as product failure.
Secret handling OAuth tokens, model keys, and bearer tokens require focused external security review before production use.
Account throttle durability The WS-1 brute-force throttle holds per-IP and per-account state in memory per process: it resets on restart and does not span processes. Fine for the current single-process deployment; a multi-process or HA setup would need shared (e.g. Redis) state.
Workflow coverage Only a bounded set of QuickBooks actions has live publish support. This is acceptable for MVP if unsupported flows stay proposal-only or out of scope.

Release readiness

Current recommendation: limited sandbox human testing.

The current evidence supports limited human testing in sandbox QuickBooks accounts. It does not yet support broad customer rollout. Since this review was first written, the account and authentication layer described above has been built to production grade (Account System v2) and deployed, and an external penetration test of the live build is the next gate.

Ready now Limited human tester handoff, sandbox QuickBooks only, with queue cleanup after each session.
Not ready yet Production customer use, unsupervised accounting workflows, or claims of full ship readiness.
Next proof Fresh year-long endurance and chaos-arc test on the final build.
Next human review External penetration test of the deployed build (scheduled), then accounting-expert review and structured usability feedback from sandbox testers.

A concise meeting summary: SafetyClaw is doing the right safety thing. It lets AI prepare bookkeeping work while keeping actual QuickBooks writes behind a human-controlled review and publish gate. The next phase should validate durability, usability, security, and accounting judgment before production release.