Follow-up to #135 (P2.5-T09) — security-auditor LOW finding deferred to a fast-follow per the auditor's own verdict.
Issue
src/schemas/phases.ts:43 (post-firatcand/forge#135 patch) introduced status: z.string().optional() on TaskSchema with no enum constraint. The current live plans/phases.yaml uses values like 'deferred-v0.5', 'dropped', 'paused', and 'active' — none of which are validated. Garbage values will pass through unnoticed and surprise any future caller that switches on task.status.
Per [[widening-shared-regex-is-security-audit-trigger]]: unconstrained shared schemas are a class of latent bug we explicitly call out.
Scope
Define a typed enum that covers the values forge actually emits, and migrate TaskSchema.status to it:
export const TASK_STATUSES = [
'active',
'deferred-v0.5',
'dropped',
'paused',
] as const;
export const TaskSchema = z.object({
...
status: z.enum(TASK_STATUSES).optional(),
});
If the team adds new status values in the future (e.g. 'cancelled'), the enum must be extended in the same PR that introduces the value.
Acceptance
TASK_STATUSES const + matching TaskStatus type exported from src/schemas/phases.ts
TaskSchema.status uses z.enum(TASK_STATUSES).optional()
live-snapshot.yaml fixture still validates (i.e. every status value present in plans/phases.yaml is in the enum)
- Unit test:
TaskSchema rejects garbage status like 'foo'
- No production callers regress (none currently read
task.status — confirmed via grep before this ticket was filed)
Source
docs/learnings/2026-Q2/widening-shared-regex-is-security-audit-trigger.md and the #135 security audit report from 2026-05-17 PM.
Follow-up to #135 (P2.5-T09) — security-auditor LOW finding deferred to a fast-follow per the auditor's own verdict.
Issue
src/schemas/phases.ts:43(post-firatcand/forge#135 patch) introducedstatus: z.string().optional()onTaskSchemawith no enum constraint. The current liveplans/phases.yamluses values like'deferred-v0.5','dropped','paused', and'active'— none of which are validated. Garbage values will pass through unnoticed and surprise any future caller that switches ontask.status.Per
[[widening-shared-regex-is-security-audit-trigger]]: unconstrained shared schemas are a class of latent bug we explicitly call out.Scope
Define a typed enum that covers the values forge actually emits, and migrate
TaskSchema.statusto it:If the team adds new status values in the future (e.g.
'cancelled'), the enum must be extended in the same PR that introduces the value.Acceptance
TASK_STATUSESconst + matchingTaskStatustype exported fromsrc/schemas/phases.tsTaskSchema.statususesz.enum(TASK_STATUSES).optional()live-snapshot.yamlfixture still validates (i.e. every status value present inplans/phases.yamlis in the enum)TaskSchemarejects garbage status like'foo'task.status— confirmed via grep before this ticket was filed)Source
docs/learnings/2026-Q2/widening-shared-regex-is-security-audit-trigger.mdand the #135 security audit report from 2026-05-17 PM.