fix(gate-25): the request-NAME arm matched a JSON syntax the extraction removed (#430) - #431
Merged
Conversation
…on removed (#430) #425 rebuilt gate-25's Newman haystack from the raw bytes of each collection into a newline-joined list of extracted VALUES. That correctly stopped a Postman `description` standing in for a request. It did not update `is_covered`, whose second Newman arm still matches JSON key syntax — re.search(rf'"name"\s*:\s*"[^"]*\b{method}\b', newman) — against a haystack that is no longer JSON. The arm stopped seeing anything a collection declares. Confirmed rather than read, on docudesk's own collections: AFTER name-arm fires for method 'versions'? False BEFORE name-arm fires for method 'versions'? True MEASURED, eighteen core apps, identical trees, only the package moving (fa555a2 vs a316aa5, `bin/hydra-gates --full`): gate-25 went 239 -> 280, nine apps PASS -> FAIL, and 36 of the 41 new findings had been carried by this arm and nothing else. 25 of the 41 could not be closed by any correct app change. Three causes, each with its own arm and its own control: 1. THE NAME ARM. Both arms now ask their question of the field that answers it: `_newman_evidence` tags every extracted value `<field>:<value>`, the name arm reads `^name:` lines, the url arm reads url-bearing lines. Its control is a collection where the word appears in a url and in a payload and in no request name — still a finding. 2. `_url_signature` DELETED PLACEHOLDERS AND JOINED THE SURVIVORS. `/api/pos-transactions/{id}/confirm` became `api/pos-transactions/confirm`, a string no correct url can contain. 16 endpoints were reported uncovered while the app's own collection held a request for exactly that route. Now a regex with placeholders as wildcard segments. Its control is a sibling operation on the same id — `…/{id}/confirm` covers `confirm` and leaves `cancel` reported, which "truncate at the first placeholder" cannot do. 3. `_ROUTE_ENTRY_RE` COULD NOT READ A NESTED ARRAY. `\[[^\[\]]*?\]` fails on every route declaring `'requirements' => [...]`, so 9 endpoints reached `is_covered` with an EMPTY url — an arm nothing can satisfy. The entry is now recovered by balancing brackets over a string-masked copy, so a bracket inside a requirement regex is not counted as structure. TWO TIGHTENINGS COME WITH IT, BOTH MEASURED, NEITHER OPTIONAL: - A trailing placeholder is now a REQUIRED segment. Keeping it optional was unshippable next to (3): four openregister SPA page routes acquired a url for the first time and were immediately answered by `…/api/registers/…`. A parser repair that converts findings into silence is the wrong trade whichever way the count moves. - The left edge is anchored on the app base (`/apps/<id>`, a `}`, a quote, or the start of the path). `/registers/{id}` and `/api/registers/{id}` are different endpoints and the second contains the first. And one correctness fix with zero fleet effect, pinned so it stays: a route name registered under several urls (a `'postfix'` entry — six of the eighteen apps do it, 13 times in zaakafhandelapp) is covered by a test for ANY of them, not for whichever entry the parser happened to keep last. RE-MEASURED, same protocol, third column: before(fa555a2) after(a316aa5) now 239 280 251 vs `before`: 13 findings appear, 1 disappears. The one that disappears is shillinq `periodClose#aiFlags` — a medial-placeholder false positive that pre-dates #425 and is answered by `…/api/period-close/{{period_id}}/ai-flags`. All 13 that appear were verified by hand to have no request, no matching request name and no PHPUnit call: softwarecatalog's four `settings#get*Groups` (a docblock route table was the only mention), openregister `ui#reports`, `ui#configurations`, `ui#endpoints`, `ui#entities`, `ui#tables`, zaakafhandelapp `users#me` and `resultaten#pages`, doriath `publicShell#page`, openconnector `synchronizations#deleteLog`. Acceptance is the two measured lists, not the count: all 16 false positives gone, all 5 true positives still firing. The suite goes 8 -> 21 tests. Reverting check_contract_coverage.py flips 10 of them and leaves 11 green, so each repair has an arm that fails without it and a control that does not.
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.
Closes #430.
d33cd36(#425) rebuilt gate-25's Newman haystack from the raw bytes of each collection into a newline-joined list of extracted values. That correctly stopped a Postmandescriptionstanding in for a request. It did not updateis_covered, whose second Newman arm still matches JSON key syntax against a haystack that is no longer JSON:Confirmed rather than read, on docudesk's own collections:
What it cost — measured, eighteen core apps, identical trees
fa555a2vsa316aa5,bin/hydra-gates --full, only the package moving:Three causes, three arms, three controls
1. The name arm.
_newman_evidencenow tags every extracted value<field>:<value>. The name arm reads^name:lines, the url arm reads url-bearing lines — so the discriminator_newman_paths' own docstring claims ("which string, not whether it is one") is actually applied. Control: a collection where the method word appears in a url and in a request body and in no request name is still a finding.2.
_url_signaturedeleted placeholders and joined the survivors.Not a path — no correct collection url can contain it. 16 endpoints were reported "missing a contract test" while the app's own collection held a request for exactly that route. Now a regex with placeholders as wildcard segments. Control:
…/{id}/confirmcoversconfirmand leavescancelreported — which "truncate at the first placeholder" cannot do, and pipelinq has five POS operations on one id.3.
_ROUTE_ENTRY_REcould not read a nested array.\[[^\[\]]*?\]is a bracket pair that forbids brackets, so every route declaring'requirements' => [...]fell through to the name-only sweep and was recorded with an empty url — an arm nothing can satisfy. 9 endpoints across the fleet. The url was in the file all along:Recovered by balancing brackets over a
php_masked copy, so a bracket inside a requirement regex is not counted as structure. Control: a'requirements'key literally namedurlmust not be read as the route's path.Two tightenings came with it — measured, and neither is optional
_url_signaturewas) is unshippable next to cause 3: four openregister SPA page routes acquired a parsed url for the first time and were immediately answered by…/api/registers/…. A parser repair that converts findings into silence is the wrong trade whichever way the count moves. Fleet cost of this arm alone: one endpoint, zaakafhandelappusers#me, previously "covered" because the two lettersmeoccur somewhere in the collection./apps/<id>, after a}({{baseUrl}}/{{app}}/api/…, zaakafhandelapp), after a quote, or at the start of the path./registers/{id}and/api/registers/{id}are different endpoints and the second contains the first.Plus one correctness fix with zero fleet effect, pinned so it stays: a route name registered under several urls (a
'postfix'entry — six of the eighteen apps do it, 13 times in zaakafhandelapp) is covered by a test for any of them, not for whichever entry the parser kept last.Re-measured — same protocol, third column
fa555a2a316aa5Every app measured on the same commit in all three columns (
out/<app>/head.sha==head.now.sha, checked; no drift).vs
before: 13 findings appear, 1 disappears.The one that disappears is shillinq
periodClose#aiFlags— a medial-placeholder false positive that pre-dates #425, answered by…/api/period-close/{{period_id_open…}}/ai-flags.The 13 that appear were each read by hand and have no request, no matching request name and no PHPUnit call: softwarecatalog's four
settings#get*UserGroups(the only mention intests/is a docblock route table atSettingsControllerUserGroupsConfigAuthTest.php:47; the body testsgetUserGroupsConfig()), openregisterui#reports/ui#configurations/ui#endpoints/ui#entities/ui#tables, zaakafhandelappusers#meandresultaten#pages, doriathpublicShell#page, openconnectorsynchronizations#deleteLog.Acceptance is the two measured lists, not the count
Tests
test_check_contract_coverage.pygoes 8 → 21. Revertingcheck_contract_coverage.pyand re-running flips 10 and leaves 11 green, so every repair has an arm that fails without it and a control that does not:Also verified:
gate-acceptance/contract-coveragefixture still holds throughbin/hydra-gates: planted FAIL namingthing#farAttribute, clean PASS;scripts/lib/test_*.pysuites give byte-identical results toa316aa5;test_an_unparseable_collection_falls_back_to_raw_textwrote/api/thingsfor a/api/things/{id}route, which only worked while trailing placeholders were dropped. It now writes the url the route has, so the arm tests the fallback rather than the leniency.What I could not verify
bin/hydra-gates --fullrun locally (python 3.8.10, node v22.22.0). No GitHub Actionspull_requestrun was made, and after fix(gates 19, 25, 26, 51, 52, 54, 55): the scope decision has exactly one source (#416) #418 a local full run and a CI run are not interchangeable claims.->method(arm. An endpoint exercised through a differently-named service call, or by an e2e spec, would still read as uncovered here./registers/{id}still matches…/api/registers/7when the app id cannot be read fromappinfo/info.xml; three of the four anchors still apply and the pattern is then no looser than the one it replaces.Two unrelated pre-existing defects found while building the controls are filed separately rather than folded in: #428 (gate-48's
src/**/*.jspathspec cannot see a file directly undersrc/) and #429 (gate-50's guard window crosses method boundaries).🤖 Generated with Claude Code