@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.2

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 (138) hide show
  1. package/AUTHORING.md +134 -0
  2. package/CHANGELOG.md +61 -0
  3. package/README.md +21 -9
  4. package/SUBMISSION.md +10 -11
  5. package/bin/apifuse-pack-check.ts +22 -0
  6. package/bin/apifuse-submit-check.ts +1747 -388
  7. package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
  8. package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  9. package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  10. package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  11. package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  12. package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  13. package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  14. package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  15. package/dist/auth-turn/fixtures/valid/abort.json +8 -0
  16. package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
  17. package/dist/auth-turn/fixtures/valid/complete.json +13 -0
  18. package/dist/auth-turn/fixtures/valid/form.json +14 -0
  19. package/dist/auth-turn/fixtures/valid/message.json +13 -0
  20. package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
  21. package/dist/auth-turn/fixtures/valid/pending.json +5 -0
  22. package/dist/auth-turn/fixtures/valid/poll.json +9 -0
  23. package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
  24. package/dist/auth-turn/fixtures/valid/retry.json +8 -0
  25. package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  26. package/dist/auth-turn/index.d.ts +195 -0
  27. package/dist/auth-turn/index.js +133 -0
  28. package/dist/auth.d.ts +76 -0
  29. package/dist/auth.js +436 -0
  30. package/dist/ceremonies/index.js +7 -31
  31. package/dist/cli/create.js +45 -30
  32. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  33. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  34. package/dist/cli/templates/provider/AGENTS.md.tpl +87 -0
  35. package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -0
  36. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  37. package/dist/cli/templates/provider/README.md.tpl +163 -0
  38. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  39. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  40. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  41. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  42. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  43. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  44. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  45. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  46. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  47. package/dist/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  48. package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  49. package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  50. package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  51. package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  52. package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  53. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  54. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  55. package/dist/contract.js +1 -0
  56. package/dist/define.d.ts +6 -1
  57. package/dist/define.js +140 -70
  58. package/dist/index.d.ts +3 -2
  59. package/dist/index.js +2 -1
  60. package/dist/lint.d.ts +1 -0
  61. package/dist/lint.js +27 -0
  62. package/dist/provider.d.ts +4 -2
  63. package/dist/provider.js +2 -1
  64. package/dist/runtime/auth-flow.js +2 -0
  65. package/dist/runtime/browser.js +203 -0
  66. package/dist/runtime/http.js +46 -336
  67. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  68. package/dist/runtime/proxy-retry-policy.js +326 -0
  69. package/dist/runtime/stealth.d.ts +5 -2
  70. package/dist/runtime/stealth.js +206 -206
  71. package/dist/server/index.d.ts +4 -0
  72. package/dist/server/index.js +4 -0
  73. package/dist/server/self-test-input-tokens.d.ts +1 -0
  74. package/dist/server/self-test-input-tokens.js +37 -0
  75. package/dist/server/self-test-redaction.d.ts +20 -0
  76. package/dist/server/self-test-redaction.js +70 -0
  77. package/dist/server/self-test-token.d.ts +30 -0
  78. package/dist/server/self-test-token.js +50 -0
  79. package/dist/server/self-test.d.ts +98 -0
  80. package/dist/server/self-test.js +555 -0
  81. package/dist/server/serve.d.ts +6 -0
  82. package/dist/server/serve.js +38 -10
  83. package/dist/server/types.d.ts +1 -0
  84. package/dist/server/types.js +1 -0
  85. package/dist/testing/run.js +5 -1
  86. package/dist/types.d.ts +150 -0
  87. package/package.json +9 -2
  88. package/src/auth-turn/auth-turn.v1.schema.json +89 -0
  89. package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  90. package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  91. package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  92. package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  93. package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  94. package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  95. package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  96. package/src/auth-turn/fixtures/valid/abort.json +8 -0
  97. package/src/auth-turn/fixtures/valid/challenge.json +17 -0
  98. package/src/auth-turn/fixtures/valid/complete.json +13 -0
  99. package/src/auth-turn/fixtures/valid/form.json +14 -0
  100. package/src/auth-turn/fixtures/valid/message.json +13 -0
  101. package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
  102. package/src/auth-turn/fixtures/valid/pending.json +5 -0
  103. package/src/auth-turn/fixtures/valid/poll.json +9 -0
  104. package/src/auth-turn/fixtures/valid/redirect.json +16 -0
  105. package/src/auth-turn/fixtures/valid/retry.json +8 -0
  106. package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  107. package/src/auth-turn/index.ts +177 -0
  108. package/src/auth.ts +786 -0
  109. package/src/ceremonies/index.ts +9 -43
  110. package/src/cli/create.ts +60 -97
  111. package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
  112. package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
  113. package/src/cli/templates/provider/README.md.tpl +7 -4
  114. package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  115. package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  116. package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  117. package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  118. package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  119. package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  120. package/src/contract.ts +1 -0
  121. package/src/define.ts +198 -71
  122. package/src/index.ts +16 -0
  123. package/src/lint.ts +33 -0
  124. package/src/provider.ts +27 -0
  125. package/src/runtime/auth-flow.ts +2 -0
  126. package/src/runtime/browser.ts +293 -1
  127. package/src/runtime/http.ts +84 -530
  128. package/src/runtime/proxy-retry-policy.ts +469 -0
  129. package/src/runtime/stealth.ts +269 -353
  130. package/src/server/index.ts +36 -0
  131. package/src/server/self-test-input-tokens.ts +46 -0
  132. package/src/server/self-test-redaction.ts +97 -0
  133. package/src/server/self-test-token.ts +70 -0
  134. package/src/server/self-test.ts +725 -0
  135. package/src/server/serve.ts +75 -6
  136. package/src/server/types.ts +1 -0
  137. package/src/testing/run.ts +9 -1
  138. package/src/types.ts +186 -0
@@ -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";
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,98 @@
1
+ import { Hono } from "hono";
2
+ import { z } from "zod";
3
+ import type { OperationDefinition, ProviderDefinition } from "../types";
4
+ import { type SelfTestMasterSecrets } from "./self-test-token";
5
+ import type { OperationConnection } from "./types";
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 interface SelfTestAppOptions {
65
+ /** Derived-token verification secrets; without them every self-test route 404s. */
66
+ secrets?: SelfTestMasterSecrets;
67
+ /** In-process invoke bound to the tenant-facing app's /v1 pipeline. */
68
+ invoke: SelfTestOperationInvoke;
69
+ /** Overall request budget; defaults to env / 120s. */
70
+ requestBudgetMs?: number;
71
+ /** Env override for secret collection + budget resolution (tests). */
72
+ env?: Readonly<Record<string, string | undefined>>;
73
+ }
74
+ /**
75
+ * Stable sha256 over the provider's declared health plan (operations, case
76
+ * names, timeouts) so the scheduler can detect plan/image skew by comparing
77
+ * the manifest planDigest against the pod-reported one.
78
+ */
79
+ export declare function computeSelfTestPlanDigest(provider: ProviderDefinition): string;
80
+ /**
81
+ * Fail-closed read-only classification: an operation may self-test only when
82
+ * its own metadata marks it read-only (annotations.readOnly or
83
+ * toolRouter.riskClass "read") and nothing marks it side-effecting. Unclassified
84
+ * operations are refused so the endpoint cannot become a mutation oracle.
85
+ */
86
+ export declare function isSelfTestReadOnlyOperation(operation: OperationDefinition): boolean;
87
+ /** Binds the self-test executor to a tenant app's /v1 pipeline in-process. */
88
+ export declare function createSelfTestInvoke(app: {
89
+ request: (input: string, requestInit?: RequestInit) => Response | Promise<Response>;
90
+ }): SelfTestOperationInvoke;
91
+ export declare function resolveSelfTestPort(env?: Readonly<Record<string, string | undefined>>): number;
92
+ /**
93
+ * Builds the internal self-test Hono app. This app is served on a SEPARATE
94
+ * listener (default :3001) that the tenant-facing gateway never dials; when no
95
+ * master secret is resolvable the self-test route responds 404 (safe default
96
+ * off) while `GET /healthz` stays available for liveness.
97
+ */
98
+ export declare function createSelfTestApp(provider: ProviderDefinition, options: SelfTestAppOptions): Hono;