Commit fb5528d
authored
feat(proxy): credential pool auto-failover + approval-prompt coalescing (#43)
* feat(channel): coalesce duplicate approval prompts by dest:port
* wip(proxy): checkpoint persist-once + store work before respawn
* test(proxy): assert concurrent same-target asks coalesce to one prompt
* feat(store): add credential pool + health schema and store API
Migration 000006 adds credential_pools, credential_pool_members and
credential_health tables. Store API: CreatePoolWithMembers (atomic;
namespace mutual-exclusion + oauth-member validation in one tx), GetPool,
ListPools, RemovePool, PoolExists, PoolsForMember, and
Set/Get/ListCredentialHealth. Covered by CRUD/ordering/validation tests
and an up/down migration reversibility test.
* feat(vault): add PoolResolver pool->active-member chokepoint
PoolResolver is the single place a bound pool name expands to a concrete
credential. IsPool/ResolveActive (first healthy or expired-cooldown member
in position order; degrade to soonest-recovering when all down) and
MarkCooldown for Phase 2 synchronous in-memory failover. Locking
discipline documented: membership immutable per instance (atomic-pointer
swap on reload), health mutated in place under an RWMutex. RateLimit
(60s) and AuthFail (300s) cooldown TTL consts. Nil-safe.
* feat(cli): add pool subcommands and credential/pool namespace guards
sluice pool create|list|status|rotate|remove. status computes the active
member via the same PoolResolver logic the proxy uses so it never
disagrees with what gets injected; rotate parks the active member
(lazy-recovery cooldown) so the next member takes over. cred add rejects
a name colliding with an existing pool; cred remove is blocked (before
the vault delete) when the credential is a live pool member, so no
dangling member rows or destroyed secrets.
* feat(proxy): wire PoolResolver into server, addon and reloadAll
Server gains an atomic PoolResolver pointer (parallel to the binding
resolver), StorePool/PoolResolverPtr, and threads it into SluiceAddon via
WithPoolResolver. addon.resolvePoolMember is the chokepoint helper
(non-pool names passthrough). main.go registers the pool subcommand,
loads the resolver at startup, and rebuilds+atomically swaps it in
reloadAll alongside the binding/oauth reloads. Injection does not consult
it yet (Phase 1).
* feat(mcp): opt MCP tool calls out of approval coalescing
* wip(telegram): checkpoint final-count edit before respawn
* fix(telegram): use coalesced-count label in resolve edit body
* docs(plans): convert tasks/phases to exec checkboxes; mark completed work
* feat(channel): re-confirm broker coalescing tests green
* feat(store): idempotent approval-rule persist
* test(proxy): confirm full suite green after coalescing merge
* feat(telegram): render coalesced count on resolve and cancel edits
* docs(plans): complete approval-coalescing; move to completed
* test(store): confirm credential-pool Phase 0 green post-merge
* feat(proxy): pool phantom indirection + R1 attribution + R3 stable JWT
* feat(proxy): auto-failover on 429/401 for credential pools
* docs(plans): complete credential-pool-failover; move to completed
* fix(proxy): address comprehensive review findings
* fix(proxy): correct token-endpoint failover member attribution + harden cooldown durability
* style: satisfy golangci-lint (errorlint, QF1002, unparam)
* test(e2e): pool failover + approval coalescing end-to-end
* fix(proxy): address Copilot review (per-request failover attribution, JWT payload marshaling, coalesced-count accuracy, single-pool membership)
* fix: address Copilot re-review (failover callback registration, cred-remove fail-closed, CLAUDE.md accuracy)
* fix(proxy): split-host pool OAuth refresh attribution + protocol-aware failover lookup
* fix(proxy): classify token-endpoint 403 invalid_grant as auth-failure; audit detected protocol
* fix(vault): monotonic cooldown (in-memory + durable); doc pool phantom shapes
* fix(proxy): fail-closed unattributed pooled refresh; broker resolve/detach race; scope pool failover fallback to pooled requests
* fix(cli): reject pool removal while bindings still reference the pool
* fix(store): enforce namespace + pool-member + health-row invariants at store layer; engine-aware persist fast-path
* fix(proxy): pool-namespace covered-set; shared-health prune for non-members; store-gated vault delete on cred remove
* fix(store): guard CAS credential-rollback for pool members + health cleanup; fix e2e coalesce deadlock-on-failure
* fix(store): RemovePool health cleanup; reject live-pool-member meta downgrade; CAS-aware health delete
* fix(proxy): API-host failover requires per-flow pool-usage evidence (non-consuming peek, no blind fallback)
* fix(cred): validate vault before store removal; cap coalesced subscribers; CLAUDE.md attribution accuracy
* fix(store): failover health write no-ops for non-pool-member (atomic; no health-row resurrection)
* fix: guard pool-rotate health write; atomic REST cred removal; gate MarkCooldown to current member set
* fix(proxy): free flow tag after Response; tag token-host flow only on actual pool-phantom swap
* fix: RemoveCredentialFully cleans health on partial-cleanup finish; exclude 5xx from token-endpoint failover classification
* fix: pool+epoch-scoped health guards; CancelAll lost-wakeup; pooled-JWT phantom swap in query/path
* fix(proxy): plain-cred refresh attribution on shared token URL; pool-aware QUIC injection
* fix: R3-stable QUIC pool phantom; pool+epoch in refresh attribution; atomic RemovePoolIfUnreferenced; REST cred-remove missing-secret tolerant
* fix(store): binding creation requires live credential or pool; CLAUDE.md QUIC R3 accuracy
* test(telegram): deterministically sync cancel-edit assertion (deflake TestCancelApprovalRendersCoalescedCount)
* fix: Telegram cred_meta on all adds; QUIC R3 comments; typed 409-vs-500 for RemoveCredentialFully
* fix(telegram): roll back vault secret if cred-add metadata/binding fails; completed-plan R3 doc accuracy
* fix(telegram): roll back credential_meta (CAS) as well as vault on env-var binding failure1 parent 84a6dc6 commit fb5528d
52 files changed
Lines changed: 14363 additions & 357 deletions
File tree
- cmd/sluice
- docs/plans
- completed
- e2e
- internal
- api
- channel
- mcp
- proxy
- store
- migrations
- telegram
- vault
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
286 | 286 | | |
287 | 287 | | |
288 | 288 | | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
289 | 314 | | |
290 | 315 | | |
291 | 316 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
30 | 40 | | |
31 | 41 | | |
32 | 42 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
227 | 227 | | |
228 | 228 | | |
229 | 229 | | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
230 | 239 | | |
231 | 240 | | |
232 | 241 | | |
| |||
553 | 562 | | |
554 | 563 | | |
555 | 564 | | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
556 | 589 | | |
557 | 590 | | |
558 | 591 | | |
559 | 592 | | |
560 | 593 | | |
561 | | - | |
562 | | - | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
563 | 644 | | |
564 | 645 | | |
565 | 646 | | |
| |||
569 | 650 | | |
570 | 651 | | |
571 | 652 | | |
572 | | - | |
573 | | - | |
574 | | - | |
575 | | - | |
576 | | - | |
577 | | - | |
578 | | - | |
579 | | - | |
580 | | - | |
581 | | - | |
582 | | - | |
583 | | - | |
584 | | - | |
585 | | - | |
586 | | - | |
587 | | - | |
588 | | - | |
589 | | - | |
590 | | - | |
591 | | - | |
592 | | - | |
593 | | - | |
594 | | - | |
595 | | - | |
596 | | - | |
597 | | - | |
598 | | - | |
599 | | - | |
600 | | - | |
601 | | - | |
602 | | - | |
603 | | - | |
604 | | - | |
605 | | - | |
606 | | - | |
607 | | - | |
608 | | - | |
609 | | - | |
610 | | - | |
611 | | - | |
612 | | - | |
613 | | - | |
614 | | - | |
615 | | - | |
616 | | - | |
617 | | - | |
618 | | - | |
619 | | - | |
620 | | - | |
621 | | - | |
622 | | - | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
623 | 656 | | |
624 | 657 | | |
625 | 658 | | |
| |||
0 commit comments