@steve31415/baselib 3.15.0 → 3.16.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 +4 -6
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.js +1 -1
- package/dist/deploy/plan.d.ts +1 -1
- package/dist/deploy/plan.js +1 -1
- package/dist/deploy/types.d.ts +3 -3
- package/dist/llm/complete.js +2 -1
- package/dist/llm/models.d.ts +17 -0
- package/dist/llm/models.js +18 -5
- package/dist/llm/providers/anthropic.js +68 -4
- package/dist/llm/types.d.ts +7 -0
- package/dist/log-core.js +1 -1
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/rum.js +1 -1
- package/dist/s2s.js +1 -1
- package/dist/twitter-client.d.ts +1 -1
- package/dist/twitter-client.js +1 -1
- package/dist/twitter-mirror.js +2 -2
- package/dist/twitter.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# baselib
|
|
2
2
|
|
|
3
|
-
The Plasticine
|
|
3
|
+
The Plasticine fleet's shared platform library (`@steve31415/baselib`).
|
|
4
4
|
What it provides and why: `docs/SPEC.md`. How it's put together:
|
|
5
5
|
`docs/IMPL.md`. Full design rationale with alternatives:
|
|
6
|
-
`~/migration/research/base-services-design.md
|
|
6
|
+
`~/migration/research/base-services-design.md`.
|
|
7
7
|
|
|
8
8
|
Server subpath exports: `config`, `log`, `auth`, `s2s`, `db`, `http`, `sync`,
|
|
9
9
|
`app-update`, `llm`, `email`, and `twitter`. Browser exports: `log-browser`, `rum`,
|
|
@@ -34,17 +34,15 @@ deploy (`~/plasticine-way/docs/OPERATIONS.md`, "Deployment safety").
|
|
|
34
34
|
first step of any production investigation (same doc, "Investigating
|
|
35
35
|
production state").
|
|
36
36
|
|
|
37
|
-
`twitter` provides
|
|
37
|
+
`twitter` provides Mirror-first tweet lookup, TwitterAPI.io batch/search/
|
|
38
38
|
article calls, and thread assembly through direct reply edges. It includes
|
|
39
39
|
one level of quotes, article bodies and URL expansions. Use one shared
|
|
40
40
|
`MirrorTweetClient(mirrorUrl, serviceFetch)` and `TwitterApiIoClient(key)`
|
|
41
41
|
per run; `assembleThread(id, { api, mirror, ancestors: 'all' })` returns
|
|
42
42
|
role-tagged tweets, source counts and warnings. Batch prefetching Mirror
|
|
43
|
-
ids seeds the shared cache.
|
|
43
|
+
ids seeds the shared cache. Reader and Snip retain their copies for now.
|
|
44
44
|
|
|
45
45
|
Releasing: bump `version`, commit, and run `npm run deploy` (Coder runs it
|
|
46
46
|
automatically when integrating a baselib branch) — it pushes the `v<version>`
|
|
47
47
|
tag, whose workflow gates on verify and publishes via OIDC, and waits for the
|
|
48
48
|
registry (`~/plasticine-way/docs/OPERATIONS.md`, "Publishing baselib").
|
|
49
|
-
|
|
50
|
-
Reflects Plasticine Way commit: see `docs/IMPL.md` footer.
|
package/dist/auth.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const SESSION_COOKIE = "pw_session";
|
|
|
6
6
|
/** The `code` in a 403 JSON body when a signed-in user is refused by the
|
|
7
7
|
* app's userAccess policy (as opposed to any other 403). */
|
|
8
8
|
export declare const ACCESS_DENIED_CODE = "app_access_denied";
|
|
9
|
-
/** A user's fleet role, assigned in
|
|
9
|
+
/** A user's fleet role, assigned in auth's users table. */
|
|
10
10
|
export type UserRole = 'owner' | 'invited';
|
|
11
11
|
/** Which signed-in users an app admits. 'owner' (the default): only the
|
|
12
12
|
* fleet owner(s). 'invited': any active fleet user. */
|
|
@@ -39,7 +39,7 @@ export interface RequireIdentityOptions extends S2sOptions {
|
|
|
39
39
|
missMs?: number;
|
|
40
40
|
};
|
|
41
41
|
/** Which signed-in users this app admits. Default 'owner': only users
|
|
42
|
-
* whose
|
|
42
|
+
* whose auth role is owner. Pass 'invited' only for an app that is
|
|
43
43
|
* multi-user by design, and say so in its SPEC. Applies to browser
|
|
44
44
|
* sessions and the test bypass alike; service callers are governed by
|
|
45
45
|
* grants, not by this. */
|
package/dist/auth.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// in-instance (10 min hits / 30 s misses — the old-world numbers).
|
|
13
13
|
//
|
|
14
14
|
// Then the user-access policy: by default an app admits only users whose
|
|
15
|
-
//
|
|
15
|
+
// auth role is 'owner'; `userAccess: 'invited'` admits any active fleet
|
|
16
16
|
// user. Being on the fleet sign-in allowlist never grants access by itself,
|
|
17
17
|
// so a port that forgets the question is closed, not open. Service callers
|
|
18
18
|
// are governed by grants, not by this policy.
|
package/dist/db.d.ts
CHANGED
|
@@ -111,7 +111,7 @@ export declare const FLEET_POOL_DEFAULTS: {
|
|
|
111
111
|
* rule: an advisory lock is not mutual exclusion. Our locks limit
|
|
112
112
|
* duplicated effort; code under one must be safe to overlap, and long
|
|
113
113
|
* work should probe the lock connection periodically (`select 1`) and
|
|
114
|
-
* stop on failure (Plasticine Way `docs/DATABASE.md`;
|
|
114
|
+
* stop on failure (Plasticine Way `docs/DATABASE.md`; mirror's ingestion
|
|
115
115
|
* loop is the reference).
|
|
116
116
|
*
|
|
117
117
|
* `createPool` attaches all of this; exported for a pool an app built
|
package/dist/db.js
CHANGED
|
@@ -117,7 +117,7 @@ const instrumented = new WeakSet();
|
|
|
117
117
|
* rule: an advisory lock is not mutual exclusion. Our locks limit
|
|
118
118
|
* duplicated effort; code under one must be safe to overlap, and long
|
|
119
119
|
* work should probe the lock connection periodically (`select 1`) and
|
|
120
|
-
* stop on failure (Plasticine Way `docs/DATABASE.md`;
|
|
120
|
+
* stop on failure (Plasticine Way `docs/DATABASE.md`; mirror's ingestion
|
|
121
121
|
* loop is the reference).
|
|
122
122
|
*
|
|
123
123
|
* `createPool` attaches all of this; exported for a pool an app built
|
package/dist/deploy/plan.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare const SHA40: RegExp;
|
|
|
3
3
|
/** Lock staleness is judged from the GCS object's server-side timeCreated,
|
|
4
4
|
* never from anything the lock writer wrote (its clock may be wrong). */
|
|
5
5
|
export declare function lockIsStale(timeCreatedIso: string, nowMs: number, staleMs: number): boolean;
|
|
6
|
-
/** Consistent serving base across an app's services (
|
|
6
|
+
/** Consistent serving base across an app's services (notes's rule,
|
|
7
7
|
* generalized): all services on one SHA -> that SHA; some already on the
|
|
8
8
|
* target -> the rest must agree on one other SHA (the base); anything else
|
|
9
9
|
* is a skew that needs a human. A null buildSha (pre-unification revision)
|
package/dist/deploy/plan.js
CHANGED
|
@@ -11,7 +11,7 @@ export function lockIsStale(timeCreatedIso, nowMs, staleMs) {
|
|
|
11
11
|
return false;
|
|
12
12
|
return nowMs - created > staleMs;
|
|
13
13
|
}
|
|
14
|
-
/** Consistent serving base across an app's services (
|
|
14
|
+
/** Consistent serving base across an app's services (notes's rule,
|
|
15
15
|
* generalized): all services on one SHA -> that SHA; some already on the
|
|
16
16
|
* target -> the rest must agree on one other SHA (the base); anything else
|
|
17
17
|
* is a skew that needs a human. A null buildSha (pre-unification revision)
|
package/dist/deploy/types.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface DeployConfig {
|
|
|
51
51
|
* runtime-config change is a commit the deploy carries rather than a hand-run
|
|
52
52
|
* `gcloud run services update`. Undeclared vars are left as they are. */
|
|
53
53
|
updateEnv?: Record<string, string>;
|
|
54
|
-
/** Refuse to deploy across a migrations/ diff (
|
|
54
|
+
/** Refuse to deploy across a migrations/ diff (notes semantics). */
|
|
55
55
|
refuseMigrationDiffOnDeploy?: boolean;
|
|
56
56
|
/** Repo-relative path of an optional ES module exporting hooks. */
|
|
57
57
|
hooks?: string;
|
|
@@ -114,10 +114,10 @@ export interface DeployHooks {
|
|
|
114
114
|
publishAssets?: (ctx: HookContext) => Promise<void>;
|
|
115
115
|
/** Runs after all gates pass, immediately before the first traffic switch. */
|
|
116
116
|
preRelease?: (ctx: HookContext) => Promise<void>;
|
|
117
|
-
/** Runs right after traffic moves (e.g.
|
|
117
|
+
/** Runs right after traffic moves (e.g. todo's announce-build). */
|
|
118
118
|
postTraffic?: (ctx: HookContext) => Promise<void>;
|
|
119
119
|
/** Extra smoke checks after the shared health checks. */
|
|
120
120
|
smoke?: (ctx: HookContext) => Promise<void>;
|
|
121
|
-
/** Runs after everything else succeeded (e.g.
|
|
121
|
+
/** Runs after everything else succeeded (e.g. notes's release state). */
|
|
122
122
|
postSuccess?: (ctx: HookContext) => Promise<void>;
|
|
123
123
|
}
|
package/dist/llm/complete.js
CHANGED
|
@@ -66,7 +66,8 @@ export async function llmComplete(request) {
|
|
|
66
66
|
temperature,
|
|
67
67
|
supportsTemperature: modelEntry.supportsTemperature ?? true,
|
|
68
68
|
supportsThinkingBudgetZero: modelEntry.supportsThinkingBudgetZero ?? true,
|
|
69
|
-
|
|
69
|
+
supportsForcedToolChoice: modelEntry.supportsForcedToolChoice ?? true,
|
|
70
|
+
effort: modelEntry.supportsEffort ? (request.effort ?? modelEntry.defaultEffort) : undefined,
|
|
70
71
|
jsonSchema: request.jsonSchema,
|
|
71
72
|
apiKey,
|
|
72
73
|
logger: request.logger,
|
package/dist/llm/models.d.ts
CHANGED
|
@@ -40,6 +40,23 @@ export interface ModelEntry {
|
|
|
40
40
|
* when the caller's output budget leaves no meaningful headroom.
|
|
41
41
|
*/
|
|
42
42
|
thinksByDefault?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether this model accepts a forced `tool_choice` (`{type: 'tool'}`),
|
|
45
|
+
* which the Anthropic adapter uses to get `jsonSchema` output. Defaults to
|
|
46
|
+
* `true`. Opus 5.5 rejects it with a 400 ("tool_choice: type \"tool\" and
|
|
47
|
+
* \"any\" are not supported for this model.", verified 2026-09-22); for
|
|
48
|
+
* such models the adapter requests structured output via
|
|
49
|
+
* `output_config.format` instead.
|
|
50
|
+
*/
|
|
51
|
+
supportsForcedToolChoice?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Effort level sent when the caller passes none (effort-capable models
|
|
54
|
+
* only). Absent means "send nothing and take the provider's default".
|
|
55
|
+
* Opus 5.5's API default is `medium`, one level below Opus 5's `high`; the
|
|
56
|
+
* registry pins `high` so moving a call site from Opus 5 to Opus 5.5 does
|
|
57
|
+
* not silently lower its effort.
|
|
58
|
+
*/
|
|
59
|
+
defaultEffort?: 'low' | 'medium' | 'high';
|
|
43
60
|
}
|
|
44
61
|
/**
|
|
45
62
|
* Output budget below which a `thinksByDefault` model is considered at risk of
|
package/dist/llm/models.js
CHANGED
|
@@ -10,7 +10,7 @@ export const THINKING_HEADROOM_WARN_TOKENS = 4096;
|
|
|
10
10
|
*/
|
|
11
11
|
export const MODEL_REGISTRY = {
|
|
12
12
|
// 2026-08 suite refresh: current models are claude-sonnet-5 / claude-opus-5
|
|
13
|
-
// (Anthropic), the gpt-5.6 sol/terra/luna family (OpenAI), and the Gemini
|
|
13
|
+
// (Anthropic; claude-opus-5-5 succeeded claude-opus-5 on 2026-09-22), the gpt-5.6 sol/terra/luna family (OpenAI), and the Gemini
|
|
14
14
|
// 3.5/3.6 GA line plus gemini-3.1-pro-preview (Google). Older names stay
|
|
15
15
|
// registered so existing callers keep working, but no fallback chain routes
|
|
16
16
|
// through a retiring model: gemini-2.5-pro and gemini-2.5-flash shut down
|
|
@@ -106,6 +106,19 @@ export const MODEL_REGISTRY = {
|
|
|
106
106
|
fallbacks: ['claude-sonnet-5', 'gemini-3.1-pro-preview'],
|
|
107
107
|
},
|
|
108
108
|
// --- Top tier ---
|
|
109
|
+
'claude-opus-5-5': {
|
|
110
|
+
provider: 'anthropic',
|
|
111
|
+
supportsEffort: true,
|
|
112
|
+
defaultEffort: 'high',
|
|
113
|
+
apiModelId: 'claude-opus-5-5',
|
|
114
|
+
fallbacks: ['gpt-5.6-sol', 'gemini-3.1-pro-preview'],
|
|
115
|
+
// All verified live 2026-09-22: rejects `temperature` and forced
|
|
116
|
+
// tool_choice (400 each); thinking is always on and its tokens count
|
|
117
|
+
// against `maxTokens`.
|
|
118
|
+
supportsTemperature: false,
|
|
119
|
+
supportsForcedToolChoice: false,
|
|
120
|
+
thinksByDefault: true,
|
|
121
|
+
},
|
|
109
122
|
'claude-opus-5': {
|
|
110
123
|
provider: 'anthropic',
|
|
111
124
|
supportsEffort: true,
|
|
@@ -136,14 +149,14 @@ export const MODEL_REGISTRY = {
|
|
|
136
149
|
'gpt-5.6-sol': {
|
|
137
150
|
provider: 'openai',
|
|
138
151
|
apiModelId: 'gpt-5.6-sol',
|
|
139
|
-
fallbacks: ['claude-opus-5', 'gemini-3.1-pro-preview'],
|
|
152
|
+
fallbacks: ['claude-opus-5-5', 'gemini-3.1-pro-preview'],
|
|
140
153
|
supportsTemperature: false,
|
|
141
154
|
thinksByDefault: true,
|
|
142
155
|
},
|
|
143
156
|
'gpt-5.2': {
|
|
144
157
|
provider: 'openai',
|
|
145
158
|
apiModelId: 'gpt-5.2',
|
|
146
|
-
fallbacks: ['claude-opus-5', 'gemini-3.1-pro-preview'],
|
|
159
|
+
fallbacks: ['claude-opus-5-5', 'gemini-3.1-pro-preview'],
|
|
147
160
|
},
|
|
148
161
|
// Google's best Pro model as of 2026-08 — still Preview-labeled (no GA
|
|
149
162
|
// Gemini 3.x Pro exists), but the only Pro-class option once 2.5-pro
|
|
@@ -151,7 +164,7 @@ export const MODEL_REGISTRY = {
|
|
|
151
164
|
'gemini-3.1-pro-preview': {
|
|
152
165
|
provider: 'gemini',
|
|
153
166
|
apiModelId: 'gemini-3.1-pro-preview',
|
|
154
|
-
fallbacks: ['claude-opus-5', 'gpt-5.6-sol'],
|
|
167
|
+
fallbacks: ['claude-opus-5-5', 'gpt-5.6-sol'],
|
|
155
168
|
// Thinks by default (thinking_level defaults to `high`).
|
|
156
169
|
thinksByDefault: true,
|
|
157
170
|
// Rejects thinkingBudget 0 (400, verified 2026-08-09).
|
|
@@ -161,6 +174,6 @@ export const MODEL_REGISTRY = {
|
|
|
161
174
|
'gemini-2.5-pro': {
|
|
162
175
|
provider: 'gemini',
|
|
163
176
|
apiModelId: 'gemini-2.5-pro',
|
|
164
|
-
fallbacks: ['claude-opus-5', 'gpt-5.6-sol'],
|
|
177
|
+
fallbacks: ['claude-opus-5-5', 'gpt-5.6-sol'],
|
|
165
178
|
},
|
|
166
179
|
};
|
|
@@ -12,6 +12,51 @@ function classifyFinish(stopReason) {
|
|
|
12
12
|
default: return 'other';
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Keywords `output_config.format` rejects outright with a 400 (verified on
|
|
17
|
+
* Opus 5.5, 2026-09-22). `minItems` is also rejected for values other than 0
|
|
18
|
+
* or 1, handled separately; `minLength`/`maxLength`, `pattern` and `format`
|
|
19
|
+
* are accepted.
|
|
20
|
+
*/
|
|
21
|
+
const UNSUPPORTED_FORMAT_KEYWORDS = [
|
|
22
|
+
'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',
|
|
23
|
+
'maxItems', 'uniqueItems',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Transform a portable JSON schema into the dialect Anthropic's structured
|
|
27
|
+
* outputs (`output_config.format`) accepts: every object schema must set
|
|
28
|
+
* `additionalProperties: false`, and the numeric/array bounds listed above are
|
|
29
|
+
* dropped. The model then no longer enforces those bounds — callers validate
|
|
30
|
+
* the parsed result as they already must for truncation. Unlike forced
|
|
31
|
+
* tool_use, optional properties stay optional. Returns a deep copy — the
|
|
32
|
+
* caller's schema object is reused across failover attempts.
|
|
33
|
+
*/
|
|
34
|
+
function toOutputFormatSchema(node) {
|
|
35
|
+
if (Array.isArray(node))
|
|
36
|
+
return node.map(toOutputFormatSchema);
|
|
37
|
+
if (node === null || typeof node !== 'object')
|
|
38
|
+
return node;
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [key, value] of Object.entries(node)) {
|
|
41
|
+
if (UNSUPPORTED_FORMAT_KEYWORDS.includes(key))
|
|
42
|
+
continue;
|
|
43
|
+
if (key === 'minItems' && typeof value === 'number' && value > 1)
|
|
44
|
+
continue;
|
|
45
|
+
// Maps whose values are schemas (not keywords): recurse per entry so a
|
|
46
|
+
// property that happens to be named `minimum` is kept.
|
|
47
|
+
if ((key === 'properties' || key === '$defs' || key === 'definitions')
|
|
48
|
+
&& value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
49
|
+
out[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toOutputFormatSchema(v)]));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
out[key] = toOutputFormatSchema(value);
|
|
53
|
+
}
|
|
54
|
+
const type = out.type;
|
|
55
|
+
if (type === 'object' || (Array.isArray(type) && type.includes('object'))) {
|
|
56
|
+
out.additionalProperties = false;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
15
60
|
export const anthropicProvider = {
|
|
16
61
|
name: 'anthropic',
|
|
17
62
|
async complete(request) {
|
|
@@ -26,7 +71,7 @@ export const anthropicProvider = {
|
|
|
26
71
|
max_tokens: request.maxTokens,
|
|
27
72
|
messages: [{ role: 'user', content: request.prompt }],
|
|
28
73
|
};
|
|
29
|
-
// Some models (Opus 4.7+, Sonnet 5, Fable 5) reject `temperature`
|
|
74
|
+
// Some models (Opus 4.7+, Sonnet 5, Fable 5, Opus 5.5) reject `temperature`
|
|
30
75
|
// outright with a 400 — omit the field entirely rather than sending it.
|
|
31
76
|
if (request.supportsTemperature) {
|
|
32
77
|
body.temperature = request.temperature;
|
|
@@ -42,8 +87,16 @@ export const anthropicProvider = {
|
|
|
42
87
|
? [{ type: 'text', text: request.systemPrompt, cache_control: { type: 'ephemeral' } }]
|
|
43
88
|
: request.systemPrompt;
|
|
44
89
|
}
|
|
45
|
-
// JSON schema:
|
|
46
|
-
|
|
90
|
+
// JSON schema: a forced tool_use call where the model allows it, else
|
|
91
|
+
// structured outputs (Opus 5.5 rejects forced tool_choice).
|
|
92
|
+
const useOutputFormat = request.jsonSchema !== undefined && !request.supportsForcedToolChoice;
|
|
93
|
+
if (request.jsonSchema && useOutputFormat) {
|
|
94
|
+
body.output_config = {
|
|
95
|
+
...body.output_config,
|
|
96
|
+
format: { type: 'json_schema', schema: toOutputFormatSchema(request.jsonSchema.schema) },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
else if (request.jsonSchema) {
|
|
47
100
|
body.tools = [{
|
|
48
101
|
name: request.jsonSchema.name,
|
|
49
102
|
description: `Generate structured output matching the ${request.jsonSchema.name} schema`,
|
|
@@ -103,7 +156,18 @@ export const anthropicProvider = {
|
|
|
103
156
|
const finishReason = data.stop_reason ?? undefined;
|
|
104
157
|
let text;
|
|
105
158
|
let json;
|
|
106
|
-
if (
|
|
159
|
+
if (useOutputFormat) {
|
|
160
|
+
text = data.content?.find((c) => c.type === 'text')?.text || '';
|
|
161
|
+
try {
|
|
162
|
+
json = text ? JSON.parse(text) : undefined;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Truncated or malformed body: surface as json undefined, like the
|
|
166
|
+
// tool_use path, so the caller's truncation handling applies.
|
|
167
|
+
json = undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (request.jsonSchema) {
|
|
107
171
|
const toolUse = data.content?.find((c) => c.type === 'tool_use');
|
|
108
172
|
json = toolUse?.input;
|
|
109
173
|
text = json !== undefined ? JSON.stringify(json) : '';
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -176,6 +176,13 @@ export interface ProviderCallRequest {
|
|
|
176
176
|
* model would 400); non-zero budgets pass through unchanged.
|
|
177
177
|
*/
|
|
178
178
|
supportsThinkingBudgetZero: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Whether the target model accepts a forced `tool_choice`, per the registry
|
|
181
|
+
* entry's `supportsForcedToolChoice` flag. When `false`, the Anthropic
|
|
182
|
+
* adapter gets `jsonSchema` output through `output_config.format` instead
|
|
183
|
+
* of a forced tool call. Other providers ignore it.
|
|
184
|
+
*/
|
|
185
|
+
supportsForcedToolChoice: boolean;
|
|
179
186
|
jsonSchema?: JsonSchema;
|
|
180
187
|
apiKey: string;
|
|
181
188
|
logger?: Logger;
|
package/dist/log-core.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// bounded, backed-off retries and per-batch identity
|
|
7
7
|
// (~/migration/research/logging-reliability-design.md): every undelivered
|
|
8
8
|
// batch either lands in Axiom or ends in an onGiveUp callback — the server
|
|
9
|
-
// logger turns those into stdout drop markers that
|
|
9
|
+
// logger turns those into stdout drop markers that watchdog's daily
|
|
10
10
|
// ship-failure check counts. makeEvent also guards the shape of `meta`: every
|
|
11
11
|
// distinct key is an Axiom column (see "Key-shape guard" below).
|
|
12
12
|
export const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
|
package/dist/log.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface LoggerOptions {
|
|
|
15
15
|
context?: Record<string, unknown>;
|
|
16
16
|
fetchFn?: typeof fetch;
|
|
17
17
|
}
|
|
18
|
-
/** Register a directly-constructed shipper (e.g.
|
|
18
|
+
/** Register a directly-constructed shipper (e.g. watchdog's platform-dataset
|
|
19
19
|
* shipper) so flushAllLoggers and shutdown cover it too. */
|
|
20
20
|
export declare function registerShipper(shipper: LogShipper): void;
|
|
21
21
|
/** Flush every registered shipper. `final` is the shutdown path: time-boxed
|
package/dist/log.js
CHANGED
|
@@ -19,7 +19,7 @@ export { LogShipper, makeEvent, makeEvents, serializeError, setLogShapeStrict, t
|
|
|
19
19
|
// with() children share their root's shipper, so registering loggers leaked
|
|
20
20
|
// one entry per child.
|
|
21
21
|
const allShippers = new Set();
|
|
22
|
-
/** Register a directly-constructed shipper (e.g.
|
|
22
|
+
/** Register a directly-constructed shipper (e.g. watchdog's platform-dataset
|
|
23
23
|
* shipper) so flushAllLoggers and shutdown cover it too. */
|
|
24
24
|
export function registerShipper(shipper) {
|
|
25
25
|
allShippers.add(shipper);
|
package/dist/rum.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Real-user monitoring: Core Web Vitals shipped to the Axiom `rum` dataset
|
|
2
|
-
// (targets: ~/
|
|
2
|
+
// (targets: ~/plasticine-way/docs/PERFORMANCE.md). Configured by the
|
|
3
3
|
// same pw-log-* meta tags as log-browser; token-less pages (local dev)
|
|
4
4
|
// no-op. Metrics finalize on tab-hide, so the shipper flushes there with
|
|
5
5
|
// keepalive fetch.
|
package/dist/s2s.js
CHANGED
|
@@ -99,7 +99,7 @@ export async function resolveS2sCaller(opts, headers, method, path) {
|
|
|
99
99
|
// ("Wrong number of segments in token: <jwt>", "Invalid token signature:
|
|
100
100
|
// <jwt>"), so the error is never logged as-is: the token's shape and a
|
|
101
101
|
// redacted message only (PW LESSONS 2026-09-02 — log the shape, never
|
|
102
|
-
// the credential). Found by
|
|
102
|
+
// the credential). Found by Mirror's device-key design review.
|
|
103
103
|
opts.logger?.warn('s2s token verification failed', {
|
|
104
104
|
path,
|
|
105
105
|
reason: redactToken(err instanceof Error ? err.message : String(err), m[1]),
|
package/dist/twitter-client.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare class TwitterApiError extends Error {
|
|
|
55
55
|
constructor(message: string, apiCalls: number);
|
|
56
56
|
}
|
|
57
57
|
export declare function assertTweetId(id: string): void;
|
|
58
|
-
/** Harvested from
|
|
58
|
+
/** Harvested from Reader/Mirror: bounded retries and timeout, four active
|
|
59
59
|
* requests per shared client. Credentials and upstream bodies are never logged. */
|
|
60
60
|
export declare class TwitterApiIoClient implements TwitterClient {
|
|
61
61
|
private readonly apiKey;
|
package/dist/twitter-client.js
CHANGED
|
@@ -17,7 +17,7 @@ export function assertTweetId(id) {
|
|
|
17
17
|
if (!/^\d{1,25}$/.test(id))
|
|
18
18
|
throw new Error('Invalid tweet id');
|
|
19
19
|
}
|
|
20
|
-
/** Harvested from
|
|
20
|
+
/** Harvested from Reader/Mirror: bounded retries and timeout, four active
|
|
21
21
|
* requests per shared client. Credentials and upstream bodies are never logged. */
|
|
22
22
|
export class TwitterApiIoClient {
|
|
23
23
|
apiKey;
|
package/dist/twitter-mirror.js
CHANGED
|
@@ -50,10 +50,10 @@ export class MirrorTweetClient {
|
|
|
50
50
|
signal: AbortSignal.timeout(30_000),
|
|
51
51
|
});
|
|
52
52
|
if (!response.ok)
|
|
53
|
-
throw new Error(`
|
|
53
|
+
throw new Error(`Mirror tweets query HTTP ${response.status}`);
|
|
54
54
|
const data = await response.json();
|
|
55
55
|
if (!Array.isArray(data.records))
|
|
56
|
-
throw new Error('
|
|
56
|
+
throw new Error('Mirror response has no records array');
|
|
57
57
|
const tweets = new Map();
|
|
58
58
|
for (const r of data.records) {
|
|
59
59
|
if (r.is_deleted || r.deleted_at)
|
package/dist/twitter.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function assembleThread(id, options) {
|
|
|
67
67
|
add(await mirror.getTweetsByIds(missing), 'mirror2');
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
warning('
|
|
70
|
+
warning('Mirror lookup failed', error);
|
|
71
71
|
}
|
|
72
72
|
const absent = missing.filter((key) => !records.has(key));
|
|
73
73
|
if (absent.length)
|
|
@@ -85,10 +85,10 @@ export async function assembleThread(id, options) {
|
|
|
85
85
|
add(conversation.tweets, 'mirror2');
|
|
86
86
|
archivedAuthor = conversation.complete && conversation.tweets.some((tweet) => sameAuthor(tweet, linked));
|
|
87
87
|
if (!conversation.complete)
|
|
88
|
-
warning('
|
|
88
|
+
warning('Mirror conversation reached its row limit; searching the author via API');
|
|
89
89
|
}
|
|
90
90
|
catch (error) {
|
|
91
|
-
warning('
|
|
91
|
+
warning('Mirror conversation failed', error);
|
|
92
92
|
}
|
|
93
93
|
if (!archivedAuthor && /^\w+$/.test(linked.author.userName)) {
|
|
94
94
|
let cursor;
|
package/package.json
CHANGED