Investigate relaxing the step-name character set
- Dominant language
- Rust
- Stars
- 21
- Forks
- 1
- Avg merge
- 10h 30m
- Merged PRs (30d)
- 20
Description
## Context
`validate_name` (`crates/pgtask-core/src/identifier.rs:24-42`) applies one allowlist - ASCII alphanumerics plus `.`, `_`, `:`, `-` - to all five name types, and the DB re-checks it (`crates/pgtask-postgres/migrations/0001_initial.sql:199`: `step_name ~ '^[A-Za-z0-9._:-]+$'`).
That rule is well motivated for queue, task, and schedule names: they show up as CLI arguments, in the observer UI, and as OTel attributes (`pgtask.queue.name`, `pgtask.task.name`) that `docs/public-contracts.md` declares public and bounded.
Step names are different. They never leave the row - `step_name text` in the primary key `(task_id, handler_version, step_name, occurrence)`. They are not identifiers (the shared task table of ADR 0004 means no name is ever interpolated into SQL), they are not notify channels (`pgtask.result_channel` hashes the task id), and they are not metric attributes. They inherited the charset rule from the macro shared with the other name types.
## Why it matters
Step names are usually composed by a framework rather than typed by a person, so the composing library has to know pgtask's charset. Concretely: Pydantic AI names an agent's own toolset ``, so every agent-level tool under `pydantic-ai-pgtask` produced `...__function_toolset__.call_tool:x` and failed the task with `ValueError: step name contains unsupported character '<'` (Kludex/pydantic-ai-pgtask#1). Worse, the failure lands mid-task: the model request is already checkpointed, so every retry replays the cached response and dies at the same step, burning all `max_attempts`.
That integration now normalizes step names on its side, so this is not urgent - it is about whether the constraint should be there at all.
For comparison, absurd validates queue names only for what it structurally needs (non-empty, <= 57 bytes, because it derives `absurd.t_` table identifiers from them) and puts no rule at all on checkpoint names, which are plain text in a primary key. Its SDK even generates `name#2` for repeated steps.
## Questions to settle
- Does anything downstream actually depend on the step-name charset - the observer UI, CLI output, exports, log formatting, a future span attribute?
- If not, is relaxing `StepName` to non-empty + <= 255 bytes (leaving queue/task/schedule/signal names untouched) worth a storage-protocol bump? A changed CHECK predicate is a protocol increment per `docs/public-contracts.md`, so this is expand-and-contract, not a one-line patch.
- If the constraint stays, should the SDKs document the charset next to `task.step(...)` so integrators normalize up front instead of discovering it from a failed task?
Contributor guide
Research direction
Start with validate_name in crates/pgtask-core/src/identifier.rs and the step_name CHECK in crates/pgtask-postgres/migrations/0001_initial.sql. Read docs/public-contracts.md, then trace the named observer UI, CLI output, exports, logging, and span-attribute paths for downstream charset assumptions. Done means a documented decision on relaxing the constraint or retaining it, including the required protocol and SDK documentation implications.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, rust
- Domain
- backend-api-design, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 38/100