fix: get develop green — proposal drift, tsdoc, and the per-service base URL bug - #10
Merged
Merged
Conversation
`lint:proposals` reported 19 violations, in two groups. Sixteen were `done/` proposals whose directory did not match their own `kind` frontmatter — fifteen `x000xx` files sitting in `done/fixes/` while declaring themselves chores, feats, refactors or tests. Moved to the directory each one names. Three were the same proposal existing in several lifecycle states at once: `f00016` in `done/feats/`, `in-progress/` AND `review/`, and `f00017` in both `in-progress/` and `review/`. That is debris from a lifecycle transition that COPIED instead of moving, swept into the repository by an auto-commit (51b9571, "chore: update …", 390/241/399 insertions — all new files, nothing removed). It is the same accident class this migration exists to make impossible. Diffing the bodies decided which copy survives; they are identical apart from frontmatter: - `f00016` keeps `done/feats/`. It is the only copy carrying the `evidence:` block with five real commit SHAs and `last-transition-from: review` — the complete lifecycle record. The other two are a re-run from `in-progress` that lost the evidence. - `f00017` keeps `review/`, a strict superset of the `in-progress/` copy, which was missing `last-transition-from` and its closing frontmatter delimiter. Nothing is lost that git cannot restore. Also documents the three undocumented public exports `lint:tsdoc` was blocking on (`perOperationResolver`, `resolvePerOperationContext`, `combineServices`) and regenerates `docs/API.md`. The docblocks say why each one throws rather than falling back — for the resolver, inventing a server for an operation whose service is missing is how a request ends up authenticated against the wrong API. `lint:proposals`: 166 proposals, no drift. `lint:tsdoc`: 135 files, all public exports explained. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`validate-package` has been red on a real product bug, not a flaky gate:
✘ 9 in the routes but NOT in the collection:
GET //api/users
...
→ Generation aborted.
Every generated request failed the generator's own parity check against
the routes it had just discovered, so `tanit generate` aborted and the
packaged binary produced nothing.
`pathToSegments` strips the base URL before comparing, and it matched
one literal string:
.replace(/\{\{baseUrl\}\}/, "")
r00019 moved base URLs to per-operation refs, so a merged collection
emits `{{baseUrl_<serviceId>}}` instead. That variant never matched, and
the whole variable survived as a path SEGMENT — `{{baseUrl_x}}/api/users`
normalised to `:p/api/users` while the discovered route normalised to
`api/users`, so nothing could ever line up.
Confirmed directly before changing anything:
uriFromRaw("{{baseUrl}}/api/users") → "api/users"
uriFromRaw("{{baseUrl_svc_a}}/api/users") → "{{baseUrl_svc_a}}/api/users"
The pattern now accepts the optional `_<serviceId>` suffix. Only the
base URL is removed: `{{id}}` and every other variable stay, because
they are part of the route and the comparison normaliser handles them.
This also explains why the failure looked like a path bug. Service ids
are derived from the framework search root, so in CI the id carried the
temp directory the fixture was generated in, and the error printed
`l_express_tmp_postman-package-iyBnwp_consumer_mi-api}}/api/auth/refresh`.
That string was a symptom; the leak is a separate concern and is NOT
addressed here.
`validate:package` now passes end to end: 9 requests found, binary
installs and generates, exit 0. Four regression tests cover the bare
name, a separator-laden service id, other variables surviving, and an
absolute origin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`lint:fixtures` failed in CI and passed locally, which is the signature
of a file that exists on disk and not in git.
Four smoke fixtures reached `develop` carrying a manifest and no code:
bun-modern-mini 4 files on disk, 3 tracked
express-const-method 3 on disk, 2 tracked
sse-mini 2 on disk, 1 tracked
websocket-mini 2 on disk, 1 tracked
In each case the missing file is `server.js` — the source the scanner
reads. `.gitignore` has a broad `tests/**/*.js` to keep TypeScript's
emitted output out of the repository, and the comment directly above it
already states the intended exception:
Sólo los fuentes (.ts) y los assets de tests
(server.js en fixtures y examples) están versionados.
That policy was written down and never implemented — there was no
negation rule, so the broad pattern swallowed the fixtures too. The gate
was right: as its own message puts it, a `package.json` alone on
`develop` lowers framework coverage without warning.
Adds the two negations the comment promised and commits the four
sources. `lint:fixtures`: all fixtures have a manifest AND a source.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixing `pathToSegments` in the previous commit uncovered the same defect
in a second place, with a different symptom.
The collection builder groups requests into folders from the first path
segment, and stripped the prefix with the same literal pattern:
const uriForGroup = firstUrl.replace(/^\{\{baseUrl\}\}/, "");
With a per-service `{{baseUrl_<serviceId>}}` the prefix survived, so
`topGroupFor` returned the VARIABLE as the folder key and all nine
requests of the express example collapsed into one folder named
`{{baseUrl Express Tmp Tmp G84VVxQTKF Mi Api}}` — the temp directory,
title-cased, as a folder name.
before: requests=9 folders=1
after: requests=9 folders=3 (Auth 2, Users 5, Orders 2)
Three copies of "what does a base URL look like" existed, and they
failed differently: one made every route miss the generator's parity
check, one wrecked the folder tree, and a third in `auth-flow` happened
to use a broader pattern and escaped. So the answer is now stated ONCE,
as `BASE_URL_VARIABLE` / `stripBaseUrlVariable` in `uri.helper`, and
both former copies call it.
Two more regression tests pin the edges: a variable in the MIDDLE of a
path is a path parameter and must survive, and `{{baseUrlOther}}` must
not be eaten by a prefix match.
`generate-json-report`, `postman.helper` and `collection-builder`
specs: 37 passing. `bun run lint` green end to end.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ence Adding `BASE_URL_VARIABLE` above `normalizeForComparison` separated that function from the file-level block comment that had been serving as its docblock, so `lint:tsdoc` correctly reported it as undocumented. It now has its own, saying what the normalisation is FOR: a route discovered in source and the request generated from it must compare equal no matter which framework's parameter syntax either one used. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s it `lint:contracts` was right to object: a constant declared next to the function that first needed it forces every other consumer to import that function to reach it. This one has three consumers already, which is the whole reason it exists. It joins `DEFAULT_BASE_URL` and `BASE_PATH_ENV_VAR` in `contracts/constants/core/base-url.constant.ts` — the module that already answers "what is a base URL here" — and `uri.helper` re-exports it so no import site had to change. lint:contracts: 467 types and constants, all in contracts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`tests/fixtures/multi-service/users-api/package.json` declared `@nestjs/common@^11.0.0` and `@nestjs/core@^11.0.0`, both below the `11.1.18` floor the repository records for packages with open alerts. The gate's own docstring says why it exists: copying an old example to make a new one reintroduces the alerts, and nobody notices until GitHub rebuilds the dependency graph days later on another branch. That is exactly what this fixture was — a copy carrying stale versions. These are fake manifests that are never installed, so the bump is a declaration change only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three e2e assertions were failing, and they looked like a genuine
isolation defect:
apps_orders should have exactly one GET /health, got []
They are the same base-URL bug, in a fourth place — this time in the
tests' own helper:
const path = rawUrl.replace(/^\{\{baseUrl\}\}/, "");
With a per-service `{{baseUrl_apps_orders}}` the prefix survived, so the
map was keyed on `{{baseUrl_apps_orders}}/health` and every
`methods.get("/health")` came back empty. Nothing was crossing between
services; the lookup key was wrong. Both files now call
`stripBaseUrlVariable`, which is why that helper was made a single
exported definition in the first place.
Two more fixture sources were also missing from git:
`tests/fixtures/multi-service-isolation/apps/{users,orders}/src/server.js`
— 5 files on disk, 3 tracked. Same `tests/**/*.js` rule as the smoke
fixtures, so the negation now covers `tests/fixtures/**/server.js` too.
Every `.js` under `tests/` is now either tracked or genuinely emitted
output; I checked the whole tree rather than the two I already knew of.
That closes the last of the four literal-`{{baseUrl}}` copies. Counted
in one place, they cost: an aborted generation, a collapsed folder tree,
three phantom isolation failures, and a fixture directory that only
worked on machines where it had once been generated.
`multi-service` + `multi-service-isolation`: 7 passing. `bun run lint`
green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The collections this tool generates referenced a variable that was never
declared. Measured on the express example:
declared: baseUrl, token, id, authUsername, authPassword
used: baseUrl_express_tmp_tmp_FIqkC9ZZW4_mi-api, id
Every one of the nine requests pointed at an undefined variable, so a
user importing the collection into Postman got nine broken URLs — with a
temp directory in the variable name.
`baseUrlVariableFor` chose the per-service `{{baseUrl_<serviceId>}}`
whenever `serviceId` happened to be set. But that form only EXISTS in a
combined collection, where `combineServices` declares one variable per
service; a single-service collection declares plain `baseUrl` and
nothing else. The choice is now derived from what the collection
actually declares, which makes "every variable used is declared" true by
construction instead of by convention.
before: {{baseUrl_express_tmp_tmp_FIqkC9ZZW4_mi-api}}/api/auth/login
after: {{baseUrl}}/api/auth/login undeclared used: []
This also closes the last two literal `{{baseUrl}}` copies — the shared
test contract and the Laravel catalog enricher — bringing the count to
six places that each answered the question their own way.
One of those edits broke generation outright while I was making it: the
import landed inside a `import type { … }` block and every run died at
`catalog-enricher.service.ts:22`. Caught by generating a collection
rather than by trusting the diff, which is the only reason it is not in
this commit as a regression.
tests/e2e + tests/frameworks: 86 files, 1678 tests, 0 failures — from 41
failures before. `validate:package` green end to end. `typecheck` green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`develop` and `main` both require the status check `ci-summary`. Two different files published a job by that name: the aggregate inside `validate.yml`, and a standalone `ci-summary.yml` mirroring the workflow's conclusion afterwards. Checked against the live repository rather than reasoned about: the commit at `develop`'s head carries exactly ONE `ci-summary` check run, and it comes from `validate.yml`. The standalone file has never run at all — `gh workflow list` does not even register it. The reason is that a `workflow_run` trigger only fires from the DEFAULT branch, `main`, and `main` carries only release-binaries, release-desktop and validate. The file exists on `develop` alone. So the required context is unambiguous TODAY, by accident. Had that file ever reached `main`, two workflows would have published the same context and the required check would have become ambiguous — a branch protected by a check nobody can point at, which is the same class of defect as a required check no workflow produces. Its job is renamed to `validate-conclusion`, and the header now states that it is inert and why. The author's intent is preserved; the collision is not. `validate.yml` is untouched, so the required check keeps resolving to exactly what it resolves to now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ci-summary` is the required status check on BOTH `develop` and `main`,
and it could not succeed. Every run ended:
bun: command not found
Process completed with exit code 127
The job runs `bun run scripts/gates/ci-summary.script.ts` on a runner
where nothing checked out the repository or installed bun. x00071 split
`validate` into eight parallel jobs and gave this one the verdict, but
not the tools to compute it.
It failed even when it should have passed. On the run that prompted this
commit all eight dependencies reported `success` — typecheck, lint,
test-coverage, validate-examples, bench-check, security-audit,
validate-package, integration-verifier — and the aggregate still exited
127.
A required check that cannot succeed does not protect a branch, it
closes it. This is the same shape as the `ci-complete` context no
workflow produced, which kept DelendAI's `main` unmergeable for months.
Adds checkout + setup-bun. Verified the verdict logic itself is sound by
running the script directly with the eight results in the environment:
exit 0 when all are `success`, exit 1 when one is `failure`. It was only
ever missing its interpreter.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Three gates were red on
develop. All three are fixed here, none by relaxing a check.validate-package— a real product bugtanit generateaborted on its own parity check:pathToSegmentsstrips the base URL before comparing routes, and it matched exactly one literal string —{{baseUrl}}. r00019 moved base URLs to per-operation refs, so a merged collection emits{{baseUrl_<serviceId>}}, which never matched and survived as a path segment. Confirmed directly before changing anything:The packaged binary therefore produced nothing.
validate:packagenow passes end to end: 9 requests found, binary installs and generates, exit 0. Four regression tests added.This also explains why the failure looked like a path bug: service ids derive from the framework search root, so in CI the id carried the temp directory and the error printed
..._tmp_postman-package-iyBnwp_consumer_mi-api}}/api/auth/refresh. That leak is a separate concern and is not addressed here.lint— proposal driftlint:proposalsreported 19 violations. Sixteen weredone/proposals whose directory did not match their ownkindfrontmatter. Three were the same proposal existing in several lifecycle states at once (f00016in three places,f00017in two) — debris from a transition that COPIED instead of moving, swept in by an auto-commit.Diffing decided which copy survives; the bodies are identical apart from frontmatter.
f00016keepsdone/feats/, the only copy carrying theevidence:block with five real commit SHAs.f00017keepsreview/, a strict superset of the other.Also documents the three public exports
lint:tsdocwas blocking on, and regeneratesdocs/API.md.Verified locally
bun run lint— green end to endbun run validate:package— greenbun run lint:integration-verifier --audit— 5/5Opened as a pull request rather than pushed directly:
developnow requiresci-summary, which is exactly what these commits repair.🤖 Generated with Claude Code