Worker completion contracts¶
Worker completion payloads used to be free-form prose in result_summary, and a worker that could not proceed had no structured way to say so. The completion contract defines the two closed terminal shapes a worker may submit at the completion API boundary: a completion or a typed refusal. An invalid payload is a typed contract_violation failure, never a silent accept.
Source:
src/bernstein/core/tasks/contracts.py- the contract and validatorssrc/bernstein/core/routes/task_crud.py- completion-API enforcement
The contract is versioned (worker-completion/v1); the version and the validation outcome are recorded in the task's ledger and audit trail for every validated payload.
The two shapes¶
Completion¶
A successful terminal payload:
{
"contract": "worker-completion/v1",
"summary": "Added JWT refresh-token rotation and tests.",
"files_changed": ["src/auth/jwt.py", "tests/test_jwt.py"],
"verification": {"command": "pytest tests/test_jwt.py", "exit_code": 0},
"receipt_ref": "lineage:task-9f3a"
}
| Field | Required | Notes |
|---|---|---|
summary | yes | Non-empty string, capped at 2000 characters. |
files_changed | no | List of non-empty path strings. |
verification | no | {command, exit_code} - a step the worker ran before completing. |
receipt_ref | no | Optional lineage/receipt reference. |
Refusal¶
A worker that cannot proceed submits a typed refusal instead. The kind is a closed taxonomy - there is deliberately no catch-all - so the orchestrator can route every refusal deterministically:
kind | Required payload field | Meaning |
|---|---|---|
awaiting_operator | question | Needs a human decision before continuing. |
underspecified | question | The task is too vague to act on. |
scope_exceeded | proposed_split (non-empty list) | Too large; proposes a split. |
blocked_on_dependency | blocking_dep | Blocked on another task/resource. |
{
"contract": "worker-completion/v1",
"kind": "scope_exceeded",
"detail": "Task spans three subsystems; splitting.",
"proposed_split": ["Migrate the auth module", "Migrate the billing module"]
}
Validation is strict¶
At the completion API boundary the payload is validated against the closed schema:
- Unknown top-level fields are rejected.
- Unknown refusal kinds are rejected.
- Missing required fields (per shape and per refusal kind) are rejected.
- Malformed JSON is rejected.
Every violation carries a JSONPath-style path (for example $.summary, $.proposed_split[1]) so the failure is diagnosable from the ledger alone.
What happens on a violation¶
A payload that fails validation auto-fails the task with terminal_reason='contract_violation', releasing the worker slot atomically. The completion API returns 422 with the schema error path (or 409 if the task is already terminal). The violation path is recorded in the audit trail.
Legacy prose is still accepted¶
Backward compatibility is preserved: a result_summary that is plain prose (does not open as a JSON object) is accepted unchanged. Only an explicit payload object, or a result_summary that is itself a JSON object, is validated against the contract. A truncated JSON document is a contract violation rather than a silently accepted prose summary.
Deterministic follow-up derivation¶
A scope_exceeded refusal is routed by deriving follow-up task specs from its proposed_split. The derivation is a pure function of (parent_task_id, contract version, split index, split text): follow-up task ids are content-addressed SHA-256 prefixes (fu-<hash>), so the same refusal payload always produces the same follow-up set. A re-delivered refusal cannot duplicate the split.