Skip to content

Revisit 'Own Our Schema' architecture claim — docs, ADRs, PRD misrepresent what we are #118

Description

@Alberto-Codes

Problem

Multiple foundational documents describe adk-secure-sessions as having an "independent schema" and being "just an encryption layer." Investigation revealed both claims are inaccurate, and the current architecture has a ceiling that blocks multi-database support.

What we discovered

1. Our schema mirrors ADK, it's not independent

Our 4 tables (app_states, user_states, sessions, events) are a copy of ADK's SqliteSessionService schema with TEXTBLOB for encrypted columns and an added version column. Same table names, same columns, same structure.

2. We're a full session service replacement, not an encryption layer

We reimplement all persistence logic (CRUD, state splitting, event storage, connection management) in addition to encryption. Every customer-facing doc says "encryption layer" or "drop-in replacement for DatabaseSessionService" — neither is accurate. We implement BaseSessionService from scratch with our own persistence.

3. ADR-000's decorator rejection reasoning is stale

ADR-000 rejected wrapping because append_event used json_patch SQL operations with no interception point. That was true for V0's per-column event schema. ADK V1 changed this — events are now a single event_data JSON blob, and DatabaseSessionService merges state in Python (dict | delta), not SQL.

4. Architecture B (current) was right for MVP, but has a ceiling

For SQLite-only MVP, reimplementing persistence in raw aiosqlite was pragmatic. But to mature beyond SQLite, we'd have to reimplement everything ADK already provides:

  • PostgreSQL/MySQL/MariaDB dialect handling
  • Connection pooling with pool_pre_ping
  • Row-level locking (SELECT ... FOR UPDATE)
  • Schema version detection and migration
  • GetSessionConfig filtering
  • Timestamp dialect normalization

5. SqliteSessionService cannot be wrapped

SqliteSessionService uses json_patch(state, excluded.state) — a SQLite JSON function that merges deltas in SQL. The state column MUST be valid JSON text. Encrypted data (BLOB or base64) breaks this. This is the same blocker ADR-000 identified, just in a different service.

6. DatabaseSessionService CAN be wrapped

DatabaseSessionService V1 loads state into Python, merges with dict | delta, writes back through SQLAlchemy. No SQL-level JSON operations. A SQLAlchemy TypeDecorator can intercept at the ORM boundary — encrypt on write, decrypt on read. SQLAlchemy is already in our dep tree via google-adk.

7. Table name collision risk

ADK's tables and our tables share identical names. Pointing both at the same database would collide. Wrapping DatabaseSessionService eliminates this — we'd use their tables with encrypted column types.

Architecture decision needed

To mature beyond SQLite-only, wrap DatabaseSessionService via SQLAlchemy TypeDecorator. This gives us:

  • SQLite + PostgreSQL + MySQL + MariaDB encryption — for free
  • Row-level locking, connection pooling, dialect handling — for free
  • Schema migrations when ADK evolves — for free
  • Focus solely on encryption — our actual value-add
  • No table name collision (we use their tables)

The encryption backends (Fernet, AES-256-GCM) are properly abstracted behind EncryptionBackend protocol and survive this change untouched.

Blast radius

10 HIGH / 8 MEDIUM / 8 LOW severity findings across docs, source, and planning artifacts.

HIGH — must fix

File Issue
pyproject.toml:4 "drop-in replacement for DatabaseSessionService"
README.md:13 Same
CLAUDE.md:11 "own schema independent of ADK"
.claude/rules/conventions.md:35-39 "Own Our Schema" — lists 2 of 4 tables, "independent" claim
docs/adr/ADR-004 Entire "own schema" narrative
docs/adr/ADR-000:45 Stale V0-era rejection reasoning
docs/ROADMAP.md:5 "missing encryption layer"
docs/index.md:13 "drop-in replacement for DatabaseSessionService and SqliteSessionService"
src/__init__.py:10-11 "Drop-in replacement for DatabaseSessionService"
src/services/encrypted_session.py:3,123 Same in module + class docstrings

MEDIUM — should fix

ADR-000 additional lines, ARCHITECTURE.md, index.md feature bullet, project-overview.md, docstring-templates.md, services/init.py, PRD line 334, planning architecture

LOW — minor / historical

CHANGELOG entries, epics, project-context.md, implementation artifacts

Action items

Phase A: Documentation honesty (can do now)

  • Revise ADR-004 — "mirror ADK V1 with encrypted column types, managed via raw SQL"
  • Revise ADR-000 — note V1 changes, document why full replacement was chosen for MVP, note wrapping is viable path forward
  • Update conventions.md — fix 2-table parenthetical (we have 4), honest framing
  • Update all HIGH severity files (pyproject.toml, README, CLAUDE.md, index.md, docstrings)
  • Update ROADMAP.md — "encrypted session service" not "encryption layer"

Phase B: Architecture migration (before Phase 3 epics)

  • Spike: prototype wrapping DatabaseSessionService with SQLAlchemy TypeDecorator
  • Evaluate migration path for existing SQLite users
  • New ADR: Architecture migration from direct implementation to DatabaseSessionService wrapper
  • Rewrite encrypted_session.py from ~800 lines raw SQL to thin wrapper
  • Update Epic 4 scope (Postgres comes for free)

Phase C: Sub-issues

  • Runner interaction surface conformance test
  • Behavioral conformance test (round-trip same Session/Event through both services)
  • Namespace tables if any raw-SQL path is retained as fallback

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    adr-violationCode that violates an Architecture Decision RecorddocumentationImprovements or additions to documentationpriority:highHigh prioritytrack:sessionSession service implementationtrack:tech-debtCode quality / architectural debt

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions