Repository navigation
setup_sql: let a caller adjust a new instance's rows with SQL at reset - #48
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
`world.instance(..., setup_sql=...)` takes a string of SQL statements. Instance
creation runs it after the fixture copy or blank build and before any node
connection is opened. It runs on one writable connection to the root's file
with every added node attached under its schema name (`payments`,
`payments__tax`), in one transaction, committed and closed before the startup
hooks run. Hooks see the rows it wrote. Its rows are not in the change log,
and `call_count` stays 0.
- New `seahaven/setup_sql.py`: `run_setup_sql`, `split_statements` (splits on
`;` only where `apsw.complete` agrees), and `SetupAuthorizer`, the sandbox's
default-deny authorizer, writable over every table, view and FTS5 shadow
table, recording the first refusal per statement. Every failure is a
`WorldBug`, raised from the original error, of the form
"setup_sql statement {i} of {n} {was refused|failed}: {reason} {fix}" plus
the statement, whitespace collapsed and cut at 200 characters.
- `ids.SETUP_STREAM`: `random()` and `randomblob()` draw from the root's seed
on a stream of their own, whichever node a row lands in. The instance's own
draws are the same with and without setup SQL.
- `InstanceManager.create` and `World.instance` take `setup_sql`. A non-string
value is refused before anything is built. `Instance.setup_sql` holds the
string as given. `_attachment_pairs` is shared with
`Instance._attachments()`. `synchronous OFF` is set on every attached schema
as well as `main`.
- The pytest marker signature lists `setup_sql=None`.
- Tests: `tests/test_setup_sql.py`, mostly through `world.instance(...)`, cover
composed worlds, FTS5, fixtures, startup hooks, seeding, the clock, the
change log, every refusal, rollback leaving no instance directory, deferred
constraints, triggers and views. Review fixes included: a module docstring
line in setup_sql.py and an `instance_dirs` assertion in the tests.
Deviations from the spec:
- Read-only pragmas: the architecture says read-only pragmas are allowed, but
the base `Authorizer` allows only `data_version`. `SetupAuthorizer` also
allows `db._READ_ONLY_PRAGMAS`, the inspection connection's list, so the
error message "Only read-only pragmas such as table_info are allowed" is
true.
- Triggers: the spec holds every action to the run_sql function and table
allowlists. The code trusts world trigger bodies, matched by name, for
function calls and reads. Without that, a world trigger using `changes()`
or `json_each` made every `INSERT` into its table fail. A caller cannot
create a trigger. Views are not trusted: SQLite names a view while it
resolves the caller's own WHERE clause in DML on an INSTEAD OF view. A name
shared by a trigger and a table or view is not trusted either. Accepted
residual: SQLite also names a CTE, so a CTE named after a world trigger
shares its trust for functions and reads. setup_sql comes from the eval
author and these checks catch mistakes, not attacks. A test pins the
residual.
- COMMIT: the architecture's error table covers statement failures only. A
failure at COMMIT (a deferred constraint) is also a `WorldBug`, "setup_sql
failed at commit: ...", because creation must fail with `WorldBug`.
- `sqlite_sequence`: the architecture calls a write to any `sqlite_*` table a
schema change. A write to `sqlite_sequence` is still refused, but with the
generic reason; only `sqlite_master`, `sqlite_schema` and
`sqlite_temp_master` writes are called schema changes.
- Pins: the architecture leaves the reset-key pins to phase 2. The key lists in
`tests/test_cli_mcp.py` and `tests/test_mcp_process.py` are updated here,
because `RESET_OPTION_KEYS` is read off the `World.instance` signature and
takes `setup_sql` from this phase on. `tests/test_world.py`,
`tests/test_ids.py` and `tests/test_composite_instance.py` pick up the new
parameter and stream.
Note for the docs phase: SQLite looks up an unqualified table name in the
attached schemas too, so an unqualified `charges` reaches `payments.charges`
when the root has no `charges` table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
…ocument
`setup_sql` now reaches an instance through every entry point, and the state
document reports it.
- State document: the envelope gains `setup_sql` directly after `startup`:
the string exactly as given (an empty string stays ""), null when there
was none and before the first reset.
- OpenEnv: `SeahavenEnv.reset` takes `setup_sql` and forwards it to
`InstanceManager.create`. `SeahavenResetRequest` publishes it as a nullable
string titled "Setup Sql", ordered after `startup` by `RESET_ORDER`, so the
console renders it as a multi-line box. `SeahavenState.setup_sql` carries it
to clients.
- HTTP `PUT` and `seahaven mcp --reset-options` needed no code: both read
their keys off `World.instance`. The stale "five names" comment in
`cli/mcp.py` is fixed.
- Console: the mock reset schema has the field, and the e2e smoke test
checks the form order and that the field is a textarea. No console source
change; the vendored build is unchanged.
- Pins updated: reset parameters and declared descriptions, reset schema
order, `state_v1.schema.json`, envelope key order and pre-reset nulls,
the WebSocket whole-document episode, and the client's document frame. The
client's pre-reset test now also asserts `clock_mode` is null, and its
docstring counts seven null fields.
Tests: in-process `reset` with `setup_sql` and a failing one leaving the
session fresh; a WebSocket round trip with a refused statement as an
EXECUTION_ERROR frame followed by a good reset; the whole document over the
wire carrying `setup_sql` with its row absent from the log; a `PUT` body
with `setup_sql` and two refused bodies answering 400; `--reset-options`
in the CLI unit tests and through a real MCP process; and in projecttracker,
`agency` plus SQL inserting a user (with an ISO `created_at` literal) plus
`startup={"user_id": ...}`, where `create_issue` is attributed to that user
and `users` is not in the change log.
Deviation: the architecture said the projecttracker test "resets agency".
The code drives it in process through the `@pytest.mark.seahaven` marker,
which goes through `world.instance(...)`, because the world's OpenEnv
module already covers the wire and the attribution is the same either way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
The bundled docs now cover `setup_sql`. Six existing pages were updated and no
page was added.
- db_schema_and_fixtures.md: a new section, "Adjusting a fixture per episode",
with a table-of-contents row. It says what `setup_sql` is for and when it
runs (after the fixture copy or the blank schema build, before the startup
hooks, in one transaction). It also says that hooks see its rows, that those
rows are not in the change log, and that the state document records it. The
advice is to write rows only, with a link to the refusal list. The section
covers composed-world table names and how the seed and clock behave, and a
table on when to use a new fixture, `setup_sql` or a startup hook. One
runnable example freezes a fixture on an inline world, then adjusts it with
two statements, one of which has a `;` inside a literal.
- serving_and_openenv.md: a `setup_sql=` row in the reset table, one example
reset message, a `setup_sql` row in the `SeahavenState` table, and
`setup_sql` in the list of fields that are null before the first reset.
- state.md: `setup_sql` in the example envelope and the envelope table. The
"not in the log" rule now covers `setup_sql` rows. The starting state is
the fixture, then the setup_sql rows, then the hooks' writes.
- composition.md: one paragraph and a fragment showing one statement that
reads the root's `staff` and writes `payments.charges`.
- reference/api.md: `setup_sql=None` in the `world.instance` row and in the
marker signature, an `inst.setup_sql` row, and a `### setup_sql`
subsection. The subsection says what the connection sees and allows, and
holds the list of what is refused: schema changes, transaction control,
ATTACH/DETACH, any pragma but the reporting ones, functions outside
ALLOWED_FUNCTIONS (MATCH included), table-valued functions such as
json_each() and pragma_table_info(), and writes to FTS5 shadow tables or
SQLite's own tables.
- reference/cli.md: a `--reset-options '{"setup_sql": ...}'` example. The
paragraph names `"setup_sql"` beside `"startup"`.
Review fixes are included:
- The pragma rule now says any pragma that does not only report is refused,
with or without a value.
- The blank-instance case ("or the blank schema is built") is in both the
guide and the reference.
- One term is used consistently.
- The cli.md sentence is reworded.
- A sentence in the guide is split in two.
- The composition fragment inserts charges with a distinct id ('c-' || id).
No deviation from the spec or the architecture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds a framework-level
setup_sqlparameter besidefixture,startupandnowonworld.instance(...)and the OpenEnvreset. With it, a caller can change a new instance's rows using SQL, without writing a startup hook. The spec is inspecs/projects/startup_sql/.Order of work at instance creation:
setup_sqlruns in one transaction on one hardened connection to the root, with every added node attached by schema name (payments.charges,payments__tax.rates).Change log and state: setup rows are not in the change log, and
call_countstays 0. The state document echoessetup_sqlbesidestartup.Statements:
;separates statements, split usingapsw.complete, so a;inside a string literal is safe.Determinism:
random()draws from its own seeded stream (SETUP_STREAM), so the instance's own draws do not change. The clock functions read the instance's clock.Guard: a default-deny authorizer, based on the sandbox
Authorizer, limits it to reading and writing rows.run_sqlallowlist.WorldBugthat names the statement (setup_sql statement 2 of 3 was refused: … <fix>) and rolls everything back. No instance directory is left behind.PUTanswers 400.Entry points:
world.instance, OpenEnvreset(in process and over the WebSocket), HTTPPUT,seahaven mcp --reset-options, and the/uiconsole. The console shows a multi-line "Setup Sql" box with no console code change, because the field is one nullable string.Commits
seahaven/setup_sql.py, the parameter onWorld.instance,InstanceManager.createandInstance, the pytest marker, and about 95 tests intests/test_setup_sql.py.SeahavenState,SeahavenResetRequest,RESET_ORDER, the console mock and smoke order, every pinned test, and entry-point tests including a projecttracker test.Deviations from the spec (decided during review)
changes()orjson_eachwould make every INSERT into its table fail.setup_sqlcomes from the eval author, and the guard catches mistakes rather than attacks.WorldBug.Test plan
tysplit, framework, projecttracker and xmlrpc suites, licence gates) in both theserveandmcpenvironments.world.instance(...)and through each real entry point.🤖 Generated with Claude Code
https://claude.ai/code/session_01D327Rt1yFbLSkhA7ocsWhz
Generated by Claude Code