@uluops/ops-sdk 6.3.1 → 6.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +190 -1
- package/dist/client.d.ts +86 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +71 -5
- package/dist/client.js.map +1 -1
- package/dist/config/constants.d.ts +1 -1
- package/dist/config/generated-version.d.ts +1 -1
- package/dist/config/generated-version.js +1 -1
- package/dist/config/validators.d.ts +25 -1
- package/dist/config/validators.d.ts.map +1 -1
- package/dist/config/validators.js +31 -1
- package/dist/config/validators.js.map +1 -1
- package/dist/errors/errors.d.ts +71 -0
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +99 -0
- package/dist/errors/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/operations/admin.d.ts +100 -0
- package/dist/operations/admin.d.ts.map +1 -0
- package/dist/operations/admin.js +130 -0
- package/dist/operations/admin.js.map +1 -0
- package/dist/operations/auth.d.ts +21 -1
- package/dist/operations/auth.d.ts.map +1 -1
- package/dist/operations/auth.js +49 -3
- package/dist/operations/auth.js.map +1 -1
- package/dist/operations/orgs.d.ts +27 -0
- package/dist/operations/orgs.d.ts.map +1 -0
- package/dist/operations/orgs.js +34 -0
- package/dist/operations/orgs.js.map +1 -0
- package/dist/operations/projects.d.ts +46 -0
- package/dist/operations/projects.d.ts.map +1 -1
- package/dist/operations/projects.js +54 -1
- package/dist/operations/projects.js.map +1 -1
- package/dist/types/auth.d.ts +10 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/rehome.d.ts +234 -0
- package/dist/types/rehome.d.ts.map +1 -0
- package/dist/types/rehome.js +171 -0
- package/dist/types/rehome.js.map +1 -0
- package/dist/types/response-schemas.d.ts +19 -0
- package/dist/types/response-schemas.d.ts.map +1 -1
- package/dist/types/response-schemas.js +19 -0
- package/dist/types/response-schemas.js.map +1 -1
- package/dist/types/schemas.d.ts +20 -0
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +22 -0
- package/dist/types/schemas.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@ All notable changes to `@uluops/ops-sdk` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [6.4.1] - 2026-09-15
|
|
8
|
+
|
|
9
|
+
6.4.0 reached Verdaccio only; the review of that change set (anxiety-reader 84 / docs-validator 81 / dx-validator 83 / code-auditor 98, all on the `feat/rehome-surfaces` diff) folded into this release before npm. What changed against 6.4.0:
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`login(email, password, { autoRefresh })`.** The default (`true`) is unchanged; `false` installs the session without the password so a 401 surfaces untouched. Why it exists: sdk-core's refresh is a *fresh login*, and under the API's default single-session policy a login **revokes the user's other sessions**; the mutation that hit the 401 is then *not* retried, and the budget is one. A script that must treat 401 as "stop" (spec §4.7) was having the SDK act first. Tested both ways: no `/auth/login` on a 401 with the option, exactly one without.
|
|
14
|
+
- **`isInsufficientRoleError`** — the fifth 403 code gains the guard every other one has.
|
|
15
|
+
- `admin.rehomeProject` / `admin.releaseProjectRehome` **refuse a non-UUID client-side** (`InputValidationError`) — `validateUuid` existed and was dead code; the admin route is id-only and answers 400 to a name, and the whole `projects.*` surface trains callers to pass names.
|
|
16
|
+
- TSDoc `@param`/`@returns`/`@example` on the new operations and validators; `@throws {MfaRequiredError}` on `login`.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **`admin.releaseProjectRehome` shape failure is a `ZodError`**, like every other shape failure in this SDK, not a bare `Error` — and the doc says what it means: the server answers 200 after the row is gone, so a shape failure is most likely a *completed* release.
|
|
21
|
+
- The admin path's missing-`reason` message says why it is required ("the target org's only standing for the move") instead of Zod's "expected nonoptional".
|
|
22
|
+
- `OrgAuditFeedQuery.limit` is documented as **1–100, not clamped** (the API answers 400) — the JSDoc said "1–200 (API-clamped)", which was false on both counts.
|
|
23
|
+
|
|
24
|
+
### Fixed — documentation that overclaimed
|
|
25
|
+
|
|
26
|
+
- The 6.4.0 entry said "an MFA-enrolled account could not log in through the SDK … Fixed". True for `login()`/`auth.login()` only: a client constructed with `{ email, password }` (or autoloaded credentials) logs in inside sdk-core, which cannot see the challenge and surfaces a generic `UnauthorizedError`. Documented on `OpsClientConfig`, `MfaRequiredError` and in the README; not fixable from this package without intercepting sdk-core's strategy.
|
|
27
|
+
- The MFA challenge token is **single-use and consumed before the code is verified** (API `totp-service.verifyLogin`) — a mistyped code burns it. The README example read as retryable; it no longer does.
|
|
28
|
+
- **`same_org` is the idempotence signal of the ADMIN path only.** The member path's lookup is source-scoped, so a re-run after the move answers 404, not `same_org`. `projects.rehome`'s doc, the README table and `rehomeRefusalReason`'s doc now say so, and the table gained the two missing reasons (`project_soft_deleted`, `project_has_no_org`) plus a row for *no HTTP answer* (`null` from `rehomeRefusalReason` is "not a refusal I can name", not "safe to retry").
|
|
29
|
+
- README "automatic token refresh" is now qualified with the three properties above.
|
|
30
|
+
|
|
31
|
+
### Known, not fixed here
|
|
32
|
+
|
|
33
|
+
- sdk-core's 401 branch inspects the *client's* auth strategy, not the request's `skipAuth`, so a wrong TOTP code on a bare client says "Set ULUOPS_API_KEY" — the one remedy D20 forbids. Filed against sdk-core.
|
|
34
|
+
- A TOTP-installed session's expiry message ("credentials were cleared after login") is sdk-core's and wrong for this path; `JwtSessionAuth` has no way to be handed `expiresAt`.
|
|
35
|
+
|
|
36
|
+
## [6.4.0] - 2026-09-15 (Verdaccio only)
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
The client side of project re-home (project-org-routing-and-rehome spec §4; API deployed 2026-09-15). Three surfaces for three audiences — a member capability, a member feed, and a platform-admin namespace — plus the login branch the admin path made unavoidable.
|
|
41
|
+
|
|
42
|
+
- **`projects.rehome(idOrName, { targetOrg, reason? }, options?)`** — the member path (`POST /projects/:id/rehome`). The SOURCE is the call's org scope (`options.org` / client `orgSlug`); the project is looked up there, so a work-org project moved without scope is a 404, not a silent personal-org lookup. Returns `RehomeResponse`: the project after the move plus `rehome.{from_org, to_org, audit_ids}` (snake_case by contract; `audit_ids` is `[]` today because the platform ACL returns no ids — the ledger is the durable record). Body is built as the API's `.strict()` schema wants it: `reason` is omitted, never sent as null.
|
|
43
|
+
- **`rehomeRefusalReason(err)`** and `REHOME_REFUSAL_REASONS` — reads `details.reason` on the 400/409 refusals (`same_org`, `moved_during_request`, `deadlock_retry`, `concurrent_modification`, `name_collision`, `soft_deleted_conflict`, `rehomed_away_conflict`, `export_in_progress`, `project_soft_deleted`, `project_has_no_org`) and returns `null` for anything else, an unknown reason included. Enumerated because the Phase 4 runbook (spec §4.7) assigns each a disposition and a script that types the strings itself will misspell one.
|
|
44
|
+
- **`orgs.getVisibleAuditLog(slug, { cursor?, limit? })`** — the D19 feed (`GET /orgs/:slug/audit-log/global`): rows a writer marked `visibility: 'org'`, readable by any member; today, projects leaving the org for a personal org. New `orgs` namespace (reads only). **`readRehomeAuditDetails(entry)`** narrows an entry's `details` to the re-home writer's shape (`RehomeAuditDetails`) or `null`, so a renderer can print a move in one line and everything else raw.
|
|
45
|
+
- **`admin.*` namespace** — `rehomeProject(projectId, { targetOrg, reason })` (the D8 platform path: any project, any orgs, by UUID, `reason` REQUIRED), `listProjectRehomes(query?)` (the reservation/redirect table), `listProjectRehomeEvents(query?)` (the D21 append-only ledger, paged by `seq`; unknown event kinds parse verbatim), `releaseProjectRehome(rehomeId)`. All four need the PLATFORM admin role (`403 INSUFFICIENT_ROLE` otherwise); the two writes are session-only (D20). This is the Phase 4 migration script's substrate; no MCP tool wraps it, on purpose.
|
|
46
|
+
- **`SESSION_REQUIRED`** / **`isSessionRequiredError`** and **`INSUFFICIENT_ROLE`** — the two 403 codes the admin path adds to the org-routing set.
|
|
47
|
+
- **MFA login.** `login()` / `auth.login()` now throw **`MfaRequiredError`** (`mfaChallengeToken`, `expiresAt`, `mfaMethods`; guard `isMfaRequiredError`) when `POST /auth/login` answers the challenge shape, and **`OpsClient.loginWithTotp(challengeToken, code)`** / `auth.totpLogin()` complete it (`POST /auth/totp/login`) and install the session. Not a nice-to-have: D20 makes the admin re-home route session-only and the operator account may be MFA-enrolled, and the SDK could not log such an account in at all — see Fixed. A TOTP-installed session carries no password, so it is NOT auto-refreshed; on expiry requests fail 401 and the caller logs in again (a migration script treats that as "stop", never "retry with the key").
|
|
48
|
+
- `ProjectResponseSchema` gains **`orgId`** (nullable, optional). The API has emitted it on every project read since org scoping landed; `z.object()` was stripping it, so a re-homed project's new org was invisible through the SDK.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **An MFA-enrolled account could not log in through the SDK.** `LoginResponseSchema` required `sessionToken`, and the API's `200 { mfa_required: true, mfa_challenge_token, ... }` challenge body therefore surfaced as a raw `ZodError` on `sessionToken` — a parse failure on a successful request, with the challenge token discarded. The challenge is now parsed first (narrower literal) and thrown as `MfaRequiredError`; a session body still parses (tested as the control).
|
|
53
|
+
|
|
54
|
+
### Notes
|
|
55
|
+
|
|
56
|
+
- Live-verified 2026-09-15 against the current API build on a prod-copy database, not only against nock: member round trip personal → team → personal, `410 PROJECT_REHOMED` at the vacated address on a write (reads there 404 — D14 is a write-path check), `same_org`, the source-scope 404, C6 (owner moving into their own personal org), annihilation on reversal, the D19 feed on both orgs with a non-member `ORG_ACCESS_DENIED` control, and the key→session login ladder. The admin ledger schemas were checked against the controller and nock only — the smoke user was not a platform admin (`INSUFFICIENT_ROLE` on all four, which also fixes the middleware order: platform role before the session gate).
|
|
57
|
+
- Spec §4.1 says the response carries `orgSlug`; the API emits `orgId` and the slug inside `rehome.to_org`. The schema follows the wire; the spec changelog records the drift.
|
|
58
|
+
|
|
7
59
|
## [6.3.1] - 2026-09-13
|
|
8
60
|
|
|
9
61
|
### Security
|
package/README.md
CHANGED
|
@@ -95,6 +95,8 @@ for (const [domain, trend] of Object.entries(burndown.trends)) {
|
|
|
95
95
|
- [Issue Operations](#issue-operations)
|
|
96
96
|
- [Analytics Operations](#analytics-operations)
|
|
97
97
|
- [Taxonomy Operations](#taxonomy-operations)
|
|
98
|
+
- [Org Operations](#org-operations)
|
|
99
|
+
- [Admin Operations](#admin-operations)
|
|
98
100
|
- [Health Check](#health-check)
|
|
99
101
|
- [Environment Variables](#environment-variables)
|
|
100
102
|
- [Error Handling](#error-handling)
|
|
@@ -170,7 +172,7 @@ console.log(client.getAuthType()); // 'api_key'
|
|
|
170
172
|
|
|
171
173
|
### Session-Based Authentication
|
|
172
174
|
|
|
173
|
-
For interactive applications, use `client.login()` which
|
|
175
|
+
For interactive applications, use `client.login()` which installs the session and, by default, re-logs in on a 401:
|
|
174
176
|
|
|
175
177
|
```typescript
|
|
176
178
|
import { OpsClient } from '@uluops/ops-sdk';
|
|
@@ -192,6 +194,43 @@ await client.logout();
|
|
|
192
194
|
|
|
193
195
|
> **Note:** Prefer `client.login()` over `client.auth.login()`. The latter only returns the token without installing it, requiring manual client construction.
|
|
194
196
|
|
|
197
|
+
**Accounts with MFA (6.4.0).** For an account with TOTP or a passkey enrolled, `POST /auth/login`
|
|
198
|
+
answers a *challenge*, not a session, and `client.login()` throws `MfaRequiredError` carrying
|
|
199
|
+
`mfaChallengeToken`, `expiresAt` and `mfaMethods`. Complete it with the current code:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { isMfaRequiredError } from '@uluops/ops-sdk';
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
await client.login(email, password);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
if (!isMfaRequiredError(err)) throw err;
|
|
208
|
+
await client.loginWithTotp(err.mfaChallengeToken, '123456'); // installs the session
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**The challenge token is single-use and is consumed before the code is checked** — a mistyped
|
|
213
|
+
code burns it; do not loop on `loginWithTotp` with the same token, call `login()` again for a fresh
|
|
214
|
+
challenge. A TOTP-installed session has no password to re-login with, so it is **not**
|
|
215
|
+
auto-refreshed: when it expires, requests fail `401` and you log in again. (Before 6.4.0 an MFA
|
|
216
|
+
account could not log in through `login()` at all — the challenge body failed the session schema
|
|
217
|
+
with a `ZodError`.) `MfaRequiredError` is raised by `login()` / `auth.login()` **only**: a client
|
|
218
|
+
constructed with `{ email, password }` (or autoloaded credentials) logs in inside sdk-core, which
|
|
219
|
+
cannot see the challenge and surfaces a generic `UnauthorizedError` — MFA accounts must use the
|
|
220
|
+
two-step path. WebAuthn completion is not offered here.
|
|
221
|
+
|
|
222
|
+
**What "auto-refresh" does — read before scripting against a session.** With the default
|
|
223
|
+
`login()`, the password is kept so sdk-core can re-login when a request answers 401. That refresh
|
|
224
|
+
is a *fresh login*: under the API's default single-session policy it **revokes the user's other
|
|
225
|
+
sessions** (your dashboard tab); **mutations are not retried** afterwards (the POST that hit the 401
|
|
226
|
+
still throws it); and the budget is **one** (the password is cleared after the first re-login).
|
|
227
|
+
For a script — the Phase 4 migration, anything that shares the account with a dashboard — pass
|
|
228
|
+
`{ autoRefresh: false }` so a 401 means *stop*, untouched:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
await client.login(email, password, { autoRefresh: false }); // session without a password → no re-login
|
|
232
|
+
```
|
|
233
|
+
|
|
195
234
|
### Credential Priority Chain
|
|
196
235
|
|
|
197
236
|
The SDK loads credentials in the following order:
|
|
@@ -347,6 +386,8 @@ Three org-routing errors are worth branching on (all exported with type guards):
|
|
|
347
386
|
| `INSUFFICIENT_ORG_ROLE` | 403 | `isInsufficientOrgRoleError` | Your role in that org is below `publisher`. **Do not retry without `org`** — an org-less retry files the work in your personal org; the API says so in the body (`details.applied: false`). |
|
|
348
387
|
| `ORG_ACCESS_DENIED` | 403 | `isOrgAccessDeniedError` | Not a member of that org, or your key is bound to a different one. Terminal. |
|
|
349
388
|
| `PROJECT_REHOMED` | 410 | `isProjectRehomedError` | The project moved orgs. `err.details.target_org.slug` is where it lives — pass it as `org` and retry the same call. |
|
|
389
|
+
| `SESSION_REQUIRED` | 403 | `isSessionRequiredError` | A session-only admin route refused an API key (D20). Log in; never mint another key to get past it. |
|
|
390
|
+
| `INSUFFICIENT_ROLE` | 403 | `isInsufficientRoleError` | The admin path needs the *platform* role (`users.role = admin`); no `org` argument changes it. Terminal. |
|
|
350
391
|
|
|
351
392
|
Low-level: `new OpsHttpClient(cfg).withOrg('acme')` returns a view of the client scoped to that org.
|
|
352
393
|
|
|
@@ -809,6 +850,52 @@ const result = await client.projects.mergeIssues('my-project', {
|
|
|
809
850
|
});
|
|
810
851
|
```
|
|
811
852
|
|
|
853
|
+
#### `client.projects.rehome(idOrName, input, options?)` — move a project to another org
|
|
854
|
+
|
|
855
|
+
Moves the project and its whole history (runs, issues, analytics) into another org
|
|
856
|
+
(project-org-routing-and-rehome spec §4.1, D14). **The source org is this call's org scope** —
|
|
857
|
+
pass `{ org: '<source>' }` (or set the client `orgSlug`) unless the project is in your personal
|
|
858
|
+
org; the project is looked up *there*, and an unscoped call for a work-org project is a 404. You
|
|
859
|
+
need `admin`/`owner` in both orgs; a personal org as *target* only when it is yours.
|
|
860
|
+
|
|
861
|
+
| Parameter | Type | Required | Description |
|
|
862
|
+
|-----------|------|----------|-------------|
|
|
863
|
+
| `targetOrg` | `string` | Yes | Slug of the destination org |
|
|
864
|
+
| `reason` | `string` | No | ≤ 500 chars; stored on the tombstone, never rendered into an error |
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
const moved = await client.projects.rehome('billing', { targetOrg: 'ulu-labs', reason: 'team took it over' }, { org: 'acme' });
|
|
868
|
+
moved.orgId; // the target org's id
|
|
869
|
+
moved.rehome.from_org; // { id, slug: 'acme' }
|
|
870
|
+
moved.rehome.to_org; // { id, slug: 'ulu-labs' }
|
|
871
|
+
moved.rehome.audit_ids; // [] today — the ledger, not this array, is the durable record
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
After the move the old `(org, name)` address is a **tombstone**: an org-less *write* naming the
|
|
875
|
+
project there (a `save` or a `create`) gets `410 PROJECT_REHOMED` naming the target
|
|
876
|
+
(`isProjectRehomedError`) instead of silently forking a new project. Reads at the old address 404.
|
|
877
|
+
Moving back is an ordinary `rehome` the other way and annihilates the tombstone.
|
|
878
|
+
|
|
879
|
+
Refusals a caller branches on — `rehomeRefusalReason(err)` reads the enumerated
|
|
880
|
+
`details.reason` values and returns `null` for anything else:
|
|
881
|
+
|
|
882
|
+
| Answer | Meaning | Disposition |
|
|
883
|
+
|---|---|---|
|
|
884
|
+
| `400` `same_org` | already there | done (idempotent re-run) |
|
|
885
|
+
| `409` `moved_during_request` / `deadlock_retry` / `concurrent_modification` | lost a race | re-read, retry once |
|
|
886
|
+
| `409` `name_collision` / `soft_deleted_conflict` / `rehomed_away_conflict` | the name is taken in the target (live, soft-deleted, or reserved by another move) | skip and report |
|
|
887
|
+
| `409` `export_in_progress` | an export holds one of the orgs | wait, retry |
|
|
888
|
+
| `409` `project_soft_deleted` | the project is soft-deleted in the source | restore it there first, then move |
|
|
889
|
+
| `400` `project_has_no_org` | pre-org legacy row | stop; an operator repairs the row |
|
|
890
|
+
| `403` `INSUFFICIENT_ORG_ROLE` / `ORG_ACCESS_DENIED` / `ORG_SUSPENDED` | authority (source role, target membership or a personal target, C3) | stop |
|
|
891
|
+
| `402` `PROJECT_LIMIT` | target org at its project cap | stop |
|
|
892
|
+
| *no HTTP answer* (`isTimeoutError` / `isNetworkError`; `rehomeRefusalReason` → `null`) | the server may have committed the move before the response was lost | do **not** retry blind — read the project with `{ org: targetOrg }` (or the admin ledger); on the member path a blind retry is a 404, not `same_org` |
|
|
893
|
+
|
|
894
|
+
**Re-runs.** `same_org` is the idempotence signal of the **admin** path (lookup by id, unscoped).
|
|
895
|
+
On the member path the lookup is source-scoped, so the same call after the move answers **404**
|
|
896
|
+
(the source address is a tombstone) — not `same_org`. A member-path script that wants "already
|
|
897
|
+
done" checks the target org, not the refusal code.
|
|
898
|
+
|
|
812
899
|
---
|
|
813
900
|
|
|
814
901
|
### Run Operations
|
|
@@ -1823,6 +1910,108 @@ console.log('Priorities:', taxonomy.priorities);
|
|
|
1823
1910
|
|
|
1824
1911
|
---
|
|
1825
1912
|
|
|
1913
|
+
### Org Operations
|
|
1914
|
+
|
|
1915
|
+
Reads any **member** of an org can make. (Org CRUD, membership and invitations are dashboard
|
|
1916
|
+
surfaces and are not wrapped by this SDK.)
|
|
1917
|
+
|
|
1918
|
+
#### `client.orgs.getVisibleAuditLog(slug, query?)` — the org-visible audit feed (D19)
|
|
1919
|
+
|
|
1920
|
+
The subset of an org's audit log that any member may read: rows whose writer marked
|
|
1921
|
+
`details.visibility = 'org'`. Today that is one class — a project **leaving this org for someone's
|
|
1922
|
+
personal org** (an org admin may do that; before D19 the org's owner could not see it). The full
|
|
1923
|
+
audit log stays admin+ and is not wrapped here.
|
|
1924
|
+
|
|
1925
|
+
| Parameter | Type | Description |
|
|
1926
|
+
|-----------|------|-------------|
|
|
1927
|
+
| `cursor` | `string` | Opaque keyset cursor — pass a prior page's `nextCursor` back verbatim |
|
|
1928
|
+
| `limit` | `number` | Page size, 1–100 — the API answers 400 outside that range (it does not clamp) |
|
|
1929
|
+
|
|
1930
|
+
```typescript
|
|
1931
|
+
import { readRehomeAuditDetails } from '@uluops/ops-sdk';
|
|
1932
|
+
|
|
1933
|
+
const feed = await client.orgs.getVisibleAuditLog('acme', { limit: 50 });
|
|
1934
|
+
for (const entry of feed.data.entries) {
|
|
1935
|
+
const move = readRehomeAuditDetails(entry); // null for rows written by anything else
|
|
1936
|
+
if (move) console.log(`${move.project_name}: ${move.from_org.slug} → ${move.to_org.slug} (${move.action})`);
|
|
1937
|
+
}
|
|
1938
|
+
// feed.count is the page size, feed.hasMore / feed.nextCursor page it
|
|
1939
|
+
```
|
|
1940
|
+
|
|
1941
|
+
`action` on every row is the platform's closed enum (`org.updated`); the real event is
|
|
1942
|
+
`details.action` (`project.rehome_out` on the source org's feed, `project.rehome_in` on the
|
|
1943
|
+
target's). A non-member gets `403 ORG_ACCESS_DENIED`.
|
|
1944
|
+
|
|
1945
|
+
---
|
|
1946
|
+
|
|
1947
|
+
### Admin Operations
|
|
1948
|
+
|
|
1949
|
+
The **platform-admin** surface (`users.role = 'admin'` — a platform role, not an org role). The two
|
|
1950
|
+
writes additionally require a **login-issued session**: an API key, even an admin's, answers
|
|
1951
|
+
`403 SESSION_REQUIRED` (`isSessionRequiredError`) — that is D20, the compensation for a stolen
|
|
1952
|
+
admin key being able to move any project anywhere in one call. Log in with `client.login()` (and
|
|
1953
|
+
`loginWithTotp()` if challenged) and the session bearer is what these calls send. A non-admin
|
|
1954
|
+
principal gets `403 INSUFFICIENT_ROLE` on all four. There is deliberately no MCP tool for any of
|
|
1955
|
+
this.
|
|
1956
|
+
|
|
1957
|
+
#### `client.admin.rehomeProject(projectId, { targetOrg, reason })` — session only
|
|
1958
|
+
|
|
1959
|
+
Move **any** project between **any** two orgs by UUID; no source- or target-membership
|
|
1960
|
+
requirement, so `reason` is **required** (on this path the target org's only standing is the audit
|
|
1961
|
+
row plus that text). Resolves the project unscoped — the route takes a UUID and the SDK refuses a
|
|
1962
|
+
name client-side (`InputValidationError`). Answers the same refusals as the member path; here
|
|
1963
|
+
`same_org` does mean "already done" (the lookup is by id, so the moved project is found wherever
|
|
1964
|
+
it is), which is what makes a scripted pass over a list idempotent.
|
|
1965
|
+
|
|
1966
|
+
```typescript
|
|
1967
|
+
await client.login(email, password, { autoRefresh: false }); // or loginWithTotp(...) after an MfaRequiredError
|
|
1968
|
+
try {
|
|
1969
|
+
const moved = await client.admin.rehomeProject(projectId, { targetOrg: 'ulu-labs', reason: 'OQ-4 row 7' });
|
|
1970
|
+
console.log(moved.rehome.from_org.slug, '→', moved.rehome.to_org.slug);
|
|
1971
|
+
} catch (err) {
|
|
1972
|
+
if (rehomeRefusalReason(err) === 'same_org') { /* done on a previous run */ }
|
|
1973
|
+
else throw err;
|
|
1974
|
+
}
|
|
1975
|
+
```
|
|
1976
|
+
|
|
1977
|
+
#### `client.admin.listProjectRehomes(query?)` — key-readable
|
|
1978
|
+
|
|
1979
|
+
The **current redirect table**: one row per vacated `(sourceOrgId, name)` and the `targetOrgId` it
|
|
1980
|
+
points at. A reversal annihilates its row and a release deletes it, so this is state, not history.
|
|
1981
|
+
`org` takes a UUID or a slug; `total` is the matching count and `returned` the page.
|
|
1982
|
+
|
|
1983
|
+
```typescript
|
|
1984
|
+
const { data, total } = await client.admin.listProjectRehomes({ org: 'system', limit: 100 });
|
|
1985
|
+
// data[0] → { id, sourceOrgId, name, projectId, targetOrgId, actorId, reason, createdAt }
|
|
1986
|
+
```
|
|
1987
|
+
|
|
1988
|
+
#### `client.admin.listProjectRehomeEvents(query?)` — key-readable
|
|
1989
|
+
|
|
1990
|
+
The **append-only ledger** (D21): every `moved`, `repointed`, `annihilated`, `degenerate_dropped`,
|
|
1991
|
+
`released` and `hard_deleted` event, written in the same transaction as the change; nothing the API
|
|
1992
|
+
offers removes a row. Pages by `seq` (insertion order) — `nextCursor` is the `seq` to continue
|
|
1993
|
+
below; pass it back verbatim. **Reconcile a migration against this**, not against the reservation
|
|
1994
|
+
table and not against the script's own log. Unknown future event kinds parse verbatim
|
|
1995
|
+
(`PROJECT_REHOME_EVENT_KINDS` lists the known six).
|
|
1996
|
+
|
|
1997
|
+
```typescript
|
|
1998
|
+
const page = await client.admin.listProjectRehomeEvents({ org: 'ulu-labs', event: 'moved', limit: 100 });
|
|
1999
|
+
page.data[0]; // { seq, event, projectId, projectName, fromOrgId, toOrgId, viaAdminPath, reason, createdAt, ... }
|
|
2000
|
+
```
|
|
2001
|
+
|
|
2002
|
+
#### `client.admin.releaseProjectRehome(rehomeId)` — session only
|
|
2003
|
+
|
|
2004
|
+
Release a vacated address so the old `(org, name)` is creatable again — a by-name writer there then
|
|
2005
|
+
gets a **new** project instead of a 410. Audited (a `released` ledger row); never done by time.
|
|
2006
|
+
|
|
2007
|
+
```typescript
|
|
2008
|
+
await client.admin.releaseProjectRehome(reservationId); // → { released: true }
|
|
2009
|
+
```
|
|
2010
|
+
A `ZodError` here means the 200 body was not `{ released: true }` — and since the server answers
|
|
2011
|
+
after the row is gone, the release most likely happened; re-read the listing before retrying.
|
|
2012
|
+
|
|
2013
|
+
---
|
|
2014
|
+
|
|
1826
2015
|
### Health Check
|
|
1827
2016
|
|
|
1828
2017
|
Check API server status. This endpoint does not require authentication.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,17 +1,47 @@
|
|
|
1
1
|
import { type HttpClientConfig } from './http/http-client.js';
|
|
2
2
|
import * as analyticsOps from './operations/analytics.js';
|
|
3
|
-
import type { RegisterInput, LoginInput, LoginResponse, RegisterResponse, UpdateProfileInput, ChangePasswordInput, ResetPasswordInput, CreateApiKeyInput, ApiKeyCreatedResponse, PublicApiKey, PublicSession, AuthUser, PublicUser } from './types/auth.js';
|
|
3
|
+
import type { RegisterInput, LoginInput, TotpLoginInput, LoginResponse, RegisterResponse, UpdateProfileInput, ChangePasswordInput, ResetPasswordInput, CreateApiKeyInput, ApiKeyCreatedResponse, PublicApiKey, PublicSession, AuthUser, PublicUser } from './types/auth.js';
|
|
4
4
|
import type { Project, CreateProjectInput, UpdateProjectInput, DeleteProjectInput, RenameProjectInput, ProjectSummaryResponse, ProjectTrends, ProjectTrendsQuery, ListProjectIssuesQuery, BulkIssueStatusUpdate, BulkIssueStatusResult, MergeIssuesInput, MergeIssuesResult, MergeProjectsInput, MergeProjectsResult } from './types/projects.js';
|
|
5
5
|
import type { Run, RunSummary, RunAnalysis, ProjectAnalysisList, AnalysisRecordsList, AgentRunsAnalysis, SaveRunInput, SaveRunResponse, ValidateRunResponse, RunDiffQuery, RunDiffResult, ArchiveRunsInput, ArchiveRunsResult, UpdateRunInput, UpdateRunByNumberInput, UpdateRunPreviewInput, UpdateRunPreviewByNumberInput, RunUpdatePreview, UpdateRunWithEchoResult, ListRunsQuery, RunDetails, ProjectAnalysisQuery, AnalysisRecordsQuery, AgentRunsAnalysisQuery, RunWriteEcho } from './types/runs.js';
|
|
6
6
|
import type { Issue, CreateUserIssueInput, UpdateIssueInput, UpdateIssueStatusInput, CreateIssueNoteInput, IssueNote, IssueDetails, IssueHistoryEnvelope, IssueSearchQuery, ListIssuesQuery, BulkStatusUpdateItem, StatusUpdateResult } from './types/issues.js';
|
|
7
7
|
import type { AnalyticsQuery, AgentPerformance, AgentInfo, AgentLifecycleEntry, AgentReliabilityQuery, BurndownQuery, VelocityQuery, DiscoveryQuery, AgentMatrixQuery, TaxonomyResponse, AgentReliabilityResult, ResolutionRateResult, FileHotspotResult, TaxonomyDistributionResult, FullTaxonomyAnalyticsResult, BurndownResultResponse, VelocityResultResponse, DiscoveryResultResponse, AgentMatrixResultResponse, TrendSummaryResult } from './types/analytics.js';
|
|
8
8
|
import type { MessageResponse, DeleteResult } from './types/responses.js';
|
|
9
9
|
import type { OrgScopedOptions, RunCallOptions } from './types/org.js';
|
|
10
|
+
import type { RehomeProjectInput, AdminRehomeProjectInput, RehomeResponse, ProjectRehomeList, ProjectRehomeListQuery, ProjectRehomeEventList, ProjectRehomeEventListQuery, OrgAuditFeed, OrgAuditFeedQuery } from './types/rehome.js';
|
|
10
11
|
/**
|
|
11
12
|
* OpsClient configuration options.
|
|
12
13
|
* All HTTP-level options (baseUrl, timeout, retries, debug, auth) are available.
|
|
14
|
+
*
|
|
15
|
+
* Note on `email`/`password` in the constructor: the login then happens inside
|
|
16
|
+
* sdk-core, which cannot see an MFA challenge — an MFA-enrolled account gets a
|
|
17
|
+
* generic `UnauthorizedError`, not `MfaRequiredError`. Use `login()` /
|
|
18
|
+
* `loginWithTotp()` for those accounts.
|
|
13
19
|
*/
|
|
14
20
|
export type OpsClientConfig = HttpClientConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Options for `OpsClient.login`.
|
|
23
|
+
*
|
|
24
|
+
* `autoRefresh` (default `true`) keeps the password on the installed session
|
|
25
|
+
* so sdk-core can re-login when a request answers 401. Three properties of
|
|
26
|
+
* that refresh are not obvious from the word "automatic", and they matter for
|
|
27
|
+
* a script driving admin writes under a session (spec §4.7):
|
|
28
|
+
*
|
|
29
|
+
* - it is a **fresh login**, and under the API's default single-session
|
|
30
|
+
* policy (`AUTH_ALLOW_CONCURRENT_SESSIONS` unset) a login **revokes the
|
|
31
|
+
* user's other sessions** — the operator's dashboard tab dies each time;
|
|
32
|
+
* - **mutations are not retried** after a refresh: the POST/DELETE that hit
|
|
33
|
+
* the 401 still throws it, so the caller sees the failure anyway;
|
|
34
|
+
* - the budget is **one**: the password is cleared after the first re-login,
|
|
35
|
+
* so the second 401 is terminal with a different message.
|
|
36
|
+
*
|
|
37
|
+
* Pass `{ autoRefresh: false }` when a 401 must mean "stop" (a migration
|
|
38
|
+
* script, anything sharing the account with a dashboard). The session is
|
|
39
|
+
* then installed without credentials, exactly as `loginWithTotp` installs
|
|
40
|
+
* its session, and a 401 surfaces untouched.
|
|
41
|
+
*/
|
|
42
|
+
export interface LoginOptions {
|
|
43
|
+
autoRefresh?: boolean;
|
|
44
|
+
}
|
|
15
45
|
/**
|
|
16
46
|
* Main SDK client for the UluOps platform API.
|
|
17
47
|
*
|
|
@@ -45,7 +75,19 @@ export declare class OpsClient {
|
|
|
45
75
|
* the client for authenticated requests with token auto-refresh. `client.auth.login()`
|
|
46
76
|
* only returns the token without installing it.
|
|
47
77
|
*/
|
|
48
|
-
login(email: string, password: string): Promise<LoginResponse>;
|
|
78
|
+
login(email: string, password: string, options?: LoginOptions): Promise<LoginResponse>;
|
|
79
|
+
/**
|
|
80
|
+
* Complete an MFA-challenged login with a TOTP code and install the session.
|
|
81
|
+
*
|
|
82
|
+
* `login()` throws `MfaRequiredError` for an account with TOTP or a passkey
|
|
83
|
+
* enrolled; pass its `mfaChallengeToken` here with the current six-digit code
|
|
84
|
+
* before `expiresAt`. The installed session has NO password to re-login with
|
|
85
|
+
* (a re-login would only produce another challenge), so it is not
|
|
86
|
+
* auto-refreshed: when it expires, requests fail `401` and the caller logs in
|
|
87
|
+
* again. A Phase 4 migration script should treat that 401 as "stop", not
|
|
88
|
+
* "retry with the key" (spec §4.7).
|
|
89
|
+
*/
|
|
90
|
+
loginWithTotp(mfaChallengeToken: string, code: string, rememberMe?: boolean): Promise<LoginResponse>;
|
|
49
91
|
/**
|
|
50
92
|
* Logout current session (revokes all sessions for this user).
|
|
51
93
|
*
|
|
@@ -78,8 +120,10 @@ export declare class OpsClient {
|
|
|
78
120
|
readonly auth: {
|
|
79
121
|
/** Register a new user account. */
|
|
80
122
|
register: (input: RegisterInput) => Promise<RegisterResponse>;
|
|
81
|
-
/** Log in with email + password, returning a session token. */
|
|
123
|
+
/** Log in with email + password, returning a session token. Throws `MfaRequiredError` for an MFA-enrolled account. */
|
|
82
124
|
login: (input: LoginInput) => Promise<LoginResponse>;
|
|
125
|
+
/** Complete an MFA challenge with a TOTP code, returning the session (not installed — see `OpsClient.loginWithTotp`). */
|
|
126
|
+
totpLogin: (input: TotpLoginInput) => Promise<LoginResponse>;
|
|
83
127
|
/** Revoke all active sessions for the current user. */
|
|
84
128
|
logoutAll: () => Promise<{
|
|
85
129
|
sessionsRevoked: number;
|
|
@@ -163,6 +207,45 @@ export declare class OpsClient {
|
|
|
163
207
|
* Pairwise only. Dry-run first: `{ ...input, dryRun: true }`.
|
|
164
208
|
*/
|
|
165
209
|
mergeProjects: (input: MergeProjectsInput, options?: OrgScopedOptions) => Promise<MergeProjectsResult>;
|
|
210
|
+
/**
|
|
211
|
+
* Move a project to another org (project-org-routing-and-rehome §4.1, D14).
|
|
212
|
+
* The SOURCE is this call's org scope — pass `{ org: '<source>' }` (or set
|
|
213
|
+
* the client `orgSlug`) when the project is not in your personal org; the
|
|
214
|
+
* project is looked up THERE. Needs `admin`/`owner` in both orgs. Old
|
|
215
|
+
* address becomes a `410 PROJECT_REHOMED` tombstone, not a fork.
|
|
216
|
+
*/
|
|
217
|
+
rehome: (idOrName: string, input: RehomeProjectInput, options?: OrgScopedOptions) => Promise<RehomeResponse>;
|
|
218
|
+
};
|
|
219
|
+
/** Org reads a member can make. (Org CRUD and membership are dashboard/platform surfaces, not wrapped here.) */
|
|
220
|
+
readonly orgs: {
|
|
221
|
+
/**
|
|
222
|
+
* The org-visible audit feed (D19) — rows a writer marked `visibility: 'org'`,
|
|
223
|
+
* readable by any member; today, projects leaving this org for a personal
|
|
224
|
+
* org. `nextCursor` is opaque; pass it back verbatim. Narrow entries with
|
|
225
|
+
* `readRehomeAuditDetails()`.
|
|
226
|
+
*/
|
|
227
|
+
getVisibleAuditLog: (slug: string, query?: OrgAuditFeedQuery) => Promise<OrgAuditFeed>;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Platform-admin surface. Requires `users.role = 'admin'`; the writes also
|
|
231
|
+
* require a login-issued SESSION (D20) — an API key gets `403 SESSION_REQUIRED`
|
|
232
|
+
* (`isSessionRequiredError`). Log in with `login()` / `loginWithTotp()` first.
|
|
233
|
+
* Deliberately not exposed as MCP tools.
|
|
234
|
+
*/
|
|
235
|
+
readonly admin: {
|
|
236
|
+
/**
|
|
237
|
+
* Move ANY project between ANY orgs by UUID — `reason` required, session
|
|
238
|
+
* only. `same_org` (via `rehomeRefusalReason`) means already done.
|
|
239
|
+
*/
|
|
240
|
+
rehomeProject: (projectId: string, input: AdminRehomeProjectInput) => Promise<RehomeResponse>;
|
|
241
|
+
/** The current redirect table: every vacated `(org, name)` and where it points. Key-readable. */
|
|
242
|
+
listProjectRehomes: (query?: ProjectRehomeListQuery) => Promise<ProjectRehomeList>;
|
|
243
|
+
/** The append-only re-home ledger (D21), paged by `seq`. Key-readable. Reconcile migrations against THIS. */
|
|
244
|
+
listProjectRehomeEvents: (query?: ProjectRehomeEventListQuery) => Promise<ProjectRehomeEventList>;
|
|
245
|
+
/** Release a vacated address so the old name is creatable again (audited; never by time). Session only. */
|
|
246
|
+
releaseProjectRehome: (rehomeId: string) => Promise<{
|
|
247
|
+
released: true;
|
|
248
|
+
}>;
|
|
166
249
|
};
|
|
167
250
|
/** Execution run save, preview, diff, archive, and retrieval */
|
|
168
251
|
readonly runs: {
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAS7E,OAAO,KAAK,YAAY,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -9,6 +9,8 @@ import * as runOps from './operations/runs.js';
|
|
|
9
9
|
import * as issueOps from './operations/issues.js';
|
|
10
10
|
import * as analyticsOps from './operations/analytics.js';
|
|
11
11
|
import * as taxonomyOps from './operations/taxonomy.js';
|
|
12
|
+
import * as orgOps from './operations/orgs.js';
|
|
13
|
+
import * as adminOps from './operations/admin.js';
|
|
12
14
|
/**
|
|
13
15
|
* Main SDK client for the UluOps platform API.
|
|
14
16
|
*
|
|
@@ -70,11 +72,30 @@ export class OpsClient {
|
|
|
70
72
|
* the client for authenticated requests with token auto-refresh. `client.auth.login()`
|
|
71
73
|
* only returns the token without installing it.
|
|
72
74
|
*/
|
|
73
|
-
async login(email, password) {
|
|
75
|
+
async login(email, password, options = {}) {
|
|
74
76
|
const response = await authOps.login(this.httpClient, { email, password });
|
|
75
|
-
// Install session auth so subsequent requests are authenticated.
|
|
76
|
-
//
|
|
77
|
-
|
|
77
|
+
// Install session auth so subsequent requests are authenticated. With
|
|
78
|
+
// `autoRefresh` (the default) the password is kept so sdk-core can re-login
|
|
79
|
+
// on a 401 — see LoginOptions for what that refresh actually does and when
|
|
80
|
+
// to turn it off.
|
|
81
|
+
this.httpClient.setAuthStrategy(new JwtSessionAuth(this.httpClient.createFetchClient(), options.autoRefresh === false ? { email: '', password: '' } : { email, password }, undefined, response.sessionToken));
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Complete an MFA-challenged login with a TOTP code and install the session.
|
|
86
|
+
*
|
|
87
|
+
* `login()` throws `MfaRequiredError` for an account with TOTP or a passkey
|
|
88
|
+
* enrolled; pass its `mfaChallengeToken` here with the current six-digit code
|
|
89
|
+
* before `expiresAt`. The installed session has NO password to re-login with
|
|
90
|
+
* (a re-login would only produce another challenge), so it is not
|
|
91
|
+
* auto-refreshed: when it expires, requests fail `401` and the caller logs in
|
|
92
|
+
* again. A Phase 4 migration script should treat that 401 as "stop", not
|
|
93
|
+
* "retry with the key" (spec §4.7).
|
|
94
|
+
*/
|
|
95
|
+
async loginWithTotp(mfaChallengeToken, code, rememberMe) {
|
|
96
|
+
const response = await authOps.totpLogin(this.httpClient, { mfaChallengeToken, code, rememberMe });
|
|
97
|
+
this.httpClient.setAuthStrategy(new JwtSessionAuth(this.httpClient.createFetchClient(), { email: '', password: '' }, // no credentials → no refresh (same shape createAuthStrategy uses for a bare sessionToken)
|
|
98
|
+
undefined, response.sessionToken));
|
|
78
99
|
return response;
|
|
79
100
|
}
|
|
80
101
|
/**
|
|
@@ -120,8 +141,10 @@ export class OpsClient {
|
|
|
120
141
|
auth = {
|
|
121
142
|
/** Register a new user account. */
|
|
122
143
|
register: (input) => authOps.register(this.httpClient, input),
|
|
123
|
-
/** Log in with email + password, returning a session token. */
|
|
144
|
+
/** Log in with email + password, returning a session token. Throws `MfaRequiredError` for an MFA-enrolled account. */
|
|
124
145
|
login: (input) => authOps.login(this.httpClient, input),
|
|
146
|
+
/** Complete an MFA challenge with a TOTP code, returning the session (not installed — see `OpsClient.loginWithTotp`). */
|
|
147
|
+
totpLogin: (input) => authOps.totpLogin(this.httpClient, input),
|
|
125
148
|
/** Revoke all active sessions for the current user. */
|
|
126
149
|
logoutAll: () => authOps.logoutAll(this.httpClient),
|
|
127
150
|
/** Trigger a password-reset email for the given address. */
|
|
@@ -193,6 +216,49 @@ export class OpsClient {
|
|
|
193
216
|
* Pairwise only. Dry-run first: `{ ...input, dryRun: true }`.
|
|
194
217
|
*/
|
|
195
218
|
mergeProjects: (input, options) => projectOps.mergeProjects(this.scope(options), input),
|
|
219
|
+
/**
|
|
220
|
+
* Move a project to another org (project-org-routing-and-rehome §4.1, D14).
|
|
221
|
+
* The SOURCE is this call's org scope — pass `{ org: '<source>' }` (or set
|
|
222
|
+
* the client `orgSlug`) when the project is not in your personal org; the
|
|
223
|
+
* project is looked up THERE. Needs `admin`/`owner` in both orgs. Old
|
|
224
|
+
* address becomes a `410 PROJECT_REHOMED` tombstone, not a fork.
|
|
225
|
+
*/
|
|
226
|
+
rehome: (idOrName, input, options) => projectOps.rehome(this.scope(options), idOrName, input),
|
|
227
|
+
};
|
|
228
|
+
// ============================================
|
|
229
|
+
// ORG OPERATIONS
|
|
230
|
+
// ============================================
|
|
231
|
+
/** Org reads a member can make. (Org CRUD and membership are dashboard/platform surfaces, not wrapped here.) */
|
|
232
|
+
orgs = {
|
|
233
|
+
/**
|
|
234
|
+
* The org-visible audit feed (D19) — rows a writer marked `visibility: 'org'`,
|
|
235
|
+
* readable by any member; today, projects leaving this org for a personal
|
|
236
|
+
* org. `nextCursor` is opaque; pass it back verbatim. Narrow entries with
|
|
237
|
+
* `readRehomeAuditDetails()`.
|
|
238
|
+
*/
|
|
239
|
+
getVisibleAuditLog: (slug, query) => orgOps.getVisibleAuditLog(this.httpClient, slug, query),
|
|
240
|
+
};
|
|
241
|
+
// ============================================
|
|
242
|
+
// ADMIN OPERATIONS (platform role)
|
|
243
|
+
// ============================================
|
|
244
|
+
/**
|
|
245
|
+
* Platform-admin surface. Requires `users.role = 'admin'`; the writes also
|
|
246
|
+
* require a login-issued SESSION (D20) — an API key gets `403 SESSION_REQUIRED`
|
|
247
|
+
* (`isSessionRequiredError`). Log in with `login()` / `loginWithTotp()` first.
|
|
248
|
+
* Deliberately not exposed as MCP tools.
|
|
249
|
+
*/
|
|
250
|
+
admin = {
|
|
251
|
+
/**
|
|
252
|
+
* Move ANY project between ANY orgs by UUID — `reason` required, session
|
|
253
|
+
* only. `same_org` (via `rehomeRefusalReason`) means already done.
|
|
254
|
+
*/
|
|
255
|
+
rehomeProject: (projectId, input) => adminOps.rehomeProject(this.httpClient, projectId, input),
|
|
256
|
+
/** The current redirect table: every vacated `(org, name)` and where it points. Key-readable. */
|
|
257
|
+
listProjectRehomes: (query) => adminOps.listProjectRehomes(this.httpClient, query),
|
|
258
|
+
/** The append-only re-home ledger (D21), paged by `seq`. Key-readable. Reconcile migrations against THIS. */
|
|
259
|
+
listProjectRehomeEvents: (query) => adminOps.listProjectRehomeEvents(this.httpClient, query),
|
|
260
|
+
/** Release a vacated address so the old name is creatable again (audited; never by time). Session only. */
|
|
261
|
+
releaseProjectRehome: (rehomeId) => adminOps.releaseProjectRehome(this.httpClient, rehomeId),
|
|
196
262
|
};
|
|
197
263
|
// ============================================
|
|
198
264
|
// RUN OPERATIONS
|