Job Schema & Audit Metadata
so1 intermediate 5 min read
ELI5
A job is a delivery slip: the parcel itself (output) plus a stamped audit slip (metadata) that says who shipped it (userId), from which depot (orgId), the manifest number (requestId), what was inside (inputs, with sensitive items blacked out), and a running courier note (logs).
Technical Deep Dive
Zod Schema
Defined in so1-shared/src/jobs.ts:
classDiagram class Job { +id : string (uuid) +state : JobState +createdAt : iso datetime +startedAt? : iso datetime +completedAt? : iso datetime +output? : unknown +error? : ErrorDetail +logs : string[] (default []) +metadata : JobMetadata } class JobMetadata { +requestId : string (uuid) +userId : string +orgId : string +action : string +inputs : unknown } class ErrorDetail { +code : string +message : string } class JobStatus { +requestId : string (uuid) +job : Job } Job --> JobMetadata Job --> ErrorDetail JobStatus --> JobField Notes
| Field | Constraint | Source |
|---|---|---|
id | z.string().uuid() | server-generated on create |
state | z.nativeEnum(JobState) | see so1-006 |
logs | z.array(z.string()).default([]) | append-only |
metadata.requestId | z.string().uuid() | matches the inbound request’s id (so1-012) |
metadata.userId | z.string() | from auth context |
metadata.orgId | z.string() | from auth context |
metadata.inputs | z.unknown() | passed through redactInputs (ADR-003) before storage |
Redaction Contract
Per ADR-003, before any inputs reach durable storage:
function redactInputs(action: string, inputs: unknown): unknown { if (action === "trigger-github-workflow") { return { repo: inputs.repo, branch: inputs.branch }; // pat/token omitted } return inputs;}The list of redactable fields is action-specific and lives next to the action handler.
JobStatus vs Job
JobStatus wraps a Job with the top-level requestId of the current status request (not the original create request) so each GET /api/jobs/{id} call is independently traceable.
Key Terms
- Audit metadata →
who/what/when/whyfields preserved beyond the job’s lifetime. - Redaction → removing or masking fields whose values must not be persisted (tokens, keys).
- append-only logs → log array can only grow; entries are never edited or removed.
Q&A
Q: Why is inputs typed unknown rather than per-action union?
A: Job actions are open-ended; per-action input schemas live with each action handler, which validates before redaction. The Job model stays generic.
Q: Are startedAt and completedAt ever both unset?
A: Yes — a job in pending has neither.
Q: Is logs part of the create response?
A: It defaults to [] — present but empty until the worker emits lines.
Examples
A successful workflow trigger ends with { state: "success", startedAt: "2026-03-01T10:00:00Z", completedAt: "2026-03-01T10:00:42Z", output: { runId: 7700123 }, metadata: { action: "trigger-github-workflow", inputs: { repo: "org/x", branch: "main" } } } — the PAT used to call GitHub never appears.
neighbors on the map
- Operations & Versions Schema writing a new sync query
- Audit Checkpoint Fleet Partition adding a new audit checkpoint definition
- EventEnvelope Wire Wrapper publishing a new domain event proto