Skip to content

feat: add installable capability catalog - #1552

Merged
lilyshen0722 merged 6 commits into
mainfrom
kai/d8-installable-catalog
Sep 5, 2026
Merged

lilyshen0722 merged 6 commits into
mainfrom
kai/d8-installable-catalog

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Summary:

  • Authenticated capability catalog uses manifest readiness and caller-owned parent/projection rows.
  • Unavailable Telegram or Slack install returns provider_not_configured before a claim.
  • Missing active projections become an error row and retry in place.

Verification:

  • TypeScript check passed.
  • 71 focused installable, catalog, OAuth, dispatcher, and contract tests passed.
  • TypeScript lint reports 0 errors; existing repository warnings only.

Stacked on #1550 (PR C).

@samxu01
samxu01 force-pushed the kai/d8-installable-catalog branch from c3dcbca to fc0690c Compare September 5, 2026 08:46
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Gate at fc0690c8, PR A's own delta isolated against PR C's head 9dd38b19.

Rebase verified. 9dd38b19 is an ancestor of this head, and the delta is eight files, purely additive — none of C's D3, rate-limiter, ObjectId-guard or PATCH { podId } work is removed. The earlier head c3dcbcab was cut from 6c99add3 and would have reverted all eight of C's later commits on merge; that is resolved.

What is correct

  • Catalog auth (my plan §2.7). router.get('/', auth, …) plus a requesterId 401, and providerReadiness never branches on role — so every authenticated caller gets the same unavailableReason enum and there is no admin-only widening to leak an instance's configuration shape.
  • Seed 1. unavailableReason is a closed enum; hasConfiguration reads env vars and never emits their names. The service test asserts the negative over the whole serialized body (not.toMatch(/SLACK_|CONNECTOR_SECRET|private-claim|secret-ref|nonce/)), which is broader than the assertion I asked for — it also catches the claim id.
  • 422 ordering. providerReadiness refuses before Pod.findById, so an unconfigured provider cannot be used to probe pod membership, and no parent row is claimed.
  • Seed 5. The sweep now sets status: 'error', errorMessage: 'projection missing' alongside the component staleness, exactly as §2 D2 specified. error is in both the partial unique index and liveClaimStatuses, so Retry reclaims the same parent rather than creating a second one. A paused parent is untouched by this branch, so an administrative stop survives a missing projection.
  • publicInstallation is a whitelist, not a delete-list. That is the shape that stays correct when someone adds a field to the parent.

One finding: config.adminPause reaches the owner with adminId

The two mappers in installableCatalogService.ts disagree, so one of them is wrong:

  • publicInstallation whitelists (status, errorMessage, boundPodId, claimedAt, updatedAt, components) — the parent's adminPause never leaves the server.
  • publicIntegration deletes config.botTokenRef, config.oauthStateNonce and config.pendingBind.botTokenRef, and nothing else — so the child's config.adminPause is serialized whole, including adminId.

A paused user therefore receives, in their own browser, the identifier of the administrator who paused them. §2 D3 says the reason reaches the owner's row verbatim; it does not say the moderator's identity does, and naming the moderator to the moderated party is a distinct concern from explaining the stop. It arrives here silently, because a delete-list only protects the fields it was told about — which is exactly why the parent mapper was written as a whitelist.

Deleting the key outright is not the fix: the owner needs the reason, and the D2 table renders Paused by an administrator with it. Project the field instead — keep reason and at, drop adminId. The full { adminId, installationId, ownerId, reason, at } audit entry stays on the parent regardless, so nothing is lost for the admin surface.

Nothing else outstanding on this PR.

@samxu01
samxu01 force-pushed the kai/d8-installable-catalog branch 2 times, most recently from 1e0cf95 to 2a1b5a5 Compare September 5, 2026 08:53
@lilyshen0722
lilyshen0722 changed the base branch from kai/d8-phase2-schema-gates to main September 5, 2026 12:15
@samxu01
samxu01 force-pushed the kai/d8-installable-catalog branch from 2a1b5a5 to 977ac11 Compare September 5, 2026 12:30
Comment thread backend/routes/installables.ts Fixed
@samxu01
samxu01 force-pushed the kai/d8-installable-catalog branch from fe1e82f to a37cfe6 Compare September 5, 2026 12:35
@lilyshen0722
lilyshen0722 merged commit 77a6225 into main Sep 5, 2026
14 checks passed
@lilyshen0722
lilyshen0722 deleted the kai/d8-installable-catalog branch September 5, 2026 12:54
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.

2 participants