Skip to content

feat(api): flag a pinned account on /v1/combos steps without leaking the id - #11076

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.50from
ntdatt812:feat/combo-account-pinned-10968
Aug 22, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.50from
ntdatt812:feat/combo-account-pinned-10968

Conversation

@ntdatt812

Copy link
Copy Markdown
Contributor

Summary

GET /v1/combos strips connectionId on purpose, and must keep doing so. The side effect is that two steps pinning different accounts of the same provider project to byte-identical objects:

"models": [
  { "kind": "model", "model": "jina-ai/some-embed", "providerId": "jina-ai" },
  { "kind": "model", "model": "jina-ai/some-embed", "providerId": "jina-ai" }
]

A client reading that cannot tell a two-account failover from the same step listed twice, so an importing integration concludes the combo has no failover.

This adds accountPinned to each model step — a boolean derived from the connectionId that still never leaves the server, not a prefix of it.

Three decisions worth calling out, since the issue left them open:

  • Per-step boolean rather than pinnedAccountCount on the combo. The count says a combo has pins somewhere; the boolean says which of the duplicate-looking steps they are, which is the question a client is actually stuck on.
  • Always set on model steps, true or false. An absent field then means an older server rather than an unpinned step, so a client can tell "not pinned" from "cannot tell".
  • Never set on combo-ref. That step routes through another combo and has no account of its own, so false would be a claim rather than a fact.

The flag uses the same shape test providerId already gets. cleanupComboConnectionRefs deletes the key when a connection is deleted, so a step whose pinned account is gone reports false rather than pointing at nothing.

No route, schema, or caller changes — the three consumers (/v1/combos, and both VS Code combo routes) pass the projection straight through, and /v1/combos is not described in docs/openapi.yaml, so nothing there needed updating.

Related Issues

Validation

  • Change type: routing (combo projection / public API shape)
  • Focused tests and category gates from the golden path
  • npm run lint — eslint clean on both changed files
  • Reconciled with the current active release base (release/v3.8.50); focused checks rerun afterward
  • Production-code changes include a new or updated automated test in this PR
tests/unit/v1-combos-projection.test.ts
  + combo-projection-capabilities-3979 / combo-routes-composite-tiers
  + models-catalog-route / combos-duplicate-route
  + combo-model-name-collision-8530 / combo-control-center      97 pass, 0 fail

Mutation-checked, after confirming each edit actually applied:

never set the flag        -> 6 failed
always set it to true     -> 3 failed

So the new cases pin the derivation itself rather than passing incidentally — in particular the second mutation is what keeps accountPinned: false from being a value nothing asserts.

Tests Added Or Updated

  • tests/unit/v1-combos-projection.test.ts — six new cases: a pinned step (asserting the id does not appear in the serialized output), an unpinned step, empty/non-string connectionId values, combo-ref carrying no flag, malformed steps still returning null, and the issue's own reproduction — two pinned accounts plus one unpinned step of the same provider and model, which now read as [true, true, false].
  • Two existing #2300 assertions gained the new key. The connectionId-drop assertions themselves are unchanged and still green, which is the point of the pair.

Coverage Notes

src/app/api/v1/combos/projectCombo.ts is covered by the file above. The added branch has both outcomes asserted directly and both directions mutation-checked.

Reviewer Notes

  • Additive key on a public response; no field is removed or renamed.
  • The one behavior change a client could notice is that accountPinned now appears in /v1/combos, the VS Code combo route, and the raw VS Code combo route alike, since all three share projectCombo. That seemed right — the same ambiguity exists on all three — but say the word if you want it scoped to /v1/combos only.

…the id

`projectComboStep` strips `connectionId` on purpose, so two steps that pin
different accounts of the same provider project to identical objects:

  { "kind": "model", "model": "jina-ai/some-embed", "providerId": "jina-ai" }
  { "kind": "model", "model": "jina-ai/some-embed", "providerId": "jina-ai" }

A client reading that catalog cannot tell a two-account failover from the same
step listed twice, so an importing integration concludes the combo has no
failover at all.

Add `accountPinned`, derived from the same `connectionId` that stays stripped —
a boolean, not a prefix. It is set on every `model` step, so an absent field
means an older server rather than an unpinned step; a `combo-ref` routes through
another combo and has no account of its own, so it carries no flag.

The shape test is the one `providerId` already gets. `cleanupComboConnectionRefs`
deletes the key when the connection is deleted, so a step whose pinned account is
gone reports false rather than pointing at nothing.

Closes cheaper-inference#10968.
@ntdatt812
ntdatt812 force-pushed the feat/combo-account-pinned-10968 branch 2 times, most recently from 57ee123 to 8a3161d Compare August 22, 2026 00:44
@ntdatt812

Copy link
Copy Markdown
Contributor Author

Note on the one red check, since it is not from this PR and it is not only this PR.

Merge integrity fails on its second half. The changelog half passes:

[changelog-integrity] OK — no base bullets lost vs origin/release/v3.8.50.

Agent Skills Generator [dry-run]
Generated: 1 · Unchanged: 45 · Pruned: 0 · Orphans: 0 · Errors: 0
  GENERATED:
    + omni-webhooks
##[error]Process completed with exit code 2.

omni-webhooks — this PR changes two files, src/app/api/v1/combos/projectCombo.ts and its test, and touches no route, no skill, and no generator input.

It traces to the base:

$ git show --stat 1c920eb8      # fix(webhooks): remove 3 ghost events (#11050)
  src/shared/constants/agentSkills.ts   | 2 +-
  src/lib/webhooks/eventDescriptions.ts | 39 +++-----
  …                                       skills/omni-webhooks/SKILL.md  — not in the diff

$ git log --oneline 1c920eb8..origin/release/v3.8.50 -- skills/omni-webhooks/SKILL.md
(nothing)

So #11050 changed a generator input and the generated file was never rebuilt, and nothing since has rebuilt it. Any PR rebased onto that commit inherits the red — which is the exact scenario the comment in ci.yml describes for gap 30.

I rebased onto 7ddbaf69 (current release/v3.8.50) after opening this and reran the focused checks; that fixed the changelog half and left this one, as expected. npm run check:agent-skills-sync -- --apply regenerates it in one commit, but it belongs in its own PR rather than smuggled into a combos change — say the word if you would rather I send that one too.

For completeness, the job is continue-on-error for fork PRs, so it is advisory here rather than blocking. It will not be advisory on the internal PR that next rebases onto this base.

@ntdatt812

Copy link
Copy Markdown
Contributor Author

Same story for the other red check, No new ESLint warnings — different cause, also on the base.

I ran the gate's own command locally, which writes the full inventory to .artifacts/eslint-results.json:

$ npm run lint:json -- --max-warnings 0        # exit 1

errors: 4  warnings: 0  files: 1
  4e/0w  tests/unit/capture-critical-db-state.test.ts
          @typescript-eslint/no-explicit-any (4x)

One file, four errors, and it is not one of mine — this PR's diff is two files:

$ git diff --name-only HEAD~1 HEAD
src/app/api/v1/combos/projectCombo.ts
tests/unit/v1-combos-projection.test.ts

$ git log --oneline -1 -- tests/unit/capture-critical-db-state.test.ts
0ff0490a test(db): assert resetDbInstance swaps the singleton, WAL mode, and schema_version seed (#10906)

Worth noting these are errors, not warnings, so they are not specific to the --max-warnings 0 variant — npm run lint fails on the same four. Either #10906 landed them past the gate, or config/quality/eslint-suppressions.json needs a regenerate to cover them.

That accounts for both red checks on this PR, and both point at the base rather than the diff:

check cause mine?
Merge integrity skills/omni-webhooks/SKILL.md never rebuilt after #11050 changed agentSkills.ts no
No new ESLint warnings 4 × no-explicit-any in capture-critical-db-state.test.ts from #10906 no

Both also fail identically on #11072 and #11074, while #11053 — cut before these landed — is green on both. Happy to send either fix as its own PR if useful; I have kept both out of this one.

@diegosouzapw
diegosouzapw merged commit e06f8b7 into cheaper-inference:release/v3.8.50 Aug 22, 2026
9 of 17 checks passed
@ntdatt812

Copy link
Copy Markdown
Contributor Author

Final CI note, then I will stop adding to this thread.

The four unit shards went red too, so here is the direct evidence that this diff is not in any of it. This PR's own tests run in shard 3, and all fourteen pass there:

✔ #2300 projectComboStep keeps model + providerId, drops connectionId/weight/label
✔ #2300 projectComboStep keeps combo-ref's comboName, drops weight/label
✔ #2300 projectComboStep returns null for unknown kinds + malformed steps
✔ #2300 projectCombo preserves name/strategy/description, projects models
✔ #2300 projectCombo defaults strategy to 'priority' when missing
✔ #2300 projectCombo returns null for empty name
✔ #2300 projectCombo filters out malformed step entries silently
✔ #2300 projectCombo omits description when empty
✔ #10968 projectComboStep reports a pinned account without the connectionId
✔ #10968 projectComboStep reports false for an unpinned model step
✔ #10968 an empty or non-string connectionId is not a pin
✔ #10968 a combo-ref carries no accountPinned
✔ #10968 malformed steps stay null rather than gaining the flag
✔ #10968 two pinned accounts of one provider are no longer identical

What shard 3 fails on instead: buildSlackPayload — all WEBHOOK_EVENTS produce valid payloads, buildSlackPayload — provider.error includes provider context, the quota classifier, probeBeforeSpawn, service adoption, and the systemd-notify handshake. Shard 1 fails on buildDiscordPayload ×2, buildTelegramPayload, and three getProviderCredentials cases.

The webhook ones are the same root as the skills drift I flagged above — #11050 removed three events from WEBHOOK_EVENTS and the payload tests that iterate it were never updated, alongside the generated omni-webhooks/SKILL.md that was never rebuilt. One change, three red gates.

Consistent across the board:

check #11076 #11072 #11074 #11053
Unit Tests fast-path (1/4) fail fail fail pass
Merge integrity fail fail fail pass
No new ESLint warnings fail fail fail pass

#11053 was cut before those commits landed and is green on all three. Nothing here is specific to this PR, and nothing here is specific to fork PRs either.

Green on this PR: Vitest, Docs Gates, Build, dast-smoke, semgrep ×2, Change Classification.

muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…the id (cheaper-inference#11076)

⭐5 — /v1/combos strip connectionId de propósito; dois passos pinando contas DIFERENTES do mesmo provider viram objetos byte-idênticos e um cliente conclui que não há failover. Adiciona accountPinned (boolean derivado do connectionId, nunca vaza o id) em cada passo model; sempre true/false, nunca em combo-ref. Sem rota/schema/mudança de caller. TDD. Fecha cheaper-inference#10968.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(api): GET /v1/combos should flag pinned accounts without leaking connectionId

2 participants