A lexicon-driven AppView for ATProto.

feat(scripts): trigger-keyed scripts subsystem master

Replaces the legacy `index_hook` lookup with a trigger-keyed scripts table where each row's PK IS the trigger string the dispatcher resolves on. There's no separate "name" or "host column" — bindings, names, and inline-vs-named all collapse into one column. Trigger grammar: record.index:<nsid> — wildcard for any record event record.create:<nsid> — specifically a create event record.update:<nsid> — specifically an update event record.delete:<nsid> — specifically a delete event xrpc.query:<nsid> — XRPC query handler xrpc.procedure:<nsid> — XRPC procedure handler labeler.apply:<nsid> — label whose uri is at://<did>/<nsid>/<rkey> labeler.apply:_actor — label whose uri is a bare DID Cascade for record events ONLY: the dispatcher tries `record.<action>:<nsid>` first, falls back to `record.index:<nsid>`. No cascade for XRPC or labeler triggers. Schema (one new migration, no data copy — origin/dev had no host columns to migrate from): - New `scripts` table (id PK + body / description / script_type + timestamps). - New `dead_letter_scripts` table (script_ref carries the trigger id). - Re-grants `scripts:read` to lexicon-readers, `scripts:manage` to lexicon-managers. Code surface: - New `src/lua/scripts.rs` dispatcher: ParsedTrigger grammar + validator, ScriptRow / ResolvedScript, `resolve` / `resolve_record_event` (with cascade), `run_record_event_script` (fail-open + retry + dead-letter), `run_label_applied_script`, `trigger_for_label_uri` (at:// → nsid, bare DID → _actor), and `run_record_event_once` for the dead-letter retry path. - New `src/admin/scripts.rs` CRUD (list / get / upsert / patch / delete) with trigger-id grammar validation at write-time and Lua body validation via `crate::lua::validate_script`. - New `Permission::ScriptsRead` / `Permission::ScriptsManage`. - `src/labeler.rs::apply_label` computes the trigger from `label.uri` and runs the script chain before persisting; rewritten labels persist; nil-returning scripts skip. - `src/record_handler.rs::handle_record_event` calls `run_record_event_script(state, &collection, &action, ...)` for both create/update and delete actions. - `src/xrpc/procedure.rs` and `src/xrpc/query.rs` look up via the new dispatcher (trigger `xrpc.procedure:<id>` / `xrpc.query:<id>`) before falling through to the default PDS-write / list flows. - `src/admin/dead_letters.rs::retry_single` resolves via the new dispatcher's cascade (404s if no script matches now). - Legacy `execute_hook_script`, `HookEvent`, and `run_hook_once` removed from `src/lua/execute.rs`. - `register_record_api` refactored to take Optional Claims / PdsAuth; new `register_record_api_no_auth` public wrapper for the no-auth contexts (record / labeler / query). PDS-touching methods (`r:save`, `r:delete`, `Record.save_all`) error cleanly with "no PDS auth in this script context" when registered without auth. - `r:save_local()` / `r:delete_local()` instance methods + `Record.delete_local(uri)` static for local-only mutation in any script context. - `r:delete()` proceeds with the local delete even on PDS failure (operator's logical action is removal regardless of PDS state). Legacy `lexicons.index_hook` column is retained but inert — operators with existing data manually port into a `record.index:<nsid>` script row via the dashboard. Tests: - 12 new lib tests in `src/lua/scripts.rs` covering trigger grammar, NSID validation, label URI routing, and the `_actor` special case. - 8 integration tests in `tests/lua_record_api.rs` covering the Record local API + the no-PDS-auth boundary. - 18 e2e tests in `tests/e2e_scripts.rs` covering admin CRUD, trigger validation, the cascade rule, label-script + record-event Record local mutation, and the dead-letter behavior when a label script reaches for `r:save()`. - The 4 query-script lib tests in `src/lua/xrpc_api.rs` updated to seed scripts via the new table (plus a single-connection test pool so the in-memory sqlite is shared). Signed-off-by: Chris Pardy <chris.pardy@gmail.com>