Conversation
….21.1) Two fixes from Cursor Bugbot findings on PR #120 / #121 that should have blocked the 0.21.0 ship and didn't because the orchestrator treated Bugbot's in_progress state as non-blocking. The findings are real and the medium-severity one is a prod regression vs pre-refactor behavior. Bug #1 (medium) — Stop-word filtering silently drops canon vocab keywords: - parseCheckColumn called tokenize(m[1]) with default STOP_WORDS filter. Canon vocab includes 'from' (in source-named) and 'to' (inside 'according to'); STOP_WORDS includes both. Result: 'from' is dropped from stemmedTokens; the runtime call site applied the same filter to inputStems so 'from' is dropped there too. - Pre-refactor regex evaluator matched 'from' literally as new RegExp('\\bfrom\\b', 'i') against raw input. Inputs like 'I learned this from my colleague' passed source-named pre-refactor and fail post-refactor — the strictly-additive invariant the 0.21.0 CHANGELOG and PR description claimed is broken. - Latent landmine: any prereq whose canon vocab is entirely stop-words would have stemmedTokens.size === 0 and trigger the conservative length>=20 fallback inappropriately (false positive). Not currently exploited by canon but the structural risk is there. - Fix: pass new Set() (empty stop-words) to both tokenize() calls so canon keywords survive on both sides and shape matches. Bug #2 (low) — DRY violation in BasePrerequisite: - 0.21.0 introduced PrereqMatchVocab interface to share shape between BasePrerequisite and ChallengeTypeDef.prerequisiteOverlays[]. The inline type on prerequisiteOverlays uses '& PrereqMatchVocab' intersection; BasePrerequisite re-listed all five fields manually. Future PrereqMatchVocab additions would not propagate to BasePrerequisite — defeats the DRY purpose. - Fix: split into BasePrerequisiteCore (3 core fields) and type BasePrerequisite = BasePrerequisiteCore & PrereqMatchVocab. Verification: - Typecheck clean - governance-parser.test.mjs 105/105 pass - 2 new regression assertions in canon-tool-envelope.smoke.mjs: (10) source-named via 'from'-only canon keyword (11) source-named via 'according to' multi-word phrase - Bugbot Autofix preview diff for PR #121 used as the basis for the Bug #1 fix (cherry-picked the change locally rather than applying via Autofix UI to keep the audit trail in this branch). Process post-mortem to follow in P1.3.3 closeout ledger.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
oddkit | 9d09fe4 | Commit Preview URL Branch Preview URL |
Apr 20 2026, 04:38 AM |
Release Validation Gate — Rule 1 DispositionPer
All reviews completed before merge. Rule 1 satisfied. Sonnet 4.6 read-only validator session Rule 2 (independent validator dispatch before promotion) is satisfied via the same session — the validator was explicitly tasked with reviewing both this PR and the upcoming promotion. |
Fix-forward for two Cursor Bugbot findings the P1.3.3 orchestrator shipped past on 0.21.0.
The orchestrator merged PR #120 and PR #121 while Bugbot was still
in_progress, treating it as non-blocking. Bugbot subsequently posted two findings on commit400eb57b:from(insource-namedvocab) is a stop word inSTOP_WORDS; defaulttokenize()strips it from bothstemmedTokensandinputStems. Pre-refactor\bfrom\bregex matched literally; post-refactor it doesn't match at all. Strictly-additive invariant broken in prod.BasePrerequisitere-declared the 5PrereqMatchVocabfields manually instead of& PrereqMatchVocabintersection — defeats the DRY purpose of introducing the shared interface.Both fixes verified live in prod 0.20.0 (the bug exists), prod 0.21.0 (still exists), and 0.21.1 preview (fixed).
What changed
Bug #1 — Stop-word filter disabled on canon vocab and input
File:
workers/src/orchestrate.tsparseCheckColumn:tokenize(m[1])→tokenize(m[1], new Set()). Canon vocab includesfrom(source-named),to(insideaccording to), and any other stop-word that appears in quoted vocab survives. Doc comment updated to explain why.runChallengeAction:tokenize(input)→tokenize(input, new Set()). Same empty stop-word set so input and vocab share shape. Doc comment updated.This restores the strictly-additive invariant that 0.21.0's CHANGELOG and PR description claimed but did not actually deliver.
Bug #2 —
BasePrerequisitecollapsed to intersectionFile:
workers/src/orchestrate.tsFuture field additions to
PrereqMatchVocabnow propagate automatically.New regression smoke assertions
Added 2 assertions to
workers/test/canon-tool-envelope.smoke.mjs:from-only source attribution:"I learned this morning that the deploy regressed from my colleague Alex Brown — observed during last night's incident review."must NOT have "no source named" inmissing_prerequisites. This is the regression anchor.according tomulti-word phrase:"We saw a 30% latency regression according to the Tuesday measurements I observed in the dashboard."must NOT have "no source named". Documents the multi-word case where stop-word filtering would have dropped half the phrase.Verification
from-only input fails source-named ✓ (bug exists)from-only input passes source-named ✓ (bug fixed)Process change context
This PR is also the first application of
klappy://canon/constraints/release-validation-gate(currently in PRklappy/klappy.dev#126, open at time of this PR's open).Per the new canon constraint:
completed.The full P1.3.3 process post-mortem and the structural fix (canon docs
release-validation-gate+contract-governs-handoff-drift+ bootstrap hook) are inklappy/klappy.dev#126.Refs
Note
Medium Risk
Changes
oddkit_challengeprerequisite tokenization to disable stop-word filtering, which can broaden or alter match behavior for user inputs and affect gating/messaging. Coverage is improved via new smoke-test regressions, but it still touches core evaluator logic.Overview
Restores the strictly-additive prerequisite-matching behavior in
oddkit_challengeby disabling stop-word filtering for both parse-time prereq vocab (parseCheckColumn) and runtime input stems, ensuring canon keywords likefromare not silently dropped.Refactors the shared prereq types so
BasePrerequisiteis defined asBasePrerequisiteCore & PrereqMatchVocabto keep the shared match-vocabulary shape DRY.Adds smoke-test regressions covering stop-word keyword matching (
from-only source attribution) and a multi-word phrase case (according to), and bumps versions/docs for the0.21.1release.Reviewed by Cursor Bugbot for commit 9d09fe4. Bugbot is set up for automated code reviews on this repo. Configure here.