@k9o/gate 1.1.1 → 1.3.0

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/dist/jwt.d.ts CHANGED
@@ -1,58 +1,39 @@
1
1
  import { GateJwtClaims, VerifyJwtResult } from './types.js';
2
2
 
3
3
  /**
4
- * JWT verification primitives for external-app standalone consumption.
5
- *
6
- * Standalone-mode customers consume Gate-issued JWTs in their own
7
- * application code without needing the full SDK shape — they just need
8
- * to verify the signature, extract claims, and (optionally) check
9
- * scopes. This module is the canonical entry for that surface.
10
- *
11
- * Backed by `jose` (already a peer dependency in the gateway via the
12
- * same library; chose `jose` for consistency + JWK fetch support out
13
- * of the box).
14
- *
15
- * Native-mode customers don't typically reach for this directly — the
16
- * gateway verifies project-issued tokens on every request and rehydrates
17
- * `subject.*` claims. But the same primitives are
18
- * portable: Native customers running their own services downstream of
19
- * the gateway can verify Gate-issued JWTs identically.
4
+ * JWT verification primitives for apps that consume Gate-issued tokens without the rest of the SDK: verify the
5
+ * signature, read the claims, check scopes. Backed by `jose`.
20
6
  */
21
7
 
22
8
  /**
23
- * Configuration for `verifyJwt`. The verifier can resolve signing keys
24
- * via either a remote JWKS URL (the typical Gate deployment shape) or
25
- * an inline key set (for tests + air-gapped deployments).
9
+ * Configuration for `verifyJwt`. Signing keys come from either the gateway's JWKS route (`jwksUrl`) or an inline key
10
+ * set (`jwks`, for tests and air-gapped deployments).
26
11
  */
27
12
  type VerifyJwtOptions = {
28
13
  token: string;
29
14
  /**
30
- * JWKS endpoint URL — the gateway exposes `/@<projectSlug>/gate/.well-known/jwks.json`
31
- * by convention. Either `jwksUrl` or `jwks` must be set; not both.
15
+ * URL of the gateway's JWKS route: `https://<org>.api.<apex>/@<projectSlug>/auth/jwks` for a project's end-user
16
+ * tokens (`/auth/jwks` on the org host for org keys). The gateway wraps the key set in its response envelope
17
+ * (`{ data: { keys }, meta }`), which `jose` cannot read directly; this helper fetches the URL and unwraps it.
18
+ * Either `jwksUrl` or `jwks` must be set; not both.
32
19
  */
33
20
  jwksUrl?: string;
34
- /**
35
- * Inline JWK set — used by tests + integration scenarios that want to
36
- * avoid the network hop. The shape is the standard JWKS `{keys: [...]}`
37
- * envelope; pass through unchanged to `jose`.
38
- */
21
+ /** Inline JWK set, the standard `{ keys: [...] }` object. */
39
22
  jwks?: {
40
23
  keys: ReadonlyArray<Record<string, unknown>>;
41
24
  };
25
+ /** Fetch implementation used for `jwksUrl`. Defaults to the global `fetch`. */
26
+ fetch?: typeof globalThis.fetch;
42
27
  /** Expected issuer (`iss` claim). Strict comparison. */
43
28
  expectedIssuer?: string;
44
- /** Expected audience (`aud` claim). Accepts either string or array per RFC 7519. */
29
+ /** Expected audience (`aud` claim). */
45
30
  expectedAudience?: string;
46
31
  /**
47
- * Allowed signing algorithms. Defaults to `["RS256", "ES256"]` per the
48
- * standard Gate issuer config. Reject anything outside this set
49
- * (defense against alg-confusion attacks).
32
+ * Allowed signing algorithms. Defaults to `["RS256", "ES256"]`. Anything outside this set is rejected (defense
33
+ * against algorithm confusion).
50
34
  */
51
35
  allowedAlgorithms?: ReadonlyArray<string>;
52
- /**
53
- * Clock-skew tolerance in seconds. Defaults to 30. Applies to both
54
- * `exp` and `nbf` checks.
55
- */
36
+ /** Clock-skew tolerance in seconds. Defaults to 30. Applies to `exp` and `nbf`. */
56
37
  clockToleranceSeconds?: number;
57
38
  };
58
39
  /**
@@ -61,7 +42,7 @@ type VerifyJwtOptions = {
61
42
  *
62
43
  * Throws `GateError` only when:
63
44
  * - `jwksUrl` and `jwks` are both unset or both set
64
- * - the JWKS fetch fails for transport reasons (UPSTREAM_UNAVAILABLE)
45
+ * - the JWKS fetch fails (UPSTREAM_UNAVAILABLE) or answers something that is not a key set
65
46
  */
66
47
  declare function verifyJwt(opts: VerifyJwtOptions): Promise<VerifyJwtResult>;
67
48
  /**
@@ -80,5 +61,13 @@ declare function extractClaims(token: string): GateJwtClaims;
80
61
  * Returns nothing on success — caller uses the throw-or-pass shape.
81
62
  */
82
63
  declare function requireScope(claims: GateJwtClaims, requiredScopes: ReadonlyArray<string>): void;
64
+ type Jwks = {
65
+ keys: ReadonlyArray<Record<string, unknown>>;
66
+ };
67
+ /**
68
+ * Fetch a JWKS from the gateway. The route answers `{ data: { keys }, meta }`; a bare `{ keys }` (any other JWKS
69
+ * host) is accepted too.
70
+ */
71
+ declare function fetchJwks(url: string, fetchImpl?: typeof globalThis.fetch | undefined): Promise<Jwks>;
83
72
 
84
- export { type VerifyJwtOptions, extractClaims, requireScope, verifyJwt };
73
+ export { type VerifyJwtOptions, extractClaims, fetchJwks, requireScope, verifyJwt };
package/dist/jwt.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  extractClaims,
3
+ fetchJwks,
3
4
  requireScope,
4
5
  verifyJwt
5
- } from "./chunk-YINWOWJC.js";
6
- import "./chunk-JEV37YEC.js";
6
+ } from "./chunk-OJQYLAGZ.js";
7
+ import "./chunk-7EED6YWR.js";
7
8
  export {
8
9
  extractClaims,
10
+ fetchJwks,
9
11
  requireScope,
10
12
  verifyJwt
11
13
  };
package/dist/mode.d.ts CHANGED
@@ -1,37 +1,12 @@
1
1
  /**
2
2
  * Standalone vs Native mode wiring.
3
3
  *
4
- * The SDK surface is identical across modes; mode determines gateway-
5
- * side behavior (whether the end-user row scope applies on read/write paths),
6
- * not SDK ergonomics. The mode boundary is a deployment-time decision
7
- *; the SDK does not branch behavior at function-call time.
4
+ * The SDK surface is identical across modes; the mode only tells the gateway how to route consumption. The SDK
5
+ * records it on every request in the `X-Gate-Mode` header and does not branch on it at call time.
8
6
  *
9
- * What mode affects (today + forward):
10
- *
11
- * - **Billing-identity routing** (commission callout):
12
- * Standalone customers are billed under the partner-credentialled
13
- * contract (Gate as Auth0/Clerk/Okta alternative — Trojan Horse
14
- * motion); Native customers compose with the broader Kilimanjaro
15
- * billing model. The SDK records the mode on every wire request via
16
- * the `X-Gate-Mode` header so the gateway routes consumption to the
17
- * correct billing surface without per-function branching.
18
- * - **Auto-scoping expectations** in marketing copy + docs hub copy
19
- * (per memory `feedback_gate_iak_brand_architecture.md` marketing-
20
- * honesty discipline). Standalone mode delivers Sovereign Ingress +
21
- * Hardware-Attested Identity + Forensic Attribution + Unlimited MAUs;
22
- * Native mode adds Automatic Identity-Aware Scoping on top.
23
- *
24
- * What mode does NOT affect:
25
- *
26
- * - Function names + signatures (identical across modes)
27
- * - Wire-envelope shape (identical)
28
- * - Error taxonomy (identical)
29
- */
30
- /**
31
- * Two-mode discriminant. The third candidate "Hybrid" was rejected per
32
- * memory `feedback_gate_iak_brand_architecture.md` — modes are
33
- * orthogonal deployment shapes, not interleaved.
7
+ * Function names, signatures, response envelope and error taxonomy are the same in both modes.
34
8
  */
9
+ /** Two orthogonal deployment shapes; they are not interleaved. */
35
10
  type GateMode = "standalone" | "native";
36
11
  /**
37
12
  * Gate SDK configuration. Returned by `getGateConfig()` and consumed
@@ -40,11 +15,9 @@ type GateMode = "standalone" | "native";
40
15
  */
41
16
  type GateConfig = {
42
17
  mode: GateMode;
43
- /** Project slug per the URL grammar (e.g. `partner-console-staging`).
44
- * The only customer-facing project addressing form; routes resolve to
45
- * `/@<slug>/gate/...` (`feedback_slug_addressing_v2_5_68`). Required. */
18
+ /** Project slug (e.g. `partner-console-staging`). Routes resolve to `/@<slug>/gate/...`. Required. */
46
19
  projectSlug: string;
47
- /** Gateway base URL — e.g. `https://<orgSlug>.api.k9o.io` per the URL grammar */
20
+ /** Gateway base URL, e.g. `https://<orgSlug>.api.<apex>` */
48
21
  baseUrl: string;
49
22
  /**
50
23
  * Optional partner-credentialled JWT for Standalone mode — used as the
package/dist/mode.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  getGateConfig,
4
4
  resetGateConfig,
5
5
  validateGateConfig
6
- } from "./chunk-XP6U5QKF.js";
6
+ } from "./chunk-RPE5N37W.js";
7
7
  export {
8
8
  configureGate,
9
9
  getGateConfig,
package/dist/types.d.ts CHANGED
@@ -1,32 +1,15 @@
1
1
  /**
2
- * `@k9o/gate` — public type surface.
3
- *
4
- * The shapes here track verbatim. End-user IDs carry the `eu_`
5
- * prefix; role and group identifiers compose against the
6
- * role lattice (Trust-Zone-separate `PROJECT_END_USER` for project-
7
- * level end-user identities). Sessions, passkeys, MFA factors, federation
8
- * links, and the rest of the Gate-managed resources receive their per-
9
- * resource prefixes as the end-user table family is specified.
10
- *
11
- * Envelope shape (`data` + `meta`) matches; failures land via
12
- * the `GateError` family per `errors.ts`, which is `KError`-compatible
13
- *.
14
- */
15
- /**
16
- * Standard envelope. Generic over the `data` shape per function.
17
- * Returned by every successful SDK call.
2
+ * `@k9o/gate` public types. They mirror what the end-user Gate routes answer; failures arrive as a `GateError`
3
+ * (see `errors.ts`).
18
4
  */
5
+ /** The gateway's response envelope, generic over the `data` shape. Returned by every successful SDK call. */
19
6
  type Envelope<T> = {
20
7
  data: T;
21
8
  meta: Record<string, unknown>;
22
9
  };
23
- /**
24
- * Project-level end-user identity (the customers OF the customer as
25
- * the `PROJECT_END_USER` 7th canonical role). End-user IDs carry the
26
- * `eu_<base62>` prefix.
27
- */
10
+ /** A project end-user (the customers of the project's customer). Ids carry the `eu_` prefix. */
28
11
  type EndUser = {
29
- /** `eu_<base62>` — see. */
12
+ /** `eu_<base62>`. */
30
13
  id: string;
31
14
  email: string;
32
15
  status: "active" | "pending" | "disabled";
@@ -35,15 +18,12 @@ type EndUser = {
35
18
  lastSigninAt: string | null;
36
19
  /** Project-level roles; `PROJECT_END_USER` is the floor membership. */
37
20
  roles: ReadonlyArray<string>;
38
- /** Project-level group memberships `_sys.end_user_groups`. */
21
+ /** Project-level group memberships. */
39
22
  groups: ReadonlyArray<string>;
40
23
  };
41
24
  /**
42
- * Session tokens returned by sign-up / sign-in / refresh flows. Both
43
- * tokens are opaque to consumers; `accessToken` carries the JWT shape
44
- * for downstream Gate-aware services; `refreshToken` is opaque to the
45
- * SDK consumer and is presented back to `gate.refreshToken({...})` to
46
- * rotate the access token.
25
+ * Session tokens returned by sign-up, sign-in, OTP and magic-link redemption. The access token is a JWT; the refresh
26
+ * token is opaque and is presented to `gate.refreshToken` and `gate.signOut`.
47
27
  */
48
28
  type Session = {
49
29
  accessToken: string;
@@ -51,41 +31,26 @@ type Session = {
51
31
  /** ISO 8601 instant the access token expires. */
52
32
  expiresAt: string;
53
33
  };
54
- /**
55
- * Marquee sign-up / sign-in shape:
56
- * `{ user, session }`
57
- */
34
+ /** Sign-up and sign-in answer: `{ user, session }`. */
58
35
  type AuthResult = {
59
36
  user: EndUser;
60
37
  session: Session;
61
38
  };
62
- /**
63
- * `gate.whoami()` shape:
64
- * `{ user }` — no fresh session is minted by `whoami` (it reflects the
65
- * caller's current session).
66
- */
67
- type WhoamiResult = {
68
- user: EndUser;
39
+ /** `gate.whoami()` answers the end-user record itself (no wrapper). */
40
+ type WhoamiResult = EndUser;
41
+ /** `gate.refreshToken()` answers a new access token only; the refresh token in hand stays valid. */
42
+ type RefreshResult = {
43
+ accessToken: string;
44
+ /** ISO 8601 instant the new access token expires. */
45
+ expiresAt: string;
69
46
  };
70
- /**
71
- * `gate.verifySession()` shape — boolean plus the surfaced session
72
- * details if valid. Returns `{ valid: false, user: null, session: null }`
73
- * when the bearer session is missing or expired (no throw on invalid;
74
- * throw only on transport / upstream failures).
75
- */
76
- type VerifySessionResult = {
77
- valid: true;
78
- user: EndUser;
79
- session: Session;
80
- } | {
81
- valid: false;
82
- user: null;
83
- session: null;
47
+ /** `gate.signOut()` answer. `revoked` is false when the token was unknown or already revoked. */
48
+ type SignOutResult = {
49
+ revoked: boolean;
84
50
  };
85
51
  /**
86
- * Marquee request shapes. `gate.signUp` takes a CLOSED body: `{ email, password, displayName? }`.
87
- * `displayName` is stored with the end user's PII (1 to 128 characters); any other field is refused by the
88
- * gateway with `K9O_INVALID_FIELD_VALUE` (`context.reason: "unknown_field"`), so this type has no escape hatch.
52
+ * `gate.signUp` takes a CLOSED body: `{ email, password, displayName? }`. `displayName` is 1 to 128 characters; any
53
+ * other field is refused by the gateway with `INVALID_FIELD_VALUE` (`context.reason: "unknown_field"`).
89
54
  */
90
55
  type SignUpInput = {
91
56
  email: string;
@@ -98,36 +63,39 @@ type SignInInput = {
98
63
  password: string;
99
64
  };
100
65
  type SignOutInput = {
101
- /**
102
- * Optional refresh token to revoke. When omitted the gateway revokes
103
- * the access token's bound refresh token only; explicit refresh tokens
104
- * let callers revoke a specific device/session.
105
- */
106
- refreshToken?: string;
66
+ /** The refresh token of the session to revoke (required by the route). */
67
+ refreshToken: string;
107
68
  };
108
69
  type RefreshTokenInput = {
109
70
  refreshToken: string;
110
71
  };
111
- /**
112
- * OTP purpose — scopes the one-time code to a flow. The gateway validates
113
- * against this closed set (otp.ts) and defaults to "signin" when omitted.
114
- */
72
+ /** OTP purpose: scopes the one-time code to a flow. The gateway defaults to `"signin"` when omitted. */
115
73
  type OtpPurpose = "signin" | "magic_link" | "password_reset" | "mfa_challenge";
116
- /**
117
- * `gate.passwordless.requestOtp` input — emails an OTP code to the
118
- * given address. Idempotent at the per-email rate-limit ceiling per
119
- * the platform anti-abuse policy.
120
- */
74
+ /** `gate.passwordless.requestOtp` input: emails a one-time code to the address. */
121
75
  type RequestOtpInput = {
122
76
  email: string;
123
77
  /** OTP purpose; defaults to "signin" server-side when omitted. */
124
78
  purpose?: OtpPurpose;
125
79
  };
80
+ /** `gate.passwordless.verifyOtp` input: the 6-character code from the email plus the address it went to. */
81
+ /** `gate.passwordless.requestOtp` answer. */
82
+ type RequestOtpResult = {
83
+ otp: {
84
+ id: string;
85
+ expiresAt: string;
86
+ };
87
+ /** Only present on a deployment configured to return the code in the response (development). */
88
+ debugCode?: string;
89
+ };
126
90
  /**
127
- * `gate.passwordless.verifyOtp` input — caller supplies the OTP code
128
- * received via email plus the originating email address so the
129
- * gateway can resolve the right OTP row.
91
+ * `gate.passwordless.verifyOtp` answer. `session` and `endUserId` are present for the `signin` purpose; other
92
+ * purposes only confirm the code (`endUserId` when the email belongs to an end-user).
130
93
  */
94
+ type VerifyOtpResult = {
95
+ verified: true;
96
+ session?: Session;
97
+ endUserId?: string;
98
+ };
131
99
  type VerifyOtpInput = {
132
100
  email: string;
133
101
  code: string;
@@ -135,11 +103,7 @@ type VerifyOtpInput = {
135
103
  * so the gateway resolves the right OTP row. */
136
104
  purpose?: OtpPurpose;
137
105
  };
138
- /**
139
- * `gate.passwordless.requestMagicLink` input — emails a magic-link
140
- * token to the given address; the customer's frontend handles the
141
- * post-redirect exchange via `verifyMagicLink`.
142
- */
106
+ /** `gate.passwordless.requestMagicLink` input: emails a magic link to the address. */
143
107
  type RequestMagicLinkInput = {
144
108
  email: string;
145
109
  /**
@@ -150,10 +114,22 @@ type RequestMagicLinkInput = {
150
114
  */
151
115
  redirectUrl?: string;
152
116
  };
153
- /**
154
- * `gate.passwordless.verifyMagicLink` input — caller supplies the
155
- * opaque token from the magic-link query string.
156
- */
117
+ /** `gate.passwordless.verifyMagicLink` input: the opaque token from the magic-link query string. */
118
+ /** `gate.passwordless.requestMagicLink` answer. The same shape comes back for an unknown email. */
119
+ type RequestMagicLinkResult = {
120
+ magicLink: {
121
+ sent: true;
122
+ expiresAt?: string;
123
+ };
124
+ /** Only present on a deployment configured to return the link in the response (development). */
125
+ debugLink?: string;
126
+ };
127
+ /** `gate.passwordless.verifyMagicLink` answer: the new session and the end-user's id. */
128
+ type VerifyMagicLinkResult = {
129
+ redeemed: true;
130
+ session: Session;
131
+ endUserId: string;
132
+ };
157
133
  type VerifyMagicLinkInput = {
158
134
  token: string;
159
135
  };
@@ -171,10 +147,8 @@ type MagicLinkCallbackResult = {
171
147
  error: string;
172
148
  };
173
149
  /**
174
- * JWT claim shape that `verifyJwt` resolves. Mirrors `subject.*`
175
- * exposure for project-level JWTs plus the Gate-issued audience / issuer
176
- * standard claims. Customer apps in Standalone mode consume this via the
177
- * `verifyJwt` helper without needing the full SDK shape.
150
+ * Claims of a Gate access token that `verifyJwt` resolves. Custom claims arrive FLAT under keys of the form
151
+ * `ext.<namespace>.<name>`; they are available through the index signature.
178
152
  */
179
153
  type GateJwtClaims = {
180
154
  /** Issuer — the Kilimanjaro gateway base URL or project-bound issuer. */
@@ -193,7 +167,7 @@ type GateJwtClaims = {
193
167
  roles?: ReadonlyArray<string>;
194
168
  /** Project-level group memberships. */
195
169
  groups?: ReadonlyArray<string>;
196
- /** Project-level scope strings — used by `requireScope`. */
170
+ /** Scope strings, when the token carries them; used by `requireScope`. */
197
171
  scopes?: ReadonlyArray<string>;
198
172
  /** Email of the underlying end-user; convenience copy of the database column. */
199
173
  email?: string;
@@ -236,15 +210,5 @@ type SignInCallbackResult = {
236
210
  error: string;
237
211
  errorDescription: string;
238
212
  };
239
- /**
240
- * The project's hosted sign-in page and WebAuthn relying party (`GET /@<projectSlug>/gate/hosted`). Both values come from the platform: an SDK never composes a Gate host from a slug (the label is a hash of
241
- * ids the SDK does not hold).
242
- */
243
- type HostedGateInfo = {
244
- /** Absolute hosted sign-in URL: the verified custom domain's when there is one, else the project's Gate host. */
245
- signInUrl: string;
246
- /** The WebAuthn relying-party id (the project's Gate host). Use it as given; never hard-code it. */
247
- rpId: string;
248
- };
249
213
 
250
- export type { AuthResult, EndUser, Envelope, GateJwtClaims, HostedGateInfo, MagicLinkCallbackResult, OtpPurpose, RefreshTokenInput, RequestMagicLinkInput, RequestOtpInput, Session, SignInCallbackResult, SignInInput, SignOutInput, SignUpInput, VerifyJwtFailure, VerifyJwtResult, VerifyMagicLinkInput, VerifyOtpInput, VerifySessionResult, WhoamiResult };
214
+ export type { AuthResult, EndUser, Envelope, GateJwtClaims, MagicLinkCallbackResult, OtpPurpose, RefreshResult, RefreshTokenInput, RequestMagicLinkInput, RequestMagicLinkResult, RequestOtpInput, RequestOtpResult, Session, SignInCallbackResult, SignInInput, SignOutInput, SignOutResult, SignUpInput, VerifyJwtFailure, VerifyJwtResult, VerifyMagicLinkInput, VerifyMagicLinkResult, VerifyOtpInput, VerifyOtpResult, WhoamiResult };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k9o/gate",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "Gate — Identity-Aware Kernel SDK (standalone Auth0-parity surface; Native composition via @k9o/core re-export)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Reads the structure of a gateway API host without knowing its apex.
3
+ *
4
+ * The gateway label is `api` or `api-staging`, optionally followed by `-<rgn>` (a 4-character region code). The label
5
+ * before it is the org slug and whatever follows it is the apex, which is never matched against a list: a public-cloud,
6
+ * internal-apex, air-gapped or localhost deployment all work.
7
+ */
8
+
9
+ /** The structure of a gateway API host, read from the host itself. */
10
+ export type ApiHost = {
11
+ /** First label of an org host (`acme` in `acme.api.corp.internal`); null for a platform host (`api.corp.internal`). */
12
+ orgSlug: string | null;
13
+ /** `api` or `api-staging`: the label that marks a gateway host. */
14
+ family: "api" | "api-staging";
15
+ /** 4-character region code when the host is regional (`api-de01`), else null (region-free). */
16
+ region: string | null;
17
+ /** Everything after the gateway label (`corp.internal`, `k9o.io`). */
18
+ apex: string;
19
+ };
20
+
21
+ const API_LABEL = /^(api|api-staging)(?:-([a-z]{2}\d{2}))?$/;
22
+ const SLUG_RE = /^[a-z][a-z0-9-]{1,62}$/;
23
+
24
+ /**
25
+ * Returns null for any other shape (localhost, an IP, a single-label host, a host with no gateway label), which callers
26
+ * use exactly as given.
27
+ */
28
+ export function parseApiHost(host: string): ApiHost | null {
29
+ const labels = host.toLowerCase().split(".");
30
+ if (labels.some((l) => l.length === 0)) return null;
31
+ const mk = (gatewayLabel: string, orgSlug: string | null, rest: string[]): ApiHost | null => {
32
+ const m = API_LABEL.exec(gatewayLabel);
33
+ if (m === null || rest.length === 0) return null;
34
+ return { orgSlug, family: m[1] as "api" | "api-staging", region: m[2] ?? null, apex: rest.join(".") };
35
+ };
36
+ // Org host first: `<org>.<api[-rgn]>.<apex>`.
37
+ if (labels.length >= 3 && SLUG_RE.test(labels[0]!) && !labels[0]!.endsWith("-") && API_LABEL.test(labels[1]!)) {
38
+ return mk(labels[1]!, labels[0]!, labels.slice(2));
39
+ }
40
+ // Platform host: `api.<apex>`.
41
+ if (labels.length >= 2 && API_LABEL.test(labels[0]!)) return mk(labels[0]!, null, labels.slice(1));
42
+ return null;
43
+ }
@@ -8,11 +8,8 @@
8
8
  * incoming request's resolved tenant) or for apps that maintain both a
9
9
  * Standalone and Native binding against the same SDK instance.
10
10
  *
11
- * Per the URL grammar, supply `projectSlug` for
12
- * canonical `/@<slug>/gate/...` routing. Slug is the only customer-facing
13
- * project addressing form — project ids are internal-only
14
- * (`feedback_slug_addressing_v2_5_68`); the retired `/v2/projects/...`
15
- * mounts were dropped at pass17 (`feedback_v2_path_is_an_error`).
11
+ * Supply `projectSlug` for
12
+ * `/@<slug>/gate/...` routing. The slug is the only project addressing form.
16
13
  *
17
14
  * Example:
18
15
  * const client = createGateClient({
package/src/client.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Centralises:
5
5
  * - URL construction against the configured `baseUrl` + per-call path
6
- * - the standard envelope unwrap on success
7
- * - the standard `KError` rehydration on failure via `gateErrorFromWire`
6
+ * - the standard response envelope check on success
7
+ * - the `KError` rehydration on failure via `gateErrorFromWire`
8
8
  * - mode-routing via the `X-Gate-Mode` header (so the gateway routes
9
9
  * consumption to the right billing surface without per-function
10
10
  * branching at the SDK)
@@ -47,7 +47,7 @@ export type RequestInit = {
47
47
  * keeps the SDK portable across Bun + Node 18+ + browsers + test
48
48
  * fixtures that supply a stub.
49
49
  * - JSON parsing of non-JSON responses throws — gateway always returns
50
- * JSON +, so a parse failure is an upstream defect.
50
+ * JSON, so a parse failure is an upstream defect.
51
51
  */
52
52
  export async function request<T>(init: RequestInit): Promise<Envelope<T>> {
53
53
  return requestWith<T>(getGateConfig(), init);
package/src/errors.ts CHANGED
@@ -36,7 +36,7 @@ export type GateErrorCode =
36
36
 
37
37
  /**
38
38
  * Surface error thrown by the SDK. Mirrors the gateway's `KError` shape
39
- * — `code` (closed taxonomy), `message` (human-readable),
39
+ * `code` (closed taxonomy), `message` (human-readable),
40
40
  * `context` (structured per-code payload), `recommendation` (action the
41
41
  * caller can take, or null when there's no canonical recommendation).
42
42
  */