prefect server in zig

add playbooks for orchestration rules and schema updates

- .claude/commands/implement-orchestration-rule.md
- .claude/commands/update-database-schema.md
- README.md: add contributing section with playbook links

๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+172
+70
.claude/commands/implement-orchestration-rule.md
··· 1 + # implement orchestration rule 2 + 3 + add a new rule for flow run state transitions. 4 + 5 + ## clarify first 6 + 7 + 1. **from_states / to_states**: which transitions trigger this rule? 8 + 2. **action**: reject, wait, abort, or modify context fields? 9 + 3. **side effects**: any database updates beyond state? 10 + 11 + ## steps 12 + 13 + ### 1. define rule in `src/orchestration/flow_rules.zig` 14 + 15 + ```zig 16 + pub const MyRule = OrchestrationRule{ 17 + .name = "MyRule", 18 + .from_states = StateTypeSet.init(&.{ .PENDING, .RUNNING }), 19 + .to_states = StateTypeSet.init(&.{.FAILED}), 20 + .before_transition = myRuleFn, 21 + }; 22 + 23 + fn myRuleFn(ctx: *RuleContext) void { 24 + // ctx.reject("reason"), ctx.wait("reason", seconds), or modify ctx fields 25 + } 26 + ``` 27 + 28 + ### 2. add to `CoreFlowPolicy` array (same file) 29 + 30 + ```zig 31 + pub const CoreFlowPolicy = [_]OrchestrationRule{ 32 + PreventPendingTransitions, 33 + CopyScheduledTime, 34 + WaitForScheduledTime, 35 + RetryFailedFlows, 36 + MyRule, // <-- order matters: rules run sequentially 37 + }; 38 + ``` 39 + 40 + ### 3. add tests (same file, bottom) 41 + 42 + ```zig 43 + test "MyRule rejects X โ†’ Y" { 44 + var ctx = RuleContext{ 45 + .initial_state = .PENDING, 46 + .proposed_state = .FAILED, 47 + .proposed_state_timestamp = "2025-01-01T00:00:00Z", 48 + .run_id = "test", 49 + }; 50 + MyRule.before_transition(&ctx); 51 + try std.testing.expectEqual(ResponseStatus.REJECT, ctx.result.status); 52 + } 53 + ``` 54 + 55 + ## context reference 56 + 57 + **fields**: `initial_state`, `proposed_state`, `run_id`, `run_count`, `retries`, `retry_delay`, `initial_scheduled_time` 58 + 59 + **methods**: `reject(reason)`, `wait(reason, seconds)`, `abort(reason)`, `scheduleRetry(reason, time)` 60 + 61 + ## if you need new context fields 62 + 63 + 1. add to `RuleContext` in `src/orchestration/rules.zig` 64 + 2. populate in `src/api/flow_runs.zig` (`setFlowRunState` function) 65 + 66 + ## verify 67 + 68 + ```bash 69 + zig build test && just test 70 + ```
+69
.claude/commands/update-database-schema.md
··· 1 + # update database schema 2 + 3 + add or modify tables/columns via the migration system. 4 + 5 + ## steps 6 + 7 + ### 1. create migration files 8 + 9 + ```bash 10 + mkdir -p src/db/migrations/002_add_feature/ 11 + ``` 12 + 13 + **sqlite.sql**: 14 + ```sql 15 + ALTER TABLE flow_run ADD COLUMN new_field TEXT DEFAULT ''; 16 + CREATE INDEX IF NOT EXISTS ix_flow_run__new_field ON flow_run(new_field); 17 + ``` 18 + 19 + **postgres.sql**: 20 + ```sql 21 + ALTER TABLE flow_run ADD COLUMN IF NOT EXISTS new_field TEXT DEFAULT ''; 22 + CREATE INDEX IF NOT EXISTS ix_flow_run__new_field ON flow_run(new_field); 23 + ``` 24 + 25 + ### 2. register in `src/db/migrations_data.zig` 26 + 27 + ```zig 28 + pub const all = [_]Migration{ 29 + .{ .id = "001_initial", ... }, 30 + .{ 31 + .id = "002_add_feature", 32 + .sqlite_sql = @embedFile("migrations/002_add_feature/sqlite.sql"), 33 + .postgres_sql = @embedFile("migrations/002_add_feature/postgres.sql"), 34 + }, 35 + }; 36 + ``` 37 + 38 + ### 3. test locally 39 + 40 + ```bash 41 + zig build && rm -f prefect.db && just dev 42 + sqlite3 prefect.db "SELECT * FROM _migrations;" 43 + ``` 44 + 45 + ### 4. test on k8s (postgres) 46 + 47 + ```bash 48 + docker build --platform linux/arm64 -t atcr.io/zzstoatzz.io/prefect-server:latest . 49 + docker push atcr.io/zzstoatzz.io/prefect-server:latest 50 + kubectl rollout restart deployment prefect-api prefect-services -n prefect 51 + kubectl exec -n prefect deployment/postgres -- psql -U prefect -d prefect -c "SELECT * FROM _migrations;" 52 + ``` 53 + 54 + ## dialect differences 55 + 56 + | operation | sqlite | postgres | 57 + |-----------|--------|----------| 58 + | add column | `ADD COLUMN c` | `ADD COLUMN IF NOT EXISTS c` | 59 + | json | `TEXT` | `JSONB` | 60 + | integer | `INTEGER` | `BIGINT` | 61 + | timestamp default | `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')` | `TO_CHAR(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')` | 62 + 63 + ## rollback (manual) 64 + 65 + no automatic rollbacks. to undo: 66 + 67 + 1. apply reverse SQL via `sqlite3` or `kubectl exec ... psql` 68 + 2. `DELETE FROM _migrations WHERE id = '002_add_feature'` 69 + 3. remove from `migrations_data.zig`, rebuild
+33
README.md
··· 45 45 ## configuration 46 46 47 47 see [`docs/`](docs/) for configuration details. 48 + 49 + <details> 50 + <summary>contributing</summary> 51 + 52 + ### playbooks 53 + 54 + - [implement orchestration rule](.claude/commands/implement-orchestration-rule.md) - add flow run state transition rules 55 + - [update database schema](.claude/commands/update-database-schema.md) - add migrations for schema changes 56 + 57 + ### architecture 58 + 59 + ``` 60 + src/ 61 + โ”œโ”€โ”€ api/ # http handlers (flow_runs.zig, deployments.zig, etc.) 62 + โ”œโ”€โ”€ db/ # database layer (backend.zig, migrations/, entity modules) 63 + โ”œโ”€โ”€ orchestration/ # state transition rules and transforms 64 + โ”œโ”€โ”€ broker/ # message broker (memory + redis backends) 65 + โ”œโ”€โ”€ services/ # background workers (scheduler, event_persister) 66 + โ””โ”€โ”€ main.zig 67 + ``` 68 + 69 + ### testing 70 + 71 + - unit tests: `zig build test` 72 + - functional tests: `just test` (requires running server) 73 + - both: `zig build test && just test` 74 + 75 + ### reference 76 + 77 + - python prefect source: `~/github.com/prefecthq/prefect` 78 + - zig 0.15 notes: `~/tangled.sh/@zzstoatzz.io/notes/languages/ziglang/0.15/` 79 + 80 + </details>