Skip to content

feat(skills): tenant selector with its own save, for super admins only - #12

Merged
squarezw merged 1 commit into
mainfrom
feat/skill-tenant-transfer
Aug 9, 2026
Merged

feat(skills): tenant selector with its own save, for super admins only#12
squarezw merged 1 commit into
mainfrom
feat/skill-tenant-transfer

Conversation

@squarezw

@squarezw squarezw commented Aug 9, 2026

Copy link
Copy Markdown
Owner

owner_tenant_id is a snapshot taken at creation, so a user moved between
tenants leaves their skills behind: the old tenant's admin still manages
them, the new tenant's admin cannot. This adds the control that fixes it, in
the skill editor, visible to super admins only.

Pairs with ragent-service #71.

It saves on its own button

Reassignment changes ownership, not content — a published skill is the same
skill afterwards, so routing it through save-and-review would cost production
a working skill for no reason. The backend counterpart is a dedicated
PUT /skills/{id}/tenant that leaves review status untouched.

The warning panel

It lists what the operator cannot see from the dropdown: the old tenant's
admins lose access, tenant-shared content becomes readable by everyone in the
new tenant, the department is cleared, and a name collision in the target
tenant will fail the save.

It appears only once the selection actually differs. A warning that is always
on is not a warning.

Details that came from imagining the failure

  • The button is disabled rather than hidden when nothing changed — a
    button appearing out of nowhere reads as "my selection wasn't registered".
  • It is re-enabled after a failure, so you can rename and retry.
  • A failed tenant-list fetch shows an error instead of an empty dropdown,
    which would read as "there are no other tenants" rather than "this didn't
    load".
  • Backend 409/403 detail text is surfaced verbatim: "target tenant already
    has a skill named X, rename it first" tells the user what to do next, which
    a generic failure toast does not.

The proxy layer, and a test for it

The first attempt returned 404 — from Next.js, not the backend. Every
/api/v1/skills/* path needs an explicit proxy file under
pages/api/v1/skills/, and this one was missing, so the request never
reached the backend at all. The 404 looks like "the backend has no such
endpoint" while the backend is fine, which sends debugging in exactly the
wrong direction.

That is the third time recently that adding a path meant forgetting the layer
which has to be updated in lockstep — pnpm-workspace.yaml and
builtin_skills/ both had to be added to the Dockerfile, with the same
silent failure mode.

So the new test does not merely pin this one file. It scans the front-end
sources for every /api/v1/skills/... call and checks each resolves to a
proxy file, understanding both [id] and [...path] segments. It keys off
the requests the front end actually makes, since those are what 404.
Mutation-verified by removing this proxy file and an unrelated one; both go
red, naming the missing path.

246 passed. tsc unchanged at the 135 baseline.

🤖 Generated with Claude Code

`owner_tenant_id` is a snapshot taken at creation, so a user moved between
tenants leaves their skills behind: the old tenant's admin still manages
them, the new tenant's admin cannot. This adds the control that fixes it,
in the skill editor, visible to super admins only.

It saves on its own button rather than with the form. Reassignment changes
ownership, not content — a published skill is the same skill afterwards, so
routing it through save-and-review would cost production a working skill for
no reason. Backend counterpart is a dedicated `PUT /skills/{id}/tenant`.

The warning panel lists what the operator cannot see from the dropdown: the
old tenant's admins lose access, tenant-shared content becomes readable by
everyone in the new tenant, the department is cleared, and a name collision
in the target tenant will fail. It appears only once the selection actually
differs — a warning that is always on is not a warning.

Small things that came from imagining the failure: the button is disabled
rather than hidden when nothing changed, because a button appearing out of
nowhere reads as "my selection wasn't registered"; it is re-enabled after a
failure, so you can rename and retry; and a failed tenant list shows an
error instead of an empty dropdown, which would read as "there are no other
tenants" rather than "this didn't load".

Backend 409/403 detail text is surfaced verbatim — "target tenant already
has a skill named X, rename it first" tells the user what to do next, which
a generic failure toast does not.

## The proxy layer, and a test for it

The first attempt returned `404` from Next.js. Every `/api/v1/skills/*` path
needs an explicit proxy file under `pages/api/v1/skills/`, and this one was
missing, so the request never reached the backend at all. The 404 looks like
"the backend has no such endpoint" while the backend is fine — it sends
debugging in exactly the wrong direction.

That is the third time recently that adding a path meant forgetting the
layer that has to be updated in lockstep (pnpm-workspace.yaml and
builtin_skills/ both had to be added to the Dockerfile). So the new test does
not just pin this one file: it scans the front-end sources for every
`/api/v1/skills/...` call and checks each one resolves to a proxy file,
understanding both `[id]` and `[...path]` segments. It keys off the requests
the front end actually makes, since those are what 404.

246 passed. tsc unchanged at the 135 baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@squarezw
squarezw merged commit d119e7f into main Aug 9, 2026
1 check passed
@squarezw
squarezw deleted the feat/skill-tenant-transfer branch August 9, 2026 01:36
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.

1 participant