@reproapp/node-sdk 0.0.3 → 0.0.4

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.
Files changed (45) hide show
  1. package/README.md +36 -2
  2. package/dist/index.d.ts +140 -15
  3. package/dist/index.js +4927 -927
  4. package/dist/ingest/client.d.ts +10 -0
  5. package/dist/ingest/client.js +158 -0
  6. package/dist/ingest/mapper.d.ts +2 -0
  7. package/dist/ingest/mapper.js +92 -0
  8. package/dist/ingest/types.d.ts +40 -0
  9. package/dist/ingest/types.js +2 -0
  10. package/dist/ingest/worker.js +19 -0
  11. package/dist/integrations/sendgrid.d.ts +2 -4
  12. package/dist/integrations/sendgrid.js +4 -14
  13. package/dist/privacy-fallback.d.ts +1 -0
  14. package/dist/privacy-fallback.js +27 -0
  15. package/dist/privacy-redaction.d.ts +3 -0
  16. package/dist/privacy-redaction.js +38 -0
  17. package/dist/privacy.d.ts +108 -0
  18. package/dist/privacy.js +2868 -0
  19. package/dist/trace-materializer-worker.d.ts +1 -0
  20. package/dist/trace-materializer-worker.js +33 -0
  21. package/docs/tracing.md +1 -0
  22. package/package.json +8 -2
  23. package/src/index.ts +5583 -954
  24. package/src/ingest/client.ts +194 -0
  25. package/src/ingest/mapper.ts +104 -0
  26. package/src/ingest/types.ts +42 -0
  27. package/src/integrations/sendgrid.ts +6 -19
  28. package/src/privacy-fallback.ts +25 -0
  29. package/src/privacy-redaction.ts +37 -0
  30. package/src/privacy.ts +3593 -0
  31. package/src/trace-materializer-worker.ts +39 -0
  32. package/test/circular-capture.test.js +111 -0
  33. package/test/disable-subtree.test.js +154 -0
  34. package/test/integration-unawaited.js +183 -0
  35. package/test/kafka-runtime-privacy-policy.test.js +285 -0
  36. package/test/privacy-runtime-policy.test.js +2043 -0
  37. package/test/promise-map.test.js +72 -0
  38. package/test/unawaited.test.js +163 -0
  39. package/test/wrap-plugin-arrow-args.test.js +80 -0
  40. package/tracer/cjs-hook.js +0 -1
  41. package/tracer/wrap-plugin.js +96 -10
  42. package/dist/redaction.d.ts +0 -44
  43. package/dist/redaction.js +0 -167
  44. package/dist/server.js +0 -26
  45. /package/dist/{server.d.ts → ingest/worker.d.ts} +0 -0
@@ -0,0 +1,10 @@
1
+ import type { IngestClientConfig, LegacyEntry } from './types';
2
+ export declare const configureIngestQueueDrain: (options: {
3
+ shouldDefer?: (() => boolean) | null;
4
+ deferMs?: number;
5
+ }) => void;
6
+ export declare const kickIngestQueueDrain: () => void;
7
+ export declare const postIngestEntries: (config: IngestClientConfig, sessionId: string, entries: LegacyEntry[]) => Promise<void>;
8
+ export declare const enqueueIngestEntries: (config: IngestClientConfig, sessionId: string, entries: LegacyEntry[]) => void;
9
+ export declare const flushIngestQueue: () => Promise<void>;
10
+ export type { IngestClientConfig, LegacyEntry } from './types';
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.flushIngestQueue = exports.enqueueIngestEntries = exports.postIngestEntries = exports.kickIngestQueueDrain = exports.configureIngestQueueDrain = void 0;
4
+ const mapper_1 = require("./mapper");
5
+ const DEFAULT_INGEST_BASE = 'http://localhost:8080';
6
+ const DEFAULT_INGEST_PATH = '/v1/ingest/events';
7
+ const DEFAULT_QUEUE_MAX_ITEMS = 1000;
8
+ const DEFAULT_QUEUE_MAX_ATTEMPTS = 5;
9
+ const DEFAULT_QUEUE_BACKOFF_MAX_MS = 4000;
10
+ const DEFAULT_DEFER_DRAIN_MS = 25;
11
+ const ingestQueue = [];
12
+ let drainTimer = null;
13
+ let draining = false;
14
+ let backoffMs = 0;
15
+ let shouldDeferDrain = null;
16
+ let deferDrainMs = DEFAULT_DEFER_DRAIN_MS;
17
+ const configureIngestQueueDrain = (options) => {
18
+ shouldDeferDrain = options.shouldDefer ?? null;
19
+ if (Number.isFinite(options.deferMs) && Number(options.deferMs) >= 0) {
20
+ deferDrainMs = Number(options.deferMs);
21
+ }
22
+ };
23
+ exports.configureIngestQueueDrain = configureIngestQueueDrain;
24
+ const kickIngestQueueDrain = () => {
25
+ if (ingestQueue.length > 0 && !drainTimer) {
26
+ scheduleDrain(shouldDeferDrain?.() ? deferDrainMs : 0);
27
+ }
28
+ };
29
+ exports.kickIngestQueueDrain = kickIngestQueueDrain;
30
+ const resolveIngestUrl = (config) => {
31
+ const envBase = typeof process !== 'undefined' ? process?.env?.REPRO_INGEST_BASE : undefined;
32
+ const ingestBase = String(envBase || config.ingestBase || DEFAULT_INGEST_BASE).replace(/\/+$/, '');
33
+ const ingestPath = config.ingestPath || DEFAULT_INGEST_PATH;
34
+ const normalizedPath = ingestPath.startsWith('/') ? ingestPath : `/${ingestPath}`;
35
+ return `${ingestBase}${normalizedPath}`;
36
+ };
37
+ const toHeaders = (config) => {
38
+ const headers = {
39
+ 'Content-Type': 'application/json',
40
+ 'X-App-Id': config.appId,
41
+ };
42
+ if (config.appSecret) {
43
+ headers['X-App-Secret'] = config.appSecret;
44
+ }
45
+ if (config.appName) {
46
+ headers['X-App-Name'] = config.appName;
47
+ }
48
+ return headers;
49
+ };
50
+ const postIngestEntries = async (config, sessionId, entries) => {
51
+ await sendIngestEntries(config, sessionId, entries);
52
+ };
53
+ exports.postIngestEntries = postIngestEntries;
54
+ const sendIngestEntries = async (config, sessionId, entries) => {
55
+ if (!sessionId || !Array.isArray(entries) || entries.length === 0) {
56
+ return true;
57
+ }
58
+ const events = (0, mapper_1.mapLegacyEntriesToCanonicalEvents)(config, sessionId, entries);
59
+ if (!events.length) {
60
+ return true;
61
+ }
62
+ try {
63
+ const response = await fetch(resolveIngestUrl(config), {
64
+ method: 'POST',
65
+ headers: toHeaders(config),
66
+ body: JSON.stringify({ events }),
67
+ });
68
+ return response.ok;
69
+ }
70
+ catch {
71
+ // swallow in SDK
72
+ return false;
73
+ }
74
+ };
75
+ const scheduleDrain = (delayMs = 0) => {
76
+ if (drainTimer) {
77
+ return;
78
+ }
79
+ const nextDelay = shouldDeferDrain?.()
80
+ ? Math.max(delayMs, deferDrainMs)
81
+ : delayMs;
82
+ drainTimer = setTimeout(() => {
83
+ drainTimer = null;
84
+ if (shouldDeferDrain?.()) {
85
+ scheduleDrain(deferDrainMs);
86
+ return;
87
+ }
88
+ void drainIngestQueue();
89
+ }, nextDelay);
90
+ try {
91
+ drainTimer.unref?.();
92
+ }
93
+ catch { }
94
+ };
95
+ const drainIngestQueue = async () => {
96
+ if (draining) {
97
+ return;
98
+ }
99
+ if (shouldDeferDrain?.()) {
100
+ scheduleDrain(deferDrainMs);
101
+ return;
102
+ }
103
+ draining = true;
104
+ try {
105
+ while (ingestQueue.length > 0) {
106
+ if (shouldDeferDrain?.()) {
107
+ scheduleDrain(deferDrainMs);
108
+ return;
109
+ }
110
+ const batch = ingestQueue[0];
111
+ const ok = await sendIngestEntries(batch.config, batch.sessionId, batch.entries);
112
+ if (ok) {
113
+ ingestQueue.shift();
114
+ backoffMs = 0;
115
+ continue;
116
+ }
117
+ batch.attempts += 1;
118
+ if (batch.attempts >= DEFAULT_QUEUE_MAX_ATTEMPTS) {
119
+ ingestQueue.shift();
120
+ backoffMs = 0;
121
+ continue;
122
+ }
123
+ backoffMs = Math.min(DEFAULT_QUEUE_BACKOFF_MAX_MS, backoffMs ? backoffMs * 2 : 250);
124
+ scheduleDrain(backoffMs);
125
+ return;
126
+ }
127
+ }
128
+ finally {
129
+ draining = false;
130
+ if (ingestQueue.length > 0 && !drainTimer) {
131
+ scheduleDrain(backoffMs || deferDrainMs);
132
+ }
133
+ }
134
+ };
135
+ const enqueueIngestEntries = (config, sessionId, entries) => {
136
+ if (!sessionId || !Array.isArray(entries) || entries.length === 0) {
137
+ return;
138
+ }
139
+ ingestQueue.push({
140
+ config: { ...config },
141
+ sessionId,
142
+ entries: entries.slice(),
143
+ attempts: 0,
144
+ });
145
+ if (ingestQueue.length > DEFAULT_QUEUE_MAX_ITEMS) {
146
+ ingestQueue.splice(0, ingestQueue.length - DEFAULT_QUEUE_MAX_ITEMS);
147
+ }
148
+ scheduleDrain(shouldDeferDrain?.() ? deferDrainMs : 0);
149
+ };
150
+ exports.enqueueIngestEntries = enqueueIngestEntries;
151
+ const flushIngestQueue = async () => {
152
+ if (drainTimer) {
153
+ clearTimeout(drainTimer);
154
+ drainTimer = null;
155
+ }
156
+ await drainIngestQueue();
157
+ };
158
+ exports.flushIngestQueue = flushIngestQueue;
@@ -0,0 +1,2 @@
1
+ import type { CanonicalEvent, IngestClientConfig, LegacyEntry } from './types';
2
+ export declare const mapLegacyEntriesToCanonicalEvents: (config: IngestClientConfig, sessionId: string, entries: LegacyEntry[]) => CanonicalEvent[];
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapLegacyEntriesToCanonicalEvents = void 0;
4
+ const toIso = (timestamp) => {
5
+ const numeric = typeof timestamp === 'number' && Number.isFinite(timestamp) ? timestamp : Date.now();
6
+ return new Date(numeric).toISOString();
7
+ };
8
+ const inferEventType = (entry) => {
9
+ if (entry.request !== undefined)
10
+ return 'backend_request';
11
+ if (entry.db !== undefined)
12
+ return 'db_change';
13
+ if (entry.trace !== undefined)
14
+ return 'trace_batch';
15
+ if (entry.email !== undefined)
16
+ return 'email_event';
17
+ return 'backend_event';
18
+ };
19
+ const toActorLabels = (config) => {
20
+ if (!config.actorLabels)
21
+ return undefined;
22
+ const labels = Object.entries(config.actorLabels).reduce((acc, [key, value]) => {
23
+ if (!key)
24
+ return acc;
25
+ if (typeof value !== 'string')
26
+ return acc;
27
+ acc[key] = value;
28
+ return acc;
29
+ }, {});
30
+ return Object.keys(labels).length ? labels : undefined;
31
+ };
32
+ const resolveServiceName = (config) => {
33
+ const candidate = config.serviceName ?? config.appName;
34
+ if (typeof candidate !== 'string') {
35
+ return undefined;
36
+ }
37
+ const normalized = candidate.trim();
38
+ return normalized.length ? normalized : undefined;
39
+ };
40
+ const toPayload = (entry) => {
41
+ const payload = {
42
+ action_id: entry.actionId ?? null,
43
+ };
44
+ const normalizeFieldValue = (key, value) => {
45
+ if (key !== 'trace' || typeof value !== 'string') {
46
+ return value;
47
+ }
48
+ try {
49
+ const parsed = JSON.parse(value);
50
+ return Array.isArray(parsed) ? parsed : [];
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ };
56
+ for (const [key, value] of Object.entries(entry)) {
57
+ if (key === 't' || key === 'actionId')
58
+ continue;
59
+ payload[key] = normalizeFieldValue(key, value);
60
+ }
61
+ return payload;
62
+ };
63
+ const mapLegacyEntriesToCanonicalEvents = (config, sessionId, entries) => {
64
+ const actorLabels = toActorLabels(config);
65
+ const actorId = config.actorId ?? config.appId;
66
+ const actorType = config.actorType ?? 'service';
67
+ const schemaVersion = config.schemaVersion ?? 1;
68
+ const serviceName = resolveServiceName(config);
69
+ return entries.map((entry) => {
70
+ const payload = toPayload(entry);
71
+ if (serviceName &&
72
+ (typeof payload.serviceName !== 'string' || !String(payload.serviceName).trim().length)) {
73
+ payload.serviceName = serviceName;
74
+ }
75
+ const canonical = {
76
+ schema_version: schemaVersion,
77
+ tenant_id: config.tenantId,
78
+ app_id: config.appId,
79
+ session_id: sessionId,
80
+ event_type: inferEventType(entry),
81
+ event_ts: toIso(entry.t),
82
+ actor_id: actorId,
83
+ actor_type: actorType,
84
+ payload,
85
+ };
86
+ if (actorLabels) {
87
+ canonical.actor_labels = actorLabels;
88
+ }
89
+ return canonical;
90
+ });
91
+ };
92
+ exports.mapLegacyEntriesToCanonicalEvents = mapLegacyEntriesToCanonicalEvents;
@@ -0,0 +1,40 @@
1
+ export interface IngestClientConfig {
2
+ appId: string;
3
+ tenantId: string;
4
+ appSecret?: string;
5
+ appName?: string;
6
+ serviceName?: string;
7
+ ingestBase?: string;
8
+ ingestPath?: string;
9
+ actorId?: string;
10
+ actorType?: string;
11
+ actorLabels?: Record<string, string>;
12
+ source?: string;
13
+ schemaVersion?: number;
14
+ }
15
+ export interface LegacyEntry {
16
+ actionId?: string | null;
17
+ requestRid?: string | null;
18
+ request?: unknown;
19
+ requestValues?: unknown[];
20
+ db?: unknown;
21
+ dbValues?: unknown[];
22
+ trace?: unknown[];
23
+ traceBatch?: unknown;
24
+ traceValues?: unknown[];
25
+ email?: unknown;
26
+ t?: number;
27
+ [key: string]: unknown;
28
+ }
29
+ export interface CanonicalEvent {
30
+ schema_version: number;
31
+ tenant_id: string;
32
+ app_id: string;
33
+ session_id: string;
34
+ event_type: string;
35
+ event_ts: string;
36
+ actor_id: string;
37
+ actor_type: string;
38
+ actor_labels?: Record<string, string>;
39
+ payload: Record<string, unknown>;
40
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_worker_threads_1 = require("node:worker_threads");
4
+ const client_1 = require("./client");
5
+ const isObject = (value) => value !== null && typeof value === 'object';
6
+ const isMessage = (value) => {
7
+ if (!isObject(value)) {
8
+ return false;
9
+ }
10
+ return (isObject(value.config) &&
11
+ typeof value.sessionId === 'string' &&
12
+ Array.isArray(value.entries));
13
+ };
14
+ node_worker_threads_1.parentPort?.on('message', (message) => {
15
+ if (!isMessage(message)) {
16
+ return;
17
+ }
18
+ void (0, client_1.postIngestEntries)(message.config, message.sessionId, message.entries).catch(() => undefined);
19
+ });
@@ -1,12 +1,10 @@
1
+ import { type IngestClientConfig } from '../ingest/client';
1
2
  type Ctx = {
2
3
  sid?: string;
3
4
  aid?: string;
4
5
  };
5
6
  export declare const getCtx: () => Ctx;
6
- export type SendgridPatchConfig = {
7
- appId: string;
8
- appSecret: string;
9
- appName?: string;
7
+ export type SendgridPatchConfig = IngestClientConfig & {
10
8
  resolveContext?: () => {
11
9
  sid?: string;
12
10
  aid?: string;
@@ -3,25 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.patchSendgridMail = exports.getCtx = void 0;
4
4
  // repro-node/src/integrations/sendgrid.ts
5
5
  const async_hooks_1 = require("async_hooks");
6
+ const client_1 = require("../ingest/client");
6
7
  const als = new async_hooks_1.AsyncLocalStorage();
7
8
  const getCtx = () => als.getStore() || {};
8
9
  exports.getCtx = getCtx;
9
10
  // If you already export als/getCtx from repro-node, reuse that instead of re-declaring.
10
- async function post(cfg, sessionId, body) {
11
+ function post(cfg, sessionId, body) {
11
12
  try {
12
- const envBase = typeof process !== 'undefined' ? process?.env?.REPRO_API_BASE : undefined;
13
- const legacyBase = cfg?.apiBase;
14
- const apiBase = String(envBase || legacyBase || 'https://repro-api-d7288.ondigitalocean.app/api').replace(/\/+$/, '');
15
- await fetch(`${apiBase}/v1/sessions/${sessionId}/backend`, {
16
- method: 'POST',
17
- headers: {
18
- 'Content-Type': 'application/json',
19
- 'X-App-Id': cfg.appId,
20
- 'X-App-Secret': cfg.appSecret,
21
- ...(cfg.appName ? { 'X-App-Name': cfg.appName } : {}),
22
- },
23
- body: JSON.stringify(body),
24
- });
13
+ const entries = Array.isArray(body?.entries) ? body.entries : [];
14
+ (0, client_1.enqueueIngestEntries)(cfg, sessionId, entries);
25
15
  }
26
16
  catch { /* swallow */ }
27
17
  }
@@ -0,0 +1 @@
1
+ export declare function sanitizeUnknownBoundaryTextWithDetectors(value: string): Promise<string>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeUnknownBoundaryTextWithDetectors = void 0;
4
+ const SANITIZED_TEXT_CACHE = new Map();
5
+ const SANITIZED_TEXT_CACHE_MAX = 500;
6
+ async function sanitizeUnknownBoundaryTextWithDetectors(value) {
7
+ if (!value) {
8
+ return value;
9
+ }
10
+ const cached = SANITIZED_TEXT_CACHE.get(value);
11
+ if (cached !== undefined) {
12
+ return cached;
13
+ }
14
+ rememberSanitizedText(value, value);
15
+ return value;
16
+ }
17
+ exports.sanitizeUnknownBoundaryTextWithDetectors = sanitizeUnknownBoundaryTextWithDetectors;
18
+ function rememberSanitizedText(input, output) {
19
+ SANITIZED_TEXT_CACHE.set(input, output);
20
+ if (SANITIZED_TEXT_CACHE.size <= SANITIZED_TEXT_CACHE_MAX) {
21
+ return;
22
+ }
23
+ const oldestKey = SANITIZED_TEXT_CACHE.keys().next().value;
24
+ if (typeof oldestKey === 'string') {
25
+ SANITIZED_TEXT_CACHE.delete(oldestKey);
26
+ }
27
+ }
@@ -0,0 +1,3 @@
1
+ export declare function redactEmailDeterministic(value: string): string;
2
+ export declare function redactEmailsInText(value: string): string;
3
+ export declare function collectEmailsInText(value: string): string[];
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectEmailsInText = exports.redactEmailsInText = exports.redactEmailDeterministic = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
6
+ const EMAIL_TOKEN_PREFIX = 'email_tok_';
7
+ const EMAIL_TOKEN_LENGTH = 12;
8
+ function redactEmailDeterministic(value) {
9
+ const normalized = String(value ?? '').trim().toLowerCase();
10
+ if (!normalized) {
11
+ return `${EMAIL_TOKEN_PREFIX}unknown`;
12
+ }
13
+ const token = (0, crypto_1.createHash)('sha256').update(normalized).digest('hex').slice(0, EMAIL_TOKEN_LENGTH);
14
+ return `${EMAIL_TOKEN_PREFIX}${token}`;
15
+ }
16
+ exports.redactEmailDeterministic = redactEmailDeterministic;
17
+ function redactEmailsInText(value) {
18
+ if (!value) {
19
+ return value;
20
+ }
21
+ return value.replace(EMAIL_PATTERN, (match) => isAlreadyMaskedEmail(match) ? match : redactEmailDeterministic(match));
22
+ }
23
+ exports.redactEmailsInText = redactEmailsInText;
24
+ function collectEmailsInText(value) {
25
+ if (!value) {
26
+ return [];
27
+ }
28
+ const matches = value.match(EMAIL_PATTERN);
29
+ return Array.isArray(matches) ? matches : [];
30
+ }
31
+ exports.collectEmailsInText = collectEmailsInText;
32
+ function isAlreadyMaskedEmail(value) {
33
+ if (/^email_tok_[a-z0-9]+$/i.test(value)) {
34
+ return true;
35
+ }
36
+ const [local = '', domain = ''] = String(value).split('@');
37
+ return local.includes('...') || domain.includes('...');
38
+ }
@@ -0,0 +1,108 @@
1
+ export type RuntimePrivacyAction = 'KEEP_EXACT' | 'TOKENIZE' | 'SUMMARIZE' | 'DROP';
2
+ export type RuntimePrivacyScope = 'FIELD' | 'FUNCTION' | 'ROUTE' | 'SCHEMA' | 'QUERY' | 'PATTERN';
3
+ export type RuntimePrivacyTextAction = 'KEEP_EXACT' | 'TOKENIZE' | 'PARTIAL_MASK' | 'DROP';
4
+ export type RuntimePrivacyRawTextMode = 'SAFE_PARTIAL_MASK' | 'REGEX_ASSISTED_EXACT';
5
+ export type RuntimePrivacyRawTextHintAction = 'TOKENIZE' | 'DROP';
6
+ export type RuntimePrivacySurface = 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error' | 'request.headers' | 'request.body' | 'request.params' | 'request.query' | 'response.body' | 'trace.args' | 'trace.returnValue' | 'trace.error';
7
+ export type RuntimePrivacyConfig = {
8
+ enabled?: boolean;
9
+ /** Bypass privacy policy and masking entirely and store raw captured values. */
10
+ captureRaw?: boolean;
11
+ environment?: string;
12
+ apiBase?: string;
13
+ /** Timeout for runtime privacy policy fetches in milliseconds. */
14
+ fetchTimeoutMs?: number;
15
+ /** Maximum total time initRepro should wait for policy fetch before failing startup. */
16
+ startupMaxWaitMs?: number;
17
+ /** Delay between startup fetch retries in milliseconds. */
18
+ startupRetryDelayMs?: number;
19
+ policy?: Record<string, any> | null;
20
+ };
21
+ export type RuntimePrivacyTraceContext = {
22
+ fn?: string | null;
23
+ file?: string | null;
24
+ functionType?: string | null;
25
+ };
26
+ export type RuntimePrivacyDbContext = {
27
+ type?: string | null;
28
+ collection?: string | null;
29
+ op?: string | null;
30
+ };
31
+ export type RuntimePrivacyApplyContext = {
32
+ routeKey?: string | null;
33
+ trace?: RuntimePrivacyTraceContext | null;
34
+ db?: RuntimePrivacyDbContext | null;
35
+ appId?: string;
36
+ appSecret?: string;
37
+ };
38
+ type TargetMatcher = {
39
+ surfaces: RuntimePrivacySurface[];
40
+ path: string[] | null;
41
+ };
42
+ type NormalizedStatement = {
43
+ sid: string;
44
+ scope: RuntimePrivacyScope;
45
+ selector: string;
46
+ action: RuntimePrivacyAction;
47
+ priority: number;
48
+ pattern?: RegExp;
49
+ selectorTokens: string[];
50
+ selectorLeaves: string[];
51
+ targetMatchers: TargetMatcher[];
52
+ textPolicy?: RuntimePrivacyTextPolicy;
53
+ rawTextMode?: RuntimePrivacyRawTextMode;
54
+ rawTextHintNames?: string[];
55
+ descendantRawTextMode?: RuntimePrivacyRawTextMode;
56
+ };
57
+ type NormalizedRawTextHint = {
58
+ name: string;
59
+ action: RuntimePrivacyRawTextHintAction;
60
+ pattern: RegExp;
61
+ tokenLabel: string;
62
+ };
63
+ type RuntimePrivacyTextFragment = {
64
+ kind: 'literal';
65
+ text: string;
66
+ } | {
67
+ kind: 'slot';
68
+ label?: string;
69
+ action: RuntimePrivacyTextAction;
70
+ };
71
+ type RuntimePrivacyTextPolicy = {
72
+ mode: 'KNOWN_TEMPLATE';
73
+ fragments: RuntimePrivacyTextFragment[];
74
+ };
75
+ export type NormalizedRuntimePrivacyPolicy = {
76
+ environment?: string;
77
+ strength?: 'weak' | 'medium' | 'strict';
78
+ statements: NormalizedStatement[];
79
+ rawTextHints: NormalizedRawTextHint[];
80
+ };
81
+ export declare function clearRuntimePrivacyProvenance(): void;
82
+ export type RuntimePrivacyFetchResult = {
83
+ status: 'ok';
84
+ policy: NormalizedRuntimePrivacyPolicy;
85
+ } | {
86
+ status: 'disabled';
87
+ reason: 'missing_config' | 'missing_credentials' | 'no_policy' | 'empty_policy';
88
+ } | {
89
+ status: 'http_error';
90
+ httpStatus: number;
91
+ } | {
92
+ status: 'network_error';
93
+ error: string;
94
+ };
95
+ export declare function normalizePrivacyEnvironment(input?: string | null): string;
96
+ export declare function resolvePrivacyApiBase(cfg?: RuntimePrivacyConfig | null): string | null;
97
+ export declare function fetchApprovedRuntimePrivacyPolicy(params: {
98
+ appId: string;
99
+ appSecret?: string;
100
+ appName?: string;
101
+ apiBase: string;
102
+ environment: string;
103
+ fetchTimeoutMs?: number;
104
+ }): Promise<RuntimePrivacyFetchResult>;
105
+ export declare function normalizeRuntimePrivacyPolicy(raw: unknown): NormalizedRuntimePrivacyPolicy | null;
106
+ export declare function applyRuntimePrivacyPolicy(policy: NormalizedRuntimePrivacyPolicy | null, surface: RuntimePrivacySurface, value: any, context: RuntimePrivacyApplyContext): any;
107
+ export declare function applyRuntimePrivacyPolicyAsync(policy: NormalizedRuntimePrivacyPolicy | null, surface: RuntimePrivacySurface, value: any, context: RuntimePrivacyApplyContext): Promise<any>;
108
+ export {};