@volter/twin-xidentity 0.1.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/README.md +112 -0
- package/client/xidentity-consent.css +204 -0
- package/client/xidentity-consent.tsx +162 -0
- package/dist/client/xidentity-consent.bundle.js +235 -0
- package/dist/client/xidentity-consent.css +204 -0
- package/dist/client/xidentity-consent.d.ts +53 -0
- package/dist/client/xidentity-consent.js +57 -0
- package/dist/client/xidentity-consent.tsx +162 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +44 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +105 -0
- package/dist/src/xidentity-budget.d.ts +50 -0
- package/dist/src/xidentity-budget.js +108 -0
- package/dist/src/xidentity-capabilities.d.ts +3 -0
- package/dist/src/xidentity-capabilities.js +905 -0
- package/dist/src/xidentity-conformance.d.ts +10 -0
- package/dist/src/xidentity-conformance.js +332 -0
- package/dist/src/xidentity-connector.d.ts +84 -0
- package/dist/src/xidentity-connector.js +239 -0
- package/dist/src/xidentity-consent-client.gen.d.ts +2 -0
- package/dist/src/xidentity-consent-client.gen.js +10 -0
- package/dist/src/xidentity-consent-ui.d.ts +21 -0
- package/dist/src/xidentity-consent-ui.js +94 -0
- package/dist/src/xidentity-pkce.d.ts +7 -0
- package/dist/src/xidentity-pkce.js +27 -0
- package/dist/src/xidentity-problems.d.ts +38 -0
- package/dist/src/xidentity-problems.js +108 -0
- package/dist/src/xidentity-scopes.d.ts +23 -0
- package/dist/src/xidentity-scopes.js +81 -0
- package/dist/src/xidentity-server.d.ts +33 -0
- package/dist/src/xidentity-server.js +85 -0
- package/dist/src/xidentity-store.d.ts +97 -0
- package/dist/src/xidentity-store.js +358 -0
- package/dist/src/xidentity-twin.d.ts +54 -0
- package/dist/src/xidentity-twin.js +851 -0
- package/package.json +74 -0
- package/src/cli.ts +43 -0
- package/src/index.ts +177 -0
- package/src/xidentity-budget.ts +135 -0
- package/src/xidentity-capabilities.ts +1012 -0
- package/src/xidentity-conformance.ts +370 -0
- package/src/xidentity-connector.ts +269 -0
- package/src/xidentity-consent-client.gen.ts +10 -0
- package/src/xidentity-consent-ui.ts +113 -0
- package/src/xidentity-journey.uitest.ts +277 -0
- package/src/xidentity-pkce.ts +29 -0
- package/src/xidentity-problems.ts +128 -0
- package/src/xidentity-scopes.ts +96 -0
- package/src/xidentity-server.ts +97 -0
- package/src/xidentity-store.ts +419 -0
- package/src/xidentity-twin.ts +944 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { RateBudget, type RateBudgetDeclaration, type RateBudgetOptions, type RateBudgetReservation, type RateBudgetSnapshot } from '@volter/world-core';
|
|
2
|
+
/** Rolling window, in ms — X's own 15-minute rate-limit window. */
|
|
3
|
+
export declare const XIDENTITY_BUDGET_WINDOW_MS: number;
|
|
4
|
+
/** Weighted units allowed inside one window — X's own per-user allowance for GET /2/users/me. */
|
|
5
|
+
export declare const XIDENTITY_BUDGET_CEILING = 75;
|
|
6
|
+
/** Weighted units allowed in any 60s (the kernel's burst sub-window): 3× the window's average
|
|
7
|
+
* pace, and UNDER the kernel fallback's burst of 30. */
|
|
8
|
+
export declare const XIDENTITY_BUDGET_BURST_CEILING = 15;
|
|
9
|
+
/** Seconds. A `Retry-After` above this means the client is throttled hard — fail loudly, don't sleep. */
|
|
10
|
+
export declare const XIDENTITY_BUDGET_MAX_RETRY_AFTER_S = 900;
|
|
11
|
+
/** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
|
|
12
|
+
export declare const XIDENTITY_CALL_WEIGHTS: {
|
|
13
|
+
/** `GET /2/users/me` — 75/15min per user is the PUBLISHED limit; weight 1 transcribes it. */
|
|
14
|
+
readonly usersMe: 1;
|
|
15
|
+
/** `POST /2/oauth2/revoke` — logs a human out of an app on a REAL account. Priced as a human
|
|
16
|
+
* blast radius, not a throttle (a judgement call, stated as one). */
|
|
17
|
+
readonly revoke: 15;
|
|
18
|
+
/** Everything else (the token endpoint): no published scalar limit → priced conservatively. */
|
|
19
|
+
readonly other: 3;
|
|
20
|
+
};
|
|
21
|
+
/** THE PACK'S DECLARATION — pure data, the only X-specific thing in the whole budget. */
|
|
22
|
+
export declare const XIDENTITY_RATE_BUDGET: RateBudgetDeclaration;
|
|
23
|
+
/**
|
|
24
|
+
* Price one call. The key is `"<METHOD> <path>"` with the query string split off. NORMALIZED
|
|
25
|
+
* (upper-cased method, collapsed trailing slash) because the anchored rules are otherwise trivially
|
|
26
|
+
* evaded by input variation: `fetch` upper-cases a known method before sending, so
|
|
27
|
+
* `execute('post', '/2/oauth2/revoke')` really does issue the destructive call and must be priced
|
|
28
|
+
* as one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function xIdentityCallWeight(method: string, path: string): number;
|
|
31
|
+
/** Where this vendor's ledger lives. Token-keyed and cwd-independent by default; pass `root` to
|
|
32
|
+
* opt into world-scoped accounting. */
|
|
33
|
+
export declare function xIdentityBudgetPath(opts?: {
|
|
34
|
+
root?: string;
|
|
35
|
+
token?: string;
|
|
36
|
+
} | string): string;
|
|
37
|
+
/** Construction options. The vendor is fixed; everything else may only TIGHTEN. */
|
|
38
|
+
export type XIdentityBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
|
|
39
|
+
/**
|
|
40
|
+
* This vendor's budget — the shared kernel guard bound to this declaration. A real subclass, not
|
|
41
|
+
* an alias, so `budget instanceof XIdentityBudget` means "a budget that accounts against
|
|
42
|
+
* XIDENTITY's ledger under XIDENTITY's ceiling".
|
|
43
|
+
*/
|
|
44
|
+
export declare class XIdentityBudget extends RateBudget {
|
|
45
|
+
constructor(opts?: XIdentityBudgetOptions);
|
|
46
|
+
}
|
|
47
|
+
export { RateBudgetError as XIdentityBudgetError } from '@volter/world-core';
|
|
48
|
+
export type { RateBudgetErrorKind as XIdentityBudgetErrorKind } from '@volter/world-core';
|
|
49
|
+
export type XIdentityBudgetReservation = RateBudgetReservation;
|
|
50
|
+
export type XIdentityBudgetSnapshot = RateBudgetSnapshot;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// X identity's CLIENT-SIDE RATE BUDGET — this pack's DECLARATION (the numbers) plus the thin typed
|
|
2
|
+
// bindings `liveXIdentityExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling
|
|
3
|
+
// window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger —
|
|
4
|
+
// lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`).
|
|
5
|
+
//
|
|
6
|
+
// ── HOW THE CEILING WAS CHOSEN (live-fetched first-party figures) ───────────────────────────────
|
|
7
|
+
// X PUBLISHES per-endpoint rate limits. From https://docs.x.com/x-api/fundamentals/rate-limits
|
|
8
|
+
// (fetched 2026-08-21): `GET /2/users/me` is **75 requests / 15 minutes per user** (no per-app
|
|
9
|
+
// row), other user lookups are 300/15min (app) and 900/15min (user), windows are 15 minutes, and
|
|
10
|
+
// exceeding one answers HTTP 429 with legacy code 88. The OAuth token/revoke endpoints publish NO
|
|
11
|
+
// scalar limit on that page — that absence is stated here rather than dressed up, and those calls
|
|
12
|
+
// are priced ABOVE the documented read so the undocumented surface is the conservative one.
|
|
13
|
+
//
|
|
14
|
+
// The window is therefore the vendor's own 15 minutes and the ceiling is the vendor's own 75,
|
|
15
|
+
// with `GET /2/users/me` at weight 1 — the declaration REPRODUCES the published per-user budget
|
|
16
|
+
// for the one endpoint the connector reads (the github-budget precedent: when the vendor's scheme
|
|
17
|
+
// is already a windowed budget, transcribe it). The kernel demands a `burstCeiling` for any
|
|
18
|
+
// window longer than its 60s burst sub-window; 15 units/60s (15 users/me reads a minute, 3× the
|
|
19
|
+
// window's average pace) bounds the instant WITHOUT out-bursting the kernel fallback's 30, so no
|
|
20
|
+
// VENDOR_BURST_ANCHOR entry is required in `scripts/rate-budget-isolation.test.ts`.
|
|
21
|
+
import { declareRateBudget, rateBudgetPath, rateBudgetWeight, RateBudget, } from '@volter/world-core';
|
|
22
|
+
const VENDOR = 'xidentity';
|
|
23
|
+
/** Rolling window, in ms — X's own 15-minute rate-limit window. */
|
|
24
|
+
export const XIDENTITY_BUDGET_WINDOW_MS = 15 * 60_000;
|
|
25
|
+
/** Weighted units allowed inside one window — X's own per-user allowance for GET /2/users/me. */
|
|
26
|
+
export const XIDENTITY_BUDGET_CEILING = 75;
|
|
27
|
+
/** Weighted units allowed in any 60s (the kernel's burst sub-window): 3× the window's average
|
|
28
|
+
* pace, and UNDER the kernel fallback's burst of 30. */
|
|
29
|
+
export const XIDENTITY_BUDGET_BURST_CEILING = 15;
|
|
30
|
+
/** Seconds. A `Retry-After` above this means the client is throttled hard — fail loudly, don't sleep. */
|
|
31
|
+
export const XIDENTITY_BUDGET_MAX_RETRY_AFTER_S = 900;
|
|
32
|
+
/** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
|
|
33
|
+
export const XIDENTITY_CALL_WEIGHTS = {
|
|
34
|
+
/** `GET /2/users/me` — 75/15min per user is the PUBLISHED limit; weight 1 transcribes it. */
|
|
35
|
+
usersMe: 1,
|
|
36
|
+
/** `POST /2/oauth2/revoke` — logs a human out of an app on a REAL account. Priced as a human
|
|
37
|
+
* blast radius, not a throttle (a judgement call, stated as one). */
|
|
38
|
+
revoke: 15,
|
|
39
|
+
/** Everything else (the token endpoint): no published scalar limit → priced conservatively. */
|
|
40
|
+
other: 3,
|
|
41
|
+
};
|
|
42
|
+
/** THE PACK'S DECLARATION — pure data, the only X-specific thing in the whole budget. */
|
|
43
|
+
export const XIDENTITY_RATE_BUDGET = {
|
|
44
|
+
windowMs: XIDENTITY_BUDGET_WINDOW_MS,
|
|
45
|
+
ceiling: XIDENTITY_BUDGET_CEILING,
|
|
46
|
+
burstCeiling: XIDENTITY_BUDGET_BURST_CEILING,
|
|
47
|
+
defaultWeight: XIDENTITY_CALL_WEIGHTS.other,
|
|
48
|
+
maxRetryAfterSeconds: XIDENTITY_BUDGET_MAX_RETRY_AFTER_S,
|
|
49
|
+
rules: [
|
|
50
|
+
{ match: '^GET /2/users/me$', weight: XIDENTITY_CALL_WEIGHTS.usersMe },
|
|
51
|
+
{ match: '^POST /2/oauth2/revoke$', weight: XIDENTITY_CALL_WEIGHTS.revoke },
|
|
52
|
+
],
|
|
53
|
+
reason: 'X publishes per-endpoint rate limits (https://docs.x.com/x-api/fundamentals/rate-limits, '
|
|
54
|
+
+ 'fetched 2026-08-21): GET /2/users/me is 75 requests / 15 minutes PER USER, windows are 15 '
|
|
55
|
+
+ 'minutes, and exceeding one answers HTTP 429 with legacy code 88 "Rate limit exceeded". This '
|
|
56
|
+
+ "declaration transcribes that scheme: the window is the vendor's own 15 minutes, the ceiling "
|
|
57
|
+
+ "is the vendor's own 75, and GET /2/users/me costs 1 — the one documented figure for the one "
|
|
58
|
+
+ 'endpoint the connector reads. The OAuth token and revoke endpoints publish NO scalar limit '
|
|
59
|
+
+ 'on that page; they are priced ABOVE the documented read (defaultWeight 3, revoke 15) so the '
|
|
60
|
+
+ 'undocumented surface is the conservative one. POST /2/oauth2/revoke costs 15 because it is '
|
|
61
|
+
+ 'the one destructive call here — it logs a human out of an app on a REAL account — which is a '
|
|
62
|
+
+ 'human blast radius, not a throttle; that price is a judgement call, stated as one. The '
|
|
63
|
+
+ "burstCeiling of 15 units/60s (3x the window's average pace, under the kernel fallback's "
|
|
64
|
+
+ 'burst of 30) bounds the instant, and the 429 / Retry-After cooldown is the backstop.',
|
|
65
|
+
};
|
|
66
|
+
// Declared at module load, so merely importing this module (which `xidentity-connector.ts` does)
|
|
67
|
+
// is enough to arm the real ceiling.
|
|
68
|
+
declareRateBudget(VENDOR, XIDENTITY_RATE_BUDGET);
|
|
69
|
+
/**
|
|
70
|
+
* Price one call. The key is `"<METHOD> <path>"` with the query string split off. NORMALIZED
|
|
71
|
+
* (upper-cased method, collapsed trailing slash) because the anchored rules are otherwise trivially
|
|
72
|
+
* evaded by input variation: `fetch` upper-cases a known method before sending, so
|
|
73
|
+
* `execute('post', '/2/oauth2/revoke')` really does issue the destructive call and must be priced
|
|
74
|
+
* as one.
|
|
75
|
+
*/
|
|
76
|
+
export function xIdentityCallWeight(method, path) {
|
|
77
|
+
const { bare, query } = splitQuery(path);
|
|
78
|
+
return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
|
|
79
|
+
}
|
|
80
|
+
function splitQuery(path) {
|
|
81
|
+
const at = path.indexOf('?');
|
|
82
|
+
const query = {};
|
|
83
|
+
if (at !== -1)
|
|
84
|
+
for (const [k, v] of new URLSearchParams(path.slice(at + 1)))
|
|
85
|
+
query[k] = v;
|
|
86
|
+
const raw = at === -1 ? path : path.slice(0, at);
|
|
87
|
+
const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
|
|
88
|
+
return { bare, query };
|
|
89
|
+
}
|
|
90
|
+
/** Where this vendor's ledger lives. Token-keyed and cwd-independent by default; pass `root` to
|
|
91
|
+
* opt into world-scoped accounting. */
|
|
92
|
+
export function xIdentityBudgetPath(opts = {}) {
|
|
93
|
+
const o = typeof opts === 'string' ? { root: opts } : opts;
|
|
94
|
+
// VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through must not
|
|
95
|
+
// redirect this pack's ledger.
|
|
96
|
+
return rateBudgetPath({ ...o, vendor: VENDOR });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* This vendor's budget — the shared kernel guard bound to this declaration. A real subclass, not
|
|
100
|
+
* an alias, so `budget instanceof XIdentityBudget` means "a budget that accounts against
|
|
101
|
+
* XIDENTITY's ledger under XIDENTITY's ceiling".
|
|
102
|
+
*/
|
|
103
|
+
export class XIdentityBudget extends RateBudget {
|
|
104
|
+
constructor(opts = {}) {
|
|
105
|
+
super({ ...opts, vendor: VENDOR });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export { RateBudgetError as XIdentityBudgetError } from '@volter/world-core';
|