@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10
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/AUTHORING.md +240 -0
- package/CHANGELOG.md +93 -0
- package/README.md +26 -10
- package/SUBMISSION.md +11 -12
- package/bin/apifuse-check.ts +44 -59
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-check.ts +36 -0
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +53 -70
- package/bin/apifuse-submit-check.ts +2177 -353
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml-semantics.ts +204 -0
- package/bin/submit-check-xml.ts +134 -0
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +427 -0
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +14 -48
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +47 -33
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/config/loader.d.ts +149 -8
- package/dist/config/loader.js +378 -89
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +5 -6
- package/dist/define.d.ts +13 -1
- package/dist/define.js +245 -178
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +48 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +44 -41
- package/dist/index.js +39 -36
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +35 -15
- package/dist/provider.d.ts +11 -9
- package/dist/provider.js +9 -8
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +4 -2
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +214 -25
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +31 -35
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +15 -3
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +160 -344
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +56 -19
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +55 -52
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +15 -4
- package/dist/runtime/stealth.d.ts +7 -4
- package/dist/runtime/stealth.js +257 -215
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -3
- package/dist/server/index.js +6 -2
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +199 -0
- package/dist/server/self-test.js +1113 -0
- package/dist/server/serve.d.ts +14 -3
- package/dist/server/serve.js +135 -64
- package/dist/server/types.d.ts +10 -9
- package/dist/server/types.js +3 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +12 -15
- package/dist/types.d.ts +237 -1
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +16 -5
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +728 -0
- package/src/ceremonies/index.ts +33 -121
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +69 -99
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/config/loader.ts +543 -208
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +13 -28
- package/src/define.ts +397 -528
- package/src/dev.ts +4 -9
- package/src/errors.ts +58 -5
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +64 -41
- package/src/lint.ts +122 -159
- package/src/provider.ts +40 -9
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +6 -6
- package/src/runtime/browser.ts +320 -151
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +79 -132
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +23 -20
- package/src/runtime/http.ts +216 -539
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +78 -65
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/proxy-telemetry.ts +79 -77
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +41 -110
- package/src/runtime/stealth.ts +331 -369
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +44 -3
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +1450 -0
- package/src/server/serve.ts +206 -216
- package/src/server/types.ts +7 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +24 -64
- package/src/types.ts +274 -1
- package/src/user-input.ts +118 -0
package/dist/runtime/stt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProviderError, TransportError, ValidationError } from "../errors";
|
|
1
|
+
import { ProviderError, TransportError, ValidationError } from "../errors.js";
|
|
2
2
|
export const APIFUSE__STT__BACKEND_ENV = "APIFUSE__STT__BACKEND";
|
|
3
3
|
export const APIFUSE__STT__MODEL_ENV = "APIFUSE__STT__MODEL";
|
|
4
4
|
export const CLOUDFLARE_ACCOUNT_ID_ENV = "APIFUSE__CLOUDFLARE__ACCOUNT_ID";
|
|
@@ -59,17 +59,12 @@ export function createSttClientFromEnv(config, env = process.env) {
|
|
|
59
59
|
return createCloudflareWorkersAiSttClient({
|
|
60
60
|
accountId,
|
|
61
61
|
apiToken,
|
|
62
|
-
model: normalizedEnvValue(env, APIFUSE__STT__MODEL_ENV) ??
|
|
63
|
-
DEFAULT_CLOUDFLARE_WORKERS_AI_STT_MODEL,
|
|
62
|
+
model: normalizedEnvValue(env, APIFUSE__STT__MODEL_ENV) ?? DEFAULT_CLOUDFLARE_WORKERS_AI_STT_MODEL,
|
|
64
63
|
});
|
|
65
64
|
}
|
|
66
65
|
function base64ByteLength(data) {
|
|
67
66
|
const normalized = data.trim();
|
|
68
|
-
const padding = normalized.endsWith("==")
|
|
69
|
-
? 2
|
|
70
|
-
: normalized.endsWith("=")
|
|
71
|
-
? 1
|
|
72
|
-
: 0;
|
|
67
|
+
const padding = normalized.endsWith("==") ? 2 : normalized.endsWith("=") ? 1 : 0;
|
|
73
68
|
return Math.floor((normalized.length * 3) / 4) - padding;
|
|
74
69
|
}
|
|
75
70
|
function assertBase64Audio(audio, maxAudioBytes) {
|
|
@@ -80,9 +75,7 @@ function assertBase64Audio(audio, maxAudioBytes) {
|
|
|
80
75
|
});
|
|
81
76
|
}
|
|
82
77
|
const data = audio.data.trim();
|
|
83
|
-
if (data.length === 0 ||
|
|
84
|
-
data.length % 4 !== 0 ||
|
|
85
|
-
!BASE64_AUDIO_PATTERN.test(data)) {
|
|
78
|
+
if (data.length === 0 || data.length % 4 !== 0 || !BASE64_AUDIO_PATTERN.test(data)) {
|
|
86
79
|
throw new ValidationError("STT audio.data must be a base64-encoded string", {
|
|
87
80
|
code: "INVALID_STT_AUDIO",
|
|
88
81
|
});
|
|
@@ -90,7 +83,9 @@ function assertBase64Audio(audio, maxAudioBytes) {
|
|
|
90
83
|
const bytes = base64ByteLength(data);
|
|
91
84
|
const maxBytes = maxAudioBytes ?? DEFAULT_STT_MAX_AUDIO_BYTES;
|
|
92
85
|
if (bytes > maxBytes) {
|
|
93
|
-
throw new ValidationError(`STT audio exceeds maxAudioBytes (${bytes} > ${maxBytes})`, {
|
|
86
|
+
throw new ValidationError(`STT audio exceeds maxAudioBytes (${bytes} > ${maxBytes})`, {
|
|
87
|
+
code: "STT_AUDIO_TOO_LARGE",
|
|
88
|
+
});
|
|
94
89
|
}
|
|
95
90
|
return bytes;
|
|
96
91
|
}
|
|
@@ -227,8 +222,7 @@ export function createCloudflareWorkersAiSttClient(options) {
|
|
|
227
222
|
return {
|
|
228
223
|
async transcribe(request) {
|
|
229
224
|
const warnings = [];
|
|
230
|
-
if (request.initialPrompt &&
|
|
231
|
-
effectivePromptPolicy(request) !== "custom-hint") {
|
|
225
|
+
if (request.initialPrompt && effectivePromptPolicy(request) !== "custom-hint") {
|
|
232
226
|
const warning = warnOrThrowUnsupportedOption(request, "initialPrompt is honored only when promptPolicy is custom-hint");
|
|
233
227
|
if (warning)
|
|
234
228
|
warnings.push(warning);
|
|
@@ -350,7 +344,9 @@ function lengthSet(codeLengths) {
|
|
|
350
344
|
}
|
|
351
345
|
function validCodeLength(value) {
|
|
352
346
|
if (!Number.isInteger(value) || value < 1 || value > 32) {
|
|
353
|
-
throw new ValidationError("STT verification code length must be an integer between 1 and 32", {
|
|
347
|
+
throw new ValidationError("STT verification code length must be an integer between 1 and 32", {
|
|
348
|
+
code: "INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
349
|
+
});
|
|
354
350
|
}
|
|
355
351
|
return value;
|
|
356
352
|
}
|
package/dist/runtime/trace.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TraceContext as BaseTraceContext, TraceAttributeValue, TraceConfig, TraceSpan } from "../types";
|
|
2
|
-
import { type OTLPExportOptions } from "./otlp";
|
|
1
|
+
import type { TraceContext as BaseTraceContext, TraceAttributeValue, TraceConfig, TraceSpan } from "../types.js";
|
|
2
|
+
import { type OTLPExportOptions } from "./otlp.js";
|
|
3
3
|
export type SpanAttributeValue = TraceAttributeValue;
|
|
4
4
|
export type Span = TraceSpan;
|
|
5
5
|
export interface TraceContext extends BaseTraceContext {
|
package/dist/runtime/trace.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import { exportSpansOTLP } from "./otlp";
|
|
2
|
+
import { exportSpansOTLP } from "./otlp.js";
|
|
3
3
|
export const TRACE_RECORDER = Symbol.for("@apifuse/provider-sdk/runtime/trace-recorder");
|
|
4
4
|
function buildOTLPExportOptions(config) {
|
|
5
5
|
if (config?.exporter !== "otlp") {
|
|
@@ -23,9 +23,7 @@ export function resolveTraceContextOptions(config) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
function normalizeAttributeValue(value) {
|
|
26
|
-
if (typeof value === "string" ||
|
|
27
|
-
typeof value === "number" ||
|
|
28
|
-
typeof value === "boolean") {
|
|
26
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
29
27
|
return value;
|
|
30
28
|
}
|
|
31
29
|
if (value === null || value === undefined) {
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ZodString, type ZodType, z } from "zod";
|
|
2
|
-
import type { InferSchemaOutput, ProviderLocaleKey, SchemaLike } from "./types";
|
|
2
|
+
import type { InferSchemaOutput, ProviderLocaleKey, SchemaLike } from "./types.js";
|
|
3
3
|
export { z };
|
|
4
4
|
export type SchemaValidationResult<TSchema extends SchemaLike> = {
|
|
5
5
|
success: true;
|
package/dist/schema.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ValidationError } from "./errors";
|
|
3
|
-
import { providerLocaleKey } from "./i18n/keys";
|
|
2
|
+
import { ValidationError } from "./errors.js";
|
|
3
|
+
import { providerLocaleKey } from "./i18n/keys.js";
|
|
4
4
|
export { z };
|
|
5
5
|
function isFailureResult(result) {
|
|
6
6
|
return "issues" in result;
|
|
@@ -99,21 +99,15 @@ export function field(schema, options = {}) {
|
|
|
99
99
|
const metadata = described.meta() ?? {};
|
|
100
100
|
return described.meta({
|
|
101
101
|
...metadata,
|
|
102
|
-
...((options.sensitive ?? true)
|
|
103
|
-
|
|
104
|
-
: {}),
|
|
105
|
-
...(options.kind
|
|
106
|
-
? { [APIFUSE_SENSITIVE_KIND_META_KEY]: options.kind }
|
|
107
|
-
: {}),
|
|
102
|
+
...((options.sensitive ?? true) ? { [APIFUSE_SENSITIVE_META_KEY]: true } : {}),
|
|
103
|
+
...(options.kind ? { [APIFUSE_SENSITIVE_KIND_META_KEY]: options.kind } : {}),
|
|
108
104
|
});
|
|
109
105
|
}
|
|
110
106
|
export function sensitive(schema, kind) {
|
|
111
107
|
return field(schema, { sensitive: true, kind });
|
|
112
108
|
}
|
|
113
109
|
function sensitiveString(kind, description, options = {}) {
|
|
114
|
-
const schema = options.minLength === undefined
|
|
115
|
-
? z.string()
|
|
116
|
-
: z.string().min(options.minLength);
|
|
110
|
+
const schema = options.minLength === undefined ? z.string() : z.string().min(options.minLength);
|
|
117
111
|
return field(schema, {
|
|
118
112
|
kind,
|
|
119
113
|
description: options.description ?? description,
|
|
@@ -132,8 +126,7 @@ export const fields = {
|
|
|
132
126
|
};
|
|
133
127
|
export function isSensitiveSchema(schema) {
|
|
134
128
|
const metadata = readZodMetadata(schema);
|
|
135
|
-
return
|
|
136
|
-
Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true);
|
|
129
|
+
return metadata !== undefined && Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true;
|
|
137
130
|
}
|
|
138
131
|
export function collectSensitivePaths(schema) {
|
|
139
132
|
const out = [];
|
|
@@ -217,8 +210,7 @@ function pathMatches(path, patterns) {
|
|
|
217
210
|
}
|
|
218
211
|
function isReservedSensitiveKey(key) {
|
|
219
212
|
const normalized = key.toLowerCase().replace(/[-_\s]/g, "");
|
|
220
|
-
return
|
|
221
|
-
RESERVED_SENSITIVE_KEYS.has(key.toLowerCase()));
|
|
213
|
+
return RESERVED_SENSITIVE_KEYS.has(normalized) || RESERVED_SENSITIVE_KEYS.has(key.toLowerCase());
|
|
222
214
|
}
|
|
223
215
|
function readZodMetadata(schema) {
|
|
224
216
|
if (!schema || typeof schema !== "object" || !("meta" in schema)) {
|
package/dist/serve.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createServerApp, type ServeOptions, serve } from "./server/serve";
|
|
1
|
+
export { createServerApp, type ServeOptions, serve } from "./server/serve.js";
|
package/dist/serve.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createServerApp, serve } from "./server/serve";
|
|
1
|
+
export { createServerApp, serve } from "./server/serve.js";
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
export { createServerApp, type ServeOptions, serve } from "./serve";
|
|
2
|
-
export
|
|
3
|
-
export {
|
|
1
|
+
export { createServerApp, type ServeOptions, serve } from "./serve.js";
|
|
2
|
+
export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, type SelfTestAppOptions, type SelfTestAuthFlowInvoke, type SelfTestAuthFlowRoute, type SelfTestCaseResult, type SelfTestCaseStatus, type SelfTestOperationInvoke, type SelfTestRequest, SelfTestRequestSchema, type SelfTestResponse, } from "./self-test.js";
|
|
3
|
+
export { resolveHealthCheckInputDateTokens } from "./self-test-input-tokens.js";
|
|
4
|
+
export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_LENGTH, SELF_TEST_REDACTED_PLACEHOLDER, } from "./self-test-redaction.js";
|
|
5
|
+
export { DEFAULT_SELF_TEST_PORT, deriveSelfTestToken, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV, PROVIDER_RUNTIME_SELF_TEST_PORT_ENV, resolveSelfTestMasterSecrets, type SelfTestMasterSecrets, verifySelfTestAuthorization, } from "./self-test-token.js";
|
|
6
|
+
export type { AuthFlowRequest, AuthFlowResponse, AuthFlowSuccessResponse, ConnectionMode, OperationConnection, OperationErrorResponse, OperationRequest, OperationResponse, OperationSuccessResponse, } from "./types.js";
|
|
7
|
+
export { AuthFlowRequestSchema, AuthFlowSuccessResponseSchema, ConnectionModeSchema, ErrorEnvelopeSchema, OperationConnectionSchema, OperationErrorResponseSchema, OperationRequestSchema, OperationSuccessResponseSchema, } from "./types.js";
|
package/dist/server/index.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
export { createServerApp, serve } from "./serve";
|
|
2
|
-
export {
|
|
1
|
+
export { createServerApp, serve } from "./serve.js";
|
|
2
|
+
export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, SelfTestRequestSchema, } from "./self-test.js";
|
|
3
|
+
export { resolveHealthCheckInputDateTokens } from "./self-test-input-tokens.js";
|
|
4
|
+
export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_LENGTH, SELF_TEST_REDACTED_PLACEHOLDER, } from "./self-test-redaction.js";
|
|
5
|
+
export { DEFAULT_SELF_TEST_PORT, deriveSelfTestToken, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV, PROVIDER_RUNTIME_SELF_TEST_PORT_ENV, resolveSelfTestMasterSecrets, verifySelfTestAuthorization, } from "./self-test-token.js";
|
|
6
|
+
export { AuthFlowRequestSchema, AuthFlowSuccessResponseSchema, ConnectionModeSchema, ErrorEnvelopeSchema, OperationConnectionSchema, OperationErrorResponseSchema, OperationRequestSchema, OperationSuccessResponseSchema, } from "./types.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveHealthCheckInputDateTokens(value: unknown, now?: Date): unknown;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relative-KST date token resolution for health-check case inputs, ported from
|
|
3
|
+
* the monorepo health-monitor (`apps/health-monitor/src/lib/health-check-input.ts`)
|
|
4
|
+
* so provider self-tests resolve durable probe inputs identically.
|
|
5
|
+
*
|
|
6
|
+
* Supported token: `+<days>d` or `+<days>d:YYYYMMDD` (1..365 days ahead, KST).
|
|
7
|
+
*/
|
|
8
|
+
const RELATIVE_KST_DATE_TOKEN = /^\+(\d{1,3})d(?::(YYYYMMDD))?$/i;
|
|
9
|
+
function dateFromKstDaysAhead(daysAhead, now = new Date(), format = "YYYY-MM-DD") {
|
|
10
|
+
const kstNow = new Date(now.getTime() + 9 * 60 * 60 * 1000);
|
|
11
|
+
const date = new Date(Date.UTC(kstNow.getUTCFullYear(), kstNow.getUTCMonth(), kstNow.getUTCDate() + daysAhead));
|
|
12
|
+
const isoDate = date.toISOString().slice(0, 10);
|
|
13
|
+
return format === "YYYYMMDD" ? isoDate.replace(/-/g, "") : isoDate;
|
|
14
|
+
}
|
|
15
|
+
export function resolveHealthCheckInputDateTokens(value, now = new Date()) {
|
|
16
|
+
if (typeof value === "string") {
|
|
17
|
+
const relative = value.match(RELATIVE_KST_DATE_TOKEN);
|
|
18
|
+
if (!relative)
|
|
19
|
+
return value;
|
|
20
|
+
const daysAhead = Number(relative[1]);
|
|
21
|
+
if (!Number.isInteger(daysAhead) || daysAhead < 1 || daysAhead > 365) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
const format = relative[2]?.toUpperCase() === "YYYYMMDD" ? "YYYYMMDD" : "YYYY-MM-DD";
|
|
25
|
+
return dateFromKstDaysAhead(daysAhead, now, format);
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map((entry) => resolveHealthCheckInputDateTokens(entry, now));
|
|
29
|
+
}
|
|
30
|
+
if (value && typeof value === "object") {
|
|
31
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
32
|
+
key,
|
|
33
|
+
resolveHealthCheckInputDateTokens(entry, now),
|
|
34
|
+
]));
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ProviderDefinition } from "../types.js";
|
|
2
|
+
export declare const SELF_TEST_REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
3
|
+
/** Maximum length of any string echoed in a self-test response. */
|
|
4
|
+
export declare const SELF_TEST_MAX_TEXT_LENGTH = 300;
|
|
5
|
+
/**
|
|
6
|
+
* Collects every secret-shaped value known to the self-test runtime so probe
|
|
7
|
+
* results can be scrubbed before they leave the pod: master secrets, declared
|
|
8
|
+
* provider env secrets, health-probe credential env values, and request-supplied
|
|
9
|
+
* credential inputs.
|
|
10
|
+
*/
|
|
11
|
+
export declare function collectSelfTestSensitiveValues(provider: ProviderDefinition, options?: {
|
|
12
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
13
|
+
credentialInputs?: Readonly<Record<string, string>>;
|
|
14
|
+
}): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Scrubs known secret values and header-shaped credentials from a string and
|
|
17
|
+
* caps its length. Every string embedded in a SelfTestResponse MUST pass
|
|
18
|
+
* through this before serialization.
|
|
19
|
+
*/
|
|
20
|
+
export declare function redactSelfTestText(text: string, sensitiveValues: readonly string[], maxLength?: number): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV, } from "./self-test-token.js";
|
|
2
|
+
export const SELF_TEST_REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
3
|
+
/** Maximum length of any string echoed in a self-test response. */
|
|
4
|
+
export const SELF_TEST_MAX_TEXT_LENGTH = 300;
|
|
5
|
+
/** Values shorter than this are too generic to be treated as secrets. */
|
|
6
|
+
const MIN_SENSITIVE_VALUE_LENGTH = 4;
|
|
7
|
+
const HEADER_SHAPED_SECRETS = [
|
|
8
|
+
/(authorization\s*[:=]\s*)[^\s;,]+/gi,
|
|
9
|
+
/(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi,
|
|
10
|
+
/((?:set-)?cookie\s*[:=]\s*)[^;\n]+/gi,
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Collects every secret-shaped value known to the self-test runtime so probe
|
|
14
|
+
* results can be scrubbed before they leave the pod: master secrets, declared
|
|
15
|
+
* provider env secrets, health-probe credential env values, and request-supplied
|
|
16
|
+
* credential inputs.
|
|
17
|
+
*/
|
|
18
|
+
export function collectSelfTestSensitiveValues(provider, options = {}) {
|
|
19
|
+
const env = options.env ?? process.env;
|
|
20
|
+
const envNames = new Set([
|
|
21
|
+
PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV,
|
|
22
|
+
PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV,
|
|
23
|
+
]);
|
|
24
|
+
for (const secret of provider.secrets ?? []) {
|
|
25
|
+
envNames.add(secret.name);
|
|
26
|
+
}
|
|
27
|
+
const healthProbe = provider.healthProbe ?? provider.healthMonitor;
|
|
28
|
+
for (const name of healthProbe?.requiredSecrets ?? []) {
|
|
29
|
+
envNames.add(name);
|
|
30
|
+
}
|
|
31
|
+
for (const name of Object.values(healthProbe?.credentialInputs ?? {})) {
|
|
32
|
+
envNames.add(name);
|
|
33
|
+
}
|
|
34
|
+
const values = new Set();
|
|
35
|
+
for (const name of envNames) {
|
|
36
|
+
const value = env[name];
|
|
37
|
+
if (typeof value === "string" && value.length >= MIN_SENSITIVE_VALUE_LENGTH) {
|
|
38
|
+
values.add(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const value of Object.values(options.credentialInputs ?? {})) {
|
|
42
|
+
if (value.length >= MIN_SENSITIVE_VALUE_LENGTH) {
|
|
43
|
+
values.add(value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...values];
|
|
47
|
+
}
|
|
48
|
+
function escapeRegExp(value) {
|
|
49
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Scrubs known secret values and header-shaped credentials from a string and
|
|
53
|
+
* caps its length. Every string embedded in a SelfTestResponse MUST pass
|
|
54
|
+
* through this before serialization.
|
|
55
|
+
*/
|
|
56
|
+
export function redactSelfTestText(text, sensitiveValues, maxLength = SELF_TEST_MAX_TEXT_LENGTH) {
|
|
57
|
+
let redacted = text;
|
|
58
|
+
for (const value of sensitiveValues) {
|
|
59
|
+
if (value.length < MIN_SENSITIVE_VALUE_LENGTH)
|
|
60
|
+
continue;
|
|
61
|
+
redacted = redacted.replace(new RegExp(escapeRegExp(value), "g"), SELF_TEST_REDACTED_PLACEHOLDER);
|
|
62
|
+
}
|
|
63
|
+
for (const pattern of HEADER_SHAPED_SECRETS) {
|
|
64
|
+
redacted = redacted.replace(pattern, (_match, prefix) => `${prefix}${SELF_TEST_REDACTED_PLACEHOLDER}`);
|
|
65
|
+
}
|
|
66
|
+
if (redacted.length > maxLength) {
|
|
67
|
+
redacted = `${redacted.slice(0, maxLength)}… [truncated]`;
|
|
68
|
+
}
|
|
69
|
+
return redacted;
|
|
70
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared master secret injected into every provider pod. The per-provider
|
|
3
|
+
* bearer token is derived from it, so compromise of one pod's env cannot
|
|
4
|
+
* unlock another provider's self-test endpoint.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV = "APIFUSE__PROVIDER_RUNTIME__SELF_TEST_MASTER_SECRET";
|
|
7
|
+
/**
|
|
8
|
+
* Previous master secret accepted during a rotation window so fleet-wide
|
|
9
|
+
* rotation does not need to be atomic.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV = "APIFUSE__PROVIDER_RUNTIME__SELF_TEST_MASTER_SECRET_PREVIOUS";
|
|
12
|
+
/** Internal self-test listener port (never the tenant-facing serve() port). */
|
|
13
|
+
export declare const PROVIDER_RUNTIME_SELF_TEST_PORT_ENV = "APIFUSE__PROVIDER_RUNTIME__SELF_TEST_PORT";
|
|
14
|
+
export declare const DEFAULT_SELF_TEST_PORT = 3001;
|
|
15
|
+
export interface SelfTestMasterSecrets {
|
|
16
|
+
readonly current: string;
|
|
17
|
+
readonly previous?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Per-provider self-test bearer token: HMAC-SHA256(masterSecret, providerId),
|
|
21
|
+
* hex-encoded. Computed identically by the provider pod and the scheduler.
|
|
22
|
+
*/
|
|
23
|
+
export declare function deriveSelfTestToken(masterSecret: string, providerId: string): string;
|
|
24
|
+
export declare function resolveSelfTestMasterSecrets(env?: Readonly<Record<string, string | undefined>>): SelfTestMasterSecrets | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Verifies an Authorization header against the current-or-previous derived
|
|
27
|
+
* token (dual acceptance for one rotation window). Constant-time comparison;
|
|
28
|
+
* returns only a boolean so callers cannot leak which check failed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function verifySelfTestAuthorization(authorizationHeader: string | undefined, providerId: string, secrets: SelfTestMasterSecrets): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Shared master secret injected into every provider pod. The per-provider
|
|
4
|
+
* bearer token is derived from it, so compromise of one pod's env cannot
|
|
5
|
+
* unlock another provider's self-test endpoint.
|
|
6
|
+
*/
|
|
7
|
+
export const PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV = "APIFUSE__PROVIDER_RUNTIME__SELF_TEST_MASTER_SECRET";
|
|
8
|
+
/**
|
|
9
|
+
* Previous master secret accepted during a rotation window so fleet-wide
|
|
10
|
+
* rotation does not need to be atomic.
|
|
11
|
+
*/
|
|
12
|
+
export const PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV = "APIFUSE__PROVIDER_RUNTIME__SELF_TEST_MASTER_SECRET_PREVIOUS";
|
|
13
|
+
/** Internal self-test listener port (never the tenant-facing serve() port). */
|
|
14
|
+
export const PROVIDER_RUNTIME_SELF_TEST_PORT_ENV = "APIFUSE__PROVIDER_RUNTIME__SELF_TEST_PORT";
|
|
15
|
+
export const DEFAULT_SELF_TEST_PORT = 3001;
|
|
16
|
+
/**
|
|
17
|
+
* Per-provider self-test bearer token: HMAC-SHA256(masterSecret, providerId),
|
|
18
|
+
* hex-encoded. Computed identically by the provider pod and the scheduler.
|
|
19
|
+
*/
|
|
20
|
+
export function deriveSelfTestToken(masterSecret, providerId) {
|
|
21
|
+
return createHmac("sha256", masterSecret).update(providerId).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
export function resolveSelfTestMasterSecrets(env = process.env) {
|
|
24
|
+
const current = env[PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV]?.trim();
|
|
25
|
+
if (!current)
|
|
26
|
+
return undefined;
|
|
27
|
+
const previous = env[PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV]?.trim();
|
|
28
|
+
return previous ? { current, previous } : { current };
|
|
29
|
+
}
|
|
30
|
+
function digestsMatch(presented, expected) {
|
|
31
|
+
// Hash both sides before comparison so timingSafeEqual sees equal-length
|
|
32
|
+
// buffers regardless of what the caller presented (constant-time path).
|
|
33
|
+
const presentedDigest = createHash("sha256").update(presented).digest();
|
|
34
|
+
const expectedDigest = createHash("sha256").update(expected).digest();
|
|
35
|
+
return timingSafeEqual(presentedDigest, expectedDigest);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Verifies an Authorization header against the current-or-previous derived
|
|
39
|
+
* token (dual acceptance for one rotation window). Constant-time comparison;
|
|
40
|
+
* returns only a boolean so callers cannot leak which check failed.
|
|
41
|
+
*/
|
|
42
|
+
export function verifySelfTestAuthorization(authorizationHeader, providerId, secrets) {
|
|
43
|
+
const match = /^Bearer\s+(\S+)$/i.exec(authorizationHeader ?? "");
|
|
44
|
+
const presented = match?.[1] ?? "";
|
|
45
|
+
const currentOk = digestsMatch(presented, deriveSelfTestToken(secrets.current, providerId));
|
|
46
|
+
const previousOk = secrets.previous
|
|
47
|
+
? digestsMatch(presented, deriveSelfTestToken(secrets.previous, providerId))
|
|
48
|
+
: false;
|
|
49
|
+
return currentOk || previousOk;
|
|
50
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { OperationDefinition, ProviderDefinition } from "../types.js";
|
|
4
|
+
import { type SelfTestMasterSecrets } from "./self-test-token.js";
|
|
5
|
+
import type { OperationConnection } from "./types.js";
|
|
6
|
+
export declare const SELF_TEST_SCHEMA_VERSION: 1;
|
|
7
|
+
export declare const SELF_TEST_PATH = "/internal/health/self-test";
|
|
8
|
+
export declare const SELF_TEST_HEALTHZ_PATH = "/healthz";
|
|
9
|
+
/** Overall wall-clock budget for one self-test request (all selected cases). */
|
|
10
|
+
export declare const PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV = "APIFUSE__PROVIDER_RUNTIME__SELF_TEST_REQUEST_BUDGET_MS";
|
|
11
|
+
export declare const DEFAULT_SELF_TEST_REQUEST_BUDGET_MS = 120000;
|
|
12
|
+
export declare const SelfTestRequestSchema: z.ZodObject<{
|
|
13
|
+
schemaVersion: z.ZodNumber;
|
|
14
|
+
requestId: z.ZodString;
|
|
15
|
+
operationId: z.ZodOptional<z.ZodString>;
|
|
16
|
+
caseName: z.ZodOptional<z.ZodString>;
|
|
17
|
+
operations: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
18
|
+
caseNames: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
19
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
credentials: z.ZodOptional<z.ZodObject<{
|
|
21
|
+
inputs: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export type SelfTestRequest = z.infer<typeof SelfTestRequestSchema>;
|
|
25
|
+
export type SelfTestCaseStatus = "ok" | "degraded" | "failed" | "error" | "skipped";
|
|
26
|
+
export interface SelfTestCaseResult {
|
|
27
|
+
operationId: string;
|
|
28
|
+
caseName: string;
|
|
29
|
+
status: SelfTestCaseStatus;
|
|
30
|
+
label: string;
|
|
31
|
+
responseTimeMs: number;
|
|
32
|
+
httpStatus?: number;
|
|
33
|
+
assertion?: {
|
|
34
|
+
passed: boolean;
|
|
35
|
+
message?: string;
|
|
36
|
+
};
|
|
37
|
+
skipReason?: string;
|
|
38
|
+
error?: {
|
|
39
|
+
code: string;
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
startedAt: string;
|
|
43
|
+
finishedAt: string;
|
|
44
|
+
}
|
|
45
|
+
export interface SelfTestResponse {
|
|
46
|
+
schemaVersion: typeof SELF_TEST_SCHEMA_VERSION;
|
|
47
|
+
providerId: string;
|
|
48
|
+
sdkVersion: string;
|
|
49
|
+
planDigest: string;
|
|
50
|
+
/** Present for single-case requests (mirrors results[0]). */
|
|
51
|
+
result?: SelfTestCaseResult;
|
|
52
|
+
results: SelfTestCaseResult[];
|
|
53
|
+
}
|
|
54
|
+
export type SelfTestOperationInvoke = (args: {
|
|
55
|
+
operationId: string;
|
|
56
|
+
input: unknown;
|
|
57
|
+
connection?: OperationConnection;
|
|
58
|
+
requestId: string;
|
|
59
|
+
}) => Promise<{
|
|
60
|
+
status: number;
|
|
61
|
+
data: unknown;
|
|
62
|
+
meta?: Record<string, unknown>;
|
|
63
|
+
}>;
|
|
64
|
+
export type SelfTestAuthFlowRoute = "start" | "continue";
|
|
65
|
+
/**
|
|
66
|
+
* In-process driver for the tenant app's /auth pipeline. Self-test uses it to
|
|
67
|
+
* materialize `requiresConnection` credentials through the provider's declared
|
|
68
|
+
* auth flow — the exact path production connections take — instead of
|
|
69
|
+
* injecting raw credential inputs as connection secrets.
|
|
70
|
+
*/
|
|
71
|
+
export type SelfTestAuthFlowInvoke = (args: {
|
|
72
|
+
route: SelfTestAuthFlowRoute;
|
|
73
|
+
requestId: string;
|
|
74
|
+
flowId: string;
|
|
75
|
+
/** Stable per-credential connection id — keeps login on the probe's affinity. */
|
|
76
|
+
connectionId?: string;
|
|
77
|
+
/** The probe connection's externalRef — flows reading ctx.externalRef see the same identity. */
|
|
78
|
+
externalRef?: string;
|
|
79
|
+
input?: Record<string, unknown>;
|
|
80
|
+
context?: Record<string, unknown>;
|
|
81
|
+
}) => Promise<{
|
|
82
|
+
status: number;
|
|
83
|
+
body: unknown;
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* Skip reason reported when a declared auth flow does not complete in a single
|
|
87
|
+
* continue (OTP, retry loop). Cross-repo contract: the health-monitor maps
|
|
88
|
+
* this exact string to `self_test_incapable`; never vary it.
|
|
89
|
+
*/
|
|
90
|
+
export declare const SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON = "auth_flow_multi_turn";
|
|
91
|
+
/**
|
|
92
|
+
* A `retry` turn after credential submission: the flow REJECTED the
|
|
93
|
+
* configured inputs (bad password, exchange failure). Distinct from the
|
|
94
|
+
* multi-turn gap so monitoring surfaces it as a real credential outage, and
|
|
95
|
+
* memoized like multi-turn so the probe does not re-submit rejected
|
|
96
|
+
* credentials every cycle (lockout safety).
|
|
97
|
+
*/
|
|
98
|
+
export declare const SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON = "auth_flow_rejected";
|
|
99
|
+
export interface SelfTestAppOptions {
|
|
100
|
+
/** Derived-token verification secrets; without them every self-test route 404s. */
|
|
101
|
+
secrets?: SelfTestMasterSecrets;
|
|
102
|
+
/** In-process invoke bound to the tenant-facing app's /v1 pipeline. */
|
|
103
|
+
invoke: SelfTestOperationInvoke;
|
|
104
|
+
/**
|
|
105
|
+
* In-process auth-flow driver bound to the tenant-facing app's /auth
|
|
106
|
+
* pipeline. Required for providers that declare `auth.mode: "credentials"`
|
|
107
|
+
* with a flow; without it their requiresConnection cases report a visible
|
|
108
|
+
* auth_flow_unavailable error instead of probing with raw inputs.
|
|
109
|
+
*/
|
|
110
|
+
authFlow?: SelfTestAuthFlowInvoke;
|
|
111
|
+
/** Overall request budget; defaults to env / 120s. */
|
|
112
|
+
requestBudgetMs?: number;
|
|
113
|
+
/** Env override for secret collection + budget resolution (tests). */
|
|
114
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Stable sha256 over the provider's declared health plan (operations, case
|
|
118
|
+
* names, timeouts) so the scheduler can detect plan/image skew by comparing
|
|
119
|
+
* the manifest planDigest against the pod-reported one.
|
|
120
|
+
*/
|
|
121
|
+
export declare function computeSelfTestPlanDigest(provider: ProviderDefinition): string;
|
|
122
|
+
/**
|
|
123
|
+
* Fail-closed read-only classification: an operation may self-test only when
|
|
124
|
+
* its own metadata marks it read-only (annotations.readOnly or
|
|
125
|
+
* toolRouter.riskClass "read") and nothing marks it side-effecting. Unclassified
|
|
126
|
+
* operations are refused so the endpoint cannot become a mutation oracle.
|
|
127
|
+
*/
|
|
128
|
+
export declare function isSelfTestReadOnlyOperation(operation: OperationDefinition): boolean;
|
|
129
|
+
/** Binds the self-test executor to a tenant app's /v1 pipeline in-process. */
|
|
130
|
+
export declare function createSelfTestInvoke(app: {
|
|
131
|
+
request: (input: string, requestInit?: RequestInit) => Response | Promise<Response>;
|
|
132
|
+
}): SelfTestOperationInvoke;
|
|
133
|
+
/** Binds the self-test auth-flow driver to a tenant app's /auth pipeline in-process. */
|
|
134
|
+
export declare function createSelfTestAuthFlowInvoke(app: {
|
|
135
|
+
request: (input: string, requestInit?: RequestInit) => Response | Promise<Response>;
|
|
136
|
+
}): SelfTestAuthFlowInvoke;
|
|
137
|
+
/**
|
|
138
|
+
* How long a memoized multi-turn flow outcome suppresses re-driving the auth
|
|
139
|
+
* flow. Generous on purpose: a multi-turn ceremony (OTP, device approval) is a
|
|
140
|
+
* provider property that changes on the timescale of releases, not probe
|
|
141
|
+
* cycles, and every re-drive is a REAL upstream login submission. The cache is
|
|
142
|
+
* in-process, so a pod restart also clears the entry.
|
|
143
|
+
*/
|
|
144
|
+
export declare const SELF_TEST_MULTI_TURN_RETRY_AFTER_MS: number;
|
|
145
|
+
/**
|
|
146
|
+
* Age bound for POSITIVE cached credentials. Expiry modes that never produce
|
|
147
|
+
* a 401/403 (a 200 login page, an assertion failure) would otherwise replay
|
|
148
|
+
* the same stale session until pod restart — one re-login per day is the
|
|
149
|
+
* upstream-safe recovery for them.
|
|
150
|
+
*/
|
|
151
|
+
export declare const SELF_TEST_CREDENTIAL_MAX_AGE_MS: number;
|
|
152
|
+
export type SelfTestCredentialSessionEntry =
|
|
153
|
+
/** Flow-materialized credential reused across probe cycles. */
|
|
154
|
+
{
|
|
155
|
+
kind: "credential";
|
|
156
|
+
credential: Record<string, string>;
|
|
157
|
+
cachedAtMs: number;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Negative entry: the flow did not complete in a single continue turn
|
|
161
|
+
* (`auth_flow_multi_turn`). Memoized so subsequent cycles report the skip
|
|
162
|
+
* WITHOUT contacting the upstream again — the first attempt already
|
|
163
|
+
* submitted real credentials (and may have triggered an OTP send).
|
|
164
|
+
*/
|
|
165
|
+
| {
|
|
166
|
+
kind: "multi_turn";
|
|
167
|
+
cachedAtMs: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Negative entry: the flow REJECTED the submitted credential inputs
|
|
171
|
+
* (`retry` turn after continue — bad password, exchange failure).
|
|
172
|
+
* Memoized so the probe does not re-submit rejected credentials every
|
|
173
|
+
* cycle; a new entry is attempted when the inputs rotate (new hash),
|
|
174
|
+
* the TTL lapses, or the process restarts.
|
|
175
|
+
*/
|
|
176
|
+
| {
|
|
177
|
+
kind: "rejected";
|
|
178
|
+
cachedAtMs: number;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* In-process cache of per-(providerId + stable hash of credentialInputs) auth
|
|
182
|
+
* flow outcomes, so consecutive probe cycles reuse the session — or the
|
|
183
|
+
* memoized multi-turn skip — instead of logging in every cycle (upstream
|
|
184
|
+
* account safety, DR-7). Credential entries are invalidated on a probe auth
|
|
185
|
+
* failure, at most once; multi-turn entries expire after
|
|
186
|
+
* `SELF_TEST_MULTI_TURN_RETRY_AFTER_MS` or on process restart. Flow ERRORS
|
|
187
|
+
* (transport/protocol failures, thrown start/continue) are deliberately NEVER
|
|
188
|
+
* cached: they are typically transient, and retrying a failed request next
|
|
189
|
+
* cycle is not a repeated login submission.
|
|
190
|
+
*/
|
|
191
|
+
export type SelfTestCredentialSessionCache = Map<string, SelfTestCredentialSessionEntry>;
|
|
192
|
+
export declare function resolveSelfTestPort(env?: Readonly<Record<string, string | undefined>>): number;
|
|
193
|
+
/**
|
|
194
|
+
* Builds the internal self-test Hono app. This app is served on a SEPARATE
|
|
195
|
+
* listener (default :3001) that the tenant-facing gateway never dials; when no
|
|
196
|
+
* master secret is resolvable the self-test route responds 404 (safe default
|
|
197
|
+
* off) while `GET /healthz` stays available for liveness.
|
|
198
|
+
*/
|
|
199
|
+
export declare function createSelfTestApp(provider: ProviderDefinition, options: SelfTestAppOptions): Hono;
|