Audit applications.status against the union, not just the typed module - #324
Merged
Merged
Conversation
#323 closed the write path — the admin form is a select over PROJECT_STATUSES and both registry endpoints 400 on anything outside the union. What neither it nor verify:portfolio covers is the rows already in the table: the verifier reads lib/portfolio.ts, the typed seed source, so a status that predates the constraint (or arrives by a hand-run UPDATE) is invisible to it while publicStageFromStatus() files it under "exploring" and /portfolio renders it as Exploring. #323 checked remote dev and prod by hand and found them clean; this is the check that means nobody has to do that again. verify:portfolio now reads applications.status directly and errors on any value outside the union. It needs a live database, so it runs only when DATABASE_URL is set — CI has none and says it skipped rather than implying it passed. VerificationProblem gains an optional observedStatus so the report can name the offending value; claimedStatus is pinned to ProjectStatus, which is exactly what a bad row doesn't have. Also corrects two API-reference param descriptions that #323's 400 outdated, and points the admin guide's status InfoBox at the audit as the way to find pre-taxonomy rows in bulk. Verified against a local database: clean at 29 rows; a seeded `Planned` row is reported as an error naming "Planned", and clears when the row goes. build, lint, and verify:portfolio pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ProfessorPolymorphic
force-pushed
the
fix/registry-status-taxonomy
branch
from
July 28, 2026 17:55
5797b48 to
f13b859
Compare
Vendored strategic-plan submodule is behind upstreamThe vendored submodule pointer at The submodule is pinned at upstream This is an advisory comment from |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rescoped. This PR originally fixed the stale
STATUS_OPTIONSlist — #323 landed that fix first, so everything duplicated has been dropped. What's left is the one part #323 doesn't cover.The remaining gap
#323 closed the write path: the admin form is a
selectoverPROJECT_STATUSES, and both registry endpoints400on anything outside the union. It did not close the read path — the rows already in the table.npm run verify:portfolioreadslib/portfolio.ts, the typed seed source.applications.statusis what/portfolioactually renders from, it's plainTEXTwith no CHECK (deliberately — ADR 0001 makes a new state a re-seed rather than a migration), andpublicStageFromStatus()buckets anything unrecognised asexploring. So a status that predates the constraint, or arrives via a hand-runUPDATE, renders as Exploring and no check sees it. #323 verified remote dev and prod by hand and found them clean; this is the check that means nobody has to do that again.What changed
verify:portfolioreadsapplications.statusand errors on any value outside the union. It needs a live database, so it runs only whenDATABASE_URLis set — CI has none and prints that it skipped, rather than implying it passed. Run it against dev or prod.VerificationProblemgains an optionalobservedStatus, so the report names the offending value.claimedStatusis pinned toProjectStatus— which is precisely what a bad row doesn't have./docs/api-referenceparam descriptions that Make the ADR 0001 ladder the only lifecycle vocabulary #323's400outdated (the PATCH one still said out-of-union values "write successfully"; the POST one didn't mention the constraint at all).Verification
Against a local database: clean at 29 rows. A seeded
Plannedrow is reported as[ERROR] stratplan (Planned)naming the value and the fix, and clears when the row goes.npm run build,npm run lint, andnpm run verify:portfolioall pass.🤖 Generated with Claude Code