@volter/twin-x 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/LICENSE +202 -0
- package/README.md +138 -0
- package/dist/client/x-mirror.bundle.js +321 -0
- package/dist/client/x-mirror.d.ts +45 -0
- package/dist/client/x-mirror.js +417 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +29 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +68 -0
- package/dist/src/x-budget.d.ts +54 -0
- package/dist/src/x-budget.js +123 -0
- package/dist/src/x-capabilities.d.ts +3 -0
- package/dist/src/x-capabilities.js +1106 -0
- package/dist/src/x-conformance.d.ts +8 -0
- package/dist/src/x-conformance.js +91 -0
- package/dist/src/x-connector.d.ts +125 -0
- package/dist/src/x-connector.js +546 -0
- package/dist/src/x-media.d.ts +87 -0
- package/dist/src/x-media.js +275 -0
- package/dist/src/x-mirror-ui.d.ts +61 -0
- package/dist/src/x-mirror-ui.js +253 -0
- package/dist/src/x-problems.d.ts +38 -0
- package/dist/src/x-problems.js +130 -0
- package/dist/src/x-scopes.d.ts +7 -0
- package/dist/src/x-scopes.js +62 -0
- package/dist/src/x-server.d.ts +14 -0
- package/dist/src/x-server.js +127 -0
- package/dist/src/x-twin.d.ts +21 -0
- package/dist/src/x-twin.js +1534 -0
- package/package.json +58 -0
- package/src/cli.ts +27 -0
- package/src/index.ts +132 -0
- package/src/x-budget.ts +150 -0
- package/src/x-capabilities.ts +1161 -0
- package/src/x-conformance.ts +113 -0
- package/src/x-connector.ts +546 -0
- package/src/x-media.ts +295 -0
- package/src/x-mirror-ui.ts +263 -0
- package/src/x-problems.ts +143 -0
- package/src/x-scopes.ts +67 -0
- package/src/x-server.ts +126 -0
- package/src/x-twin.ts +1545 -0
|
@@ -0,0 +1,54 @@
|
|
|
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 X_BUDGET_WINDOW_MS: number;
|
|
4
|
+
/** Weighted units allowed inside one window. See the EVIDENCE BOUNDARY above: conservative, not
|
|
5
|
+
* transcribed. */
|
|
6
|
+
export declare const X_BUDGET_CEILING = 50;
|
|
7
|
+
/** Weighted units allowed in any 60s (the kernel's burst sub-window): 10, well under the kernel
|
|
8
|
+
* fallback's burst of 30, so no VENDOR_BURST_ANCHOR entry is required in
|
|
9
|
+
* scripts/rate-budget-isolation.test.ts. */
|
|
10
|
+
export declare const X_BUDGET_BURST_CEILING = 10;
|
|
11
|
+
/** Seconds. A `Retry-After` above this means the client is throttled hard — fail loudly, don't sleep. */
|
|
12
|
+
export declare const X_BUDGET_MAX_RETRY_AFTER_S = 900;
|
|
13
|
+
/** Per-call cost, keyed by `"<METHOD> <path>"`. */
|
|
14
|
+
export declare const X_CALL_WEIGHTS: {
|
|
15
|
+
/** A post or a reply — the org's public voice SPEAKING. The expensive act. */
|
|
16
|
+
readonly write: 5;
|
|
17
|
+
/** Reading who mentioned the org. Cheap: it is the input to a decision, not an act. */
|
|
18
|
+
readonly read: 1;
|
|
19
|
+
/** A media upload call (one-shot, initialize, append, finalize) and its STATUS read. Staging a
|
|
20
|
+
* file publishes nothing — only the post that attaches it speaks — so it is priced as a read:
|
|
21
|
+
* the smallest weight the kernel allows, the post itself still paying the write. */
|
|
22
|
+
readonly media: 1;
|
|
23
|
+
/** Everything else on the vendor: no figure of our own → priced at the write. */
|
|
24
|
+
readonly other: 5;
|
|
25
|
+
};
|
|
26
|
+
/** THE PACK'S DECLARATION — pure data, the only X-specific thing in the whole budget. */
|
|
27
|
+
export declare const X_RATE_BUDGET: RateBudgetDeclaration;
|
|
28
|
+
/**
|
|
29
|
+
* Price one call. The key is `"<METHOD> <path>"` with the query string split off. NORMALIZED
|
|
30
|
+
* (upper-cased method, collapsed trailing slash) because the anchored rules are otherwise
|
|
31
|
+
* trivially evaded by input variation: `fetch` upper-cases a known method before sending, so
|
|
32
|
+
* `execute('post', '/2/tweets')` really does post in public and must be priced as one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function xCallWeight(method: string, path: string): number;
|
|
35
|
+
/** Where this vendor's ledger lives. Token-keyed and cwd-independent by default; pass `root` to
|
|
36
|
+
* opt into world-scoped accounting. */
|
|
37
|
+
export declare function xBudgetPath(opts?: {
|
|
38
|
+
root?: string;
|
|
39
|
+
token?: string;
|
|
40
|
+
} | string): string;
|
|
41
|
+
/** Construction options. The vendor is fixed; everything else may only TIGHTEN. */
|
|
42
|
+
export type XBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
|
|
43
|
+
/**
|
|
44
|
+
* This vendor's budget — the shared kernel guard bound to this declaration. A real subclass, not
|
|
45
|
+
* an alias, so `budget instanceof XBudget` means "a budget that accounts against X's ledger under
|
|
46
|
+
* X's ceiling".
|
|
47
|
+
*/
|
|
48
|
+
export declare class XBudget extends RateBudget {
|
|
49
|
+
constructor(opts?: XBudgetOptions);
|
|
50
|
+
}
|
|
51
|
+
export { RateBudgetError as XBudgetError } from '@volter/world-core';
|
|
52
|
+
export type { RateBudgetErrorKind as XBudgetErrorKind } from '@volter/world-core';
|
|
53
|
+
export type XBudgetReservation = RateBudgetReservation;
|
|
54
|
+
export type XBudgetSnapshot = RateBudgetSnapshot;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// The X posting surface's CLIENT-SIDE RATE BUDGET — this pack's DECLARATION (the numbers) plus
|
|
2
|
+
// the thin typed bindings `liveXExecute` uses. The MECHANISM — the durable token-keyed ledger,
|
|
3
|
+
// the rolling window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a
|
|
4
|
+
// corrupt ledger — lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → rateBudget.ts).
|
|
5
|
+
//
|
|
6
|
+
// ── HOW THE CEILING WAS CHOSEN ──────────────────────────────────────────────────────────────
|
|
7
|
+
// The vendor's SCHEME is first-party and recorded in this repo: X publishes per-endpoint rate
|
|
8
|
+
// limits on 15-minute windows and answers an exceeded window with HTTP 429 and legacy error
|
|
9
|
+
// code 88 (docs.x.com/x-api/fundamentals/rate-limits, fetched 2026-08-21 — the fetch recorded in
|
|
10
|
+
// spec-sources.json's `xidentity` entry, and transcribed by
|
|
11
|
+
// packages/twin/xidentity/src/xidentity-budget.ts). This declaration transcribes the SCHEME: the
|
|
12
|
+
// window is the vendor's own 15 minutes, and 429/code-88 is what the guard expects back.
|
|
13
|
+
//
|
|
14
|
+
// EVIDENCE BOUNDARY — READ THIS BEFORE TRUSTING THE NUMBER. The per-endpoint FIGURES for the
|
|
15
|
+
// POSTING endpoints (`POST /2/tweets`, `DELETE /2/tweets/:id`, `GET /2/users/:id/mentions`) were
|
|
16
|
+
// NOT fetched by this build: no X call is made anywhere in this pack, by standing rule, and the
|
|
17
|
+
// figure recorded in this repo covers `GET /2/users/me` only. So the ceiling here is NOT a
|
|
18
|
+
// transcription — it is a deliberately CONSERVATIVE choice: 50 weighted units per 15 minutes,
|
|
19
|
+
// BELOW every posting figure X is reported to publish, with a WRITE priced at 5 and a read at 1
|
|
20
|
+
// so a runaway poster exhausts the budget an order of magnitude before a runaway reader does.
|
|
21
|
+
// Pinning these to the vendor's published figures is `x.rate_limit.published_figures` (todo).
|
|
22
|
+
// A budget that is too tight fails closed and costs a retry; one that is too loose spends a real
|
|
23
|
+
// org's real posting allowance, so the asymmetry decides the direction.
|
|
24
|
+
import { declareRateBudget, rateBudgetPath, rateBudgetWeight, RateBudget, } from '@volter/world-core';
|
|
25
|
+
const VENDOR = 'x';
|
|
26
|
+
/** Rolling window, in ms — X's own 15-minute rate-limit window. */
|
|
27
|
+
export const X_BUDGET_WINDOW_MS = 15 * 60_000;
|
|
28
|
+
/** Weighted units allowed inside one window. See the EVIDENCE BOUNDARY above: conservative, not
|
|
29
|
+
* transcribed. */
|
|
30
|
+
export const X_BUDGET_CEILING = 50;
|
|
31
|
+
/** Weighted units allowed in any 60s (the kernel's burst sub-window): 10, well under the kernel
|
|
32
|
+
* fallback's burst of 30, so no VENDOR_BURST_ANCHOR entry is required in
|
|
33
|
+
* scripts/rate-budget-isolation.test.ts. */
|
|
34
|
+
export const X_BUDGET_BURST_CEILING = 10;
|
|
35
|
+
/** Seconds. A `Retry-After` above this means the client is throttled hard — fail loudly, don't sleep. */
|
|
36
|
+
export const X_BUDGET_MAX_RETRY_AFTER_S = 900;
|
|
37
|
+
/** Per-call cost, keyed by `"<METHOD> <path>"`. */
|
|
38
|
+
export const X_CALL_WEIGHTS = {
|
|
39
|
+
/** A post or a reply — the org's public voice SPEAKING. The expensive act. */
|
|
40
|
+
write: 5,
|
|
41
|
+
/** Reading who mentioned the org. Cheap: it is the input to a decision, not an act. */
|
|
42
|
+
read: 1,
|
|
43
|
+
/** A media upload call (one-shot, initialize, append, finalize) and its STATUS read. Staging a
|
|
44
|
+
* file publishes nothing — only the post that attaches it speaks — so it is priced as a read:
|
|
45
|
+
* the smallest weight the kernel allows, the post itself still paying the write. */
|
|
46
|
+
media: 1,
|
|
47
|
+
/** Everything else on the vendor: no figure of our own → priced at the write. */
|
|
48
|
+
other: 5,
|
|
49
|
+
};
|
|
50
|
+
/** THE PACK'S DECLARATION — pure data, the only X-specific thing in the whole budget. */
|
|
51
|
+
export const X_RATE_BUDGET = {
|
|
52
|
+
windowMs: X_BUDGET_WINDOW_MS,
|
|
53
|
+
ceiling: X_BUDGET_CEILING,
|
|
54
|
+
burstCeiling: X_BUDGET_BURST_CEILING,
|
|
55
|
+
defaultWeight: X_CALL_WEIGHTS.other,
|
|
56
|
+
maxRetryAfterSeconds: X_BUDGET_MAX_RETRY_AFTER_S,
|
|
57
|
+
rules: [
|
|
58
|
+
{ match: '^POST /2/tweets$', weight: X_CALL_WEIGHTS.write },
|
|
59
|
+
{ match: '^DELETE /2/tweets/', weight: X_CALL_WEIGHTS.write },
|
|
60
|
+
{ match: '^GET /2/users/[^/]+/mentions$', weight: X_CALL_WEIGHTS.read },
|
|
61
|
+
{ match: '^POST /2/media/upload(?:/initialize|/[0-9]+/(?:append|finalize))?$', weight: X_CALL_WEIGHTS.media },
|
|
62
|
+
{ match: '^GET /2/media/upload$', weight: X_CALL_WEIGHTS.media },
|
|
63
|
+
],
|
|
64
|
+
reason: "X publishes per-endpoint rate limits on 15-minute windows and answers an exceeded window with "
|
|
65
|
+
+ 'HTTP 429 and legacy error code 88 (docs.x.com/x-api/fundamentals/rate-limits, fetched '
|
|
66
|
+
+ '2026-08-21 and recorded in spec-sources.json under xidentity). The WINDOW here is the '
|
|
67
|
+
+ "vendor's own 15 minutes and the 429/code-88 pair is what the guard expects back. The "
|
|
68
|
+
+ 'per-endpoint FIGURES for the posting endpoints were NOT fetched by this build — no X call is '
|
|
69
|
+
+ 'made anywhere in this pack, and the figure this repo holds covers GET /2/users/me only — so '
|
|
70
|
+
+ 'the ceiling of 50 units is a deliberately CONSERVATIVE choice rather than a transcription, '
|
|
71
|
+
+ 'sitting below every posting figure X is reported to publish. A write (a post or a reply — the '
|
|
72
|
+
+ "org's public voice speaking, on a real account, in public) costs 5; reading the mentions "
|
|
73
|
+
+ 'timeline costs 1, because reading is the input to a decision and posting is the act. So a '
|
|
74
|
+
+ 'runaway poster exhausts the window after ten posts while a runaway reader gets fifty reads. '
|
|
75
|
+
+ 'A media upload call (one-shot, initialize, append, finalize, STATUS) costs 1: staging a file '
|
|
76
|
+
+ 'publishes nothing, and the post that attaches it still pays the write. '
|
|
77
|
+
+ 'Pinning these to the vendor\'s published posting figures is x.rate_limit.published_figures '
|
|
78
|
+
+ '(todo). A budget that is too tight fails closed and costs a retry; one that is too loose '
|
|
79
|
+
+ "spends a real org's real posting allowance in public, so the asymmetry decides the direction. "
|
|
80
|
+
+ "The burstCeiling of 10 units/60s bounds the instant and stays under the kernel fallback's 30.",
|
|
81
|
+
};
|
|
82
|
+
// Declared at module load, so merely importing this module (which `x-connector.ts` does) is
|
|
83
|
+
// enough to arm the real ceiling.
|
|
84
|
+
declareRateBudget(VENDOR, X_RATE_BUDGET);
|
|
85
|
+
/**
|
|
86
|
+
* Price one call. The key is `"<METHOD> <path>"` with the query string split off. NORMALIZED
|
|
87
|
+
* (upper-cased method, collapsed trailing slash) because the anchored rules are otherwise
|
|
88
|
+
* trivially evaded by input variation: `fetch` upper-cases a known method before sending, so
|
|
89
|
+
* `execute('post', '/2/tweets')` really does post in public and must be priced as one.
|
|
90
|
+
*/
|
|
91
|
+
export function xCallWeight(method, path) {
|
|
92
|
+
const { bare, query } = splitQuery(path);
|
|
93
|
+
return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
|
|
94
|
+
}
|
|
95
|
+
function splitQuery(path) {
|
|
96
|
+
const at = path.indexOf('?');
|
|
97
|
+
const query = {};
|
|
98
|
+
if (at !== -1)
|
|
99
|
+
for (const [k, v] of new URLSearchParams(path.slice(at + 1)))
|
|
100
|
+
query[k] = v;
|
|
101
|
+
const raw = at === -1 ? path : path.slice(0, at);
|
|
102
|
+
const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
|
|
103
|
+
return { bare, query };
|
|
104
|
+
}
|
|
105
|
+
/** Where this vendor's ledger lives. Token-keyed and cwd-independent by default; pass `root` to
|
|
106
|
+
* opt into world-scoped accounting. */
|
|
107
|
+
export function xBudgetPath(opts = {}) {
|
|
108
|
+
const o = typeof opts === 'string' ? { root: opts } : opts;
|
|
109
|
+
// VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through must not
|
|
110
|
+
// redirect this pack's ledger.
|
|
111
|
+
return rateBudgetPath({ ...o, vendor: VENDOR });
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* This vendor's budget — the shared kernel guard bound to this declaration. A real subclass, not
|
|
115
|
+
* an alias, so `budget instanceof XBudget` means "a budget that accounts against X's ledger under
|
|
116
|
+
* X's ceiling".
|
|
117
|
+
*/
|
|
118
|
+
export class XBudget extends RateBudget {
|
|
119
|
+
constructor(opts = {}) {
|
|
120
|
+
super({ ...opts, vendor: VENDOR });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export { RateBudgetError as XBudgetError } from '@volter/world-core';
|