Current Limitation
ApplicationStatus is represented as a bare Go string everywhere it's touched — ApplicationRecord.Status and Application.Status in internal/application/store.go/service.go, and mirrored again in ConsignmentRecord.Status (internal/consignment/store.go). Nothing in code enforces the closed set; only docs/task-config-reference.md's "Application status lifecycle" section documents it as the target contract (PENDING, FEEDBACK_REQUESTED, APPROVED, REJECTED).
Concretely, ReviewApplication (internal/application/service.go) still defaults an unresolved review outcome to the literal "DONE" — a value outside the documented closed set — instead of failing closed. Status comparisons throughout the store layer (store.go) are raw string literals ("PENDING", "FEEDBACK_REQUESTED", ...) with no compiler-checked exhaustiveness, so a typo or a new ad-hoc status string would silently pass through.
Suggested Improvement
Introduce an iota-based ApplicationStatus type (in internal/application) with exactly the 4 canonical values (StatusPending, StatusFeedbackRequested, StatusApproved, StatusRejected), implementing String(), MarshalJSON/UnmarshalJSON, and Value()/Scan() so the JSON wire format and the varchar(50) DB column are unaffected — only the in-memory representation changes from string comparisons to a closed, typed enum.
Replace the raw string literals in store.go, service.go, and the mirrored status on consignment.Store with the typed constants. As part of this, retire the "DONE" fallback in ReviewApplication per the target contract already documented in docs/task-config-reference.md#application-status-lifecycle-canonical-applies-to-every-task — an outcome that doesn't resolve via statusMap/autoApprove should reject the review request (400) rather than silently storing a status outside the closed set.
Stretch goal (can be split further if needed): extend TaskConfig.Validate to reject behavior.statusMap values outside {APPROVED, REJECTED, FEEDBACK_REQUESTED} at config load time, closing the other half of the same target contract.
Version
28356cb (branch impr/244-improve-task-config-validation, PR #245)
Additional Context
Follow-up to #244 / PR #245, which tightened TaskConfig validation (forms.review/behavior required, Behavior converted to a value type) but deliberately left the ApplicationStatus representation and the statusMap value contract untouched to keep that PR scoped to config-shape validation. This issue tracks that remaining half.
Relevant files:
internal/application/store.go
internal/application/service.go
internal/consignment/store.go
docs/task-config-reference.md (canonical status lifecycle table)
Current Limitation
ApplicationStatusis represented as a bare Gostringeverywhere it's touched —ApplicationRecord.StatusandApplication.Statusininternal/application/store.go/service.go, and mirrored again inConsignmentRecord.Status(internal/consignment/store.go). Nothing in code enforces the closed set; onlydocs/task-config-reference.md's "Application status lifecycle" section documents it as the target contract (PENDING,FEEDBACK_REQUESTED,APPROVED,REJECTED).Concretely,
ReviewApplication(internal/application/service.go) still defaults an unresolved review outcome to the literal"DONE"— a value outside the documented closed set — instead of failing closed. Status comparisons throughout the store layer (store.go) are raw string literals ("PENDING","FEEDBACK_REQUESTED", ...) with no compiler-checked exhaustiveness, so a typo or a new ad-hoc status string would silently pass through.Suggested Improvement
Introduce an iota-based
ApplicationStatustype (ininternal/application) with exactly the 4 canonical values (StatusPending,StatusFeedbackRequested,StatusApproved,StatusRejected), implementingString(),MarshalJSON/UnmarshalJSON, andValue()/Scan()so the JSON wire format and thevarchar(50)DB column are unaffected — only the in-memory representation changes from string comparisons to a closed, typed enum.Replace the raw string literals in
store.go,service.go, and the mirrored status onconsignment.Storewith the typed constants. As part of this, retire the"DONE"fallback inReviewApplicationper the target contract already documented indocs/task-config-reference.md#application-status-lifecycle-canonical-applies-to-every-task— an outcome that doesn't resolve viastatusMap/autoApproveshould reject the review request (400) rather than silently storing a status outside the closed set.Stretch goal (can be split further if needed): extend
TaskConfig.Validateto rejectbehavior.statusMapvalues outside{APPROVED, REJECTED, FEEDBACK_REQUESTED}at config load time, closing the other half of the same target contract.Version
28356cb (branch
impr/244-improve-task-config-validation, PR #245)Additional Context
Follow-up to #244 / PR #245, which tightened
TaskConfigvalidation (forms.review/behaviorrequired,Behaviorconverted to a value type) but deliberately left theApplicationStatusrepresentation and thestatusMapvalue contract untouched to keep that PR scoped to config-shape validation. This issue tracks that remaining half.Relevant files:
internal/application/store.gointernal/application/service.gointernal/consignment/store.godocs/task-config-reference.md(canonical status lifecycle table)