Make iids-portfolio vocabulary parity a CI error - #319
Merged
Conversation
The `scoping` drift closed in #318 should never have been findable only by hand. ADR 0001's amendment named the upstream registration as a follow-up, the follow-up was missed, a project shipped claiming the status, and every check stayed green for three days. My first instinct was to add lib/portfolio.ts to the Governance Drift workflow's path filter. That would not have worked: the drift script validates the vendored registry's internal consistency and its remote repo canaries, and has no knowledge of this repo's TypeScript unions. Running it on a portfolio change would have been a no-op that looked like a fix. The check belongs in verify:portfolio, which already runs on every PR and can see both sides. verifyGovernanceVocabularyParity() asserts that the ProjectStatus and PublicStage unions in lib/portfolio.ts and the registered iids-portfolio vocabulary contain exactly the same codes. It reads the unions from the exhaustive OPERATIONAL_LABEL and PUBLIC_STAGE_LABEL records, so tsc guarantees there is no third list to keep in sync, and a missing vocabulary group is itself an error rather than a silently-skipped check. Both directions fail. A code in the union but not the vocabulary means the registry misdescribes what statuses exist; a code in the vocabulary but not the union means one was retired without deregistering it, and the Data Model explorer shows stakeholders a state the site can no longer produce. Each message names the fix, including the two-repo order. Verified by reproducing the original drift — removing `scoping` from the generated vocabulary fails with the intended error — and the reverse, by adding a code the union lacks. Baseline still passes: 29 projects, 0 errors, 2 pre-existing stale-commit-date warnings. ADR 0001 gains an amendment closing both vocabulary follow-ups and recording the consequence for future ones: adding an operational status is now a two-repo change in a fixed order, and an amendment that defers something should say what will fail if it is forgotten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
The
scopingdrift closed in #318 should never have been findable only by hand. ADR 0001's amendment named the upstream registration as a follow-up, the follow-up was missed, a project shipped claiming the status, and every check stayed green for three days.My first suggestion was wrong
I proposed adding
lib/portfolio.tsto the Governance Drift workflow's path filter. Checking before building showed that wouldn't work:check_governance_drift.pyvalidates the vendored registry's internal consistency and its remote repo canaries. It has no knowledge of this repo's TypeScript unions. Running it on a portfolio change would have been a no-op that looked like a fix — arguably worse than nothing, since it would have closed the item.What actually closes it
verifyGovernanceVocabularyParity()inscripts/verify-portfolio.ts— theverify:portfoliojob already runs on every PR and can see both sides.It asserts that the
ProjectStatusandPublicStageunions inlib/portfolio.tsand the registerediids-portfoliovocabulary contain exactly the same codes. The unions are read from the exhaustiveOPERATIONAL_LABEL/PUBLIC_STAGE_LABELrecords, so tsc guarantees there's no third list to maintain. A missing vocabulary group is itself an error rather than a silently-skipped check.Both directions fail:
scopingcaseEach message names the fix, including the two-repo order.
Verified against real drift, not just "it passes"
Reproduced the original failure by removing
scopingfrom the generated vocabulary:And the reverse, by adding a code the union lacks:
Baseline unchanged: 29 projects, 0 errors, 2 pre-existing stale-commit-date warnings.
npm run buildandnpm run lintpass.Docs
ADR 0001 gains an amendment closing both vocabulary follow-ups (
pausedwas done in data-governance#15;scopingin #17) and recording the consequence: adding an operational status is now a two-repo change in a fixed order — vocabulary upstream, submodule bump,build:governance, then the union. It also notes that a "Follow-up" line whose only enforcement is someone remembering it isn't enforcement, which is the lesson thescopinggap actually taught.Follows #318.
🤖 Generated with Claude Code