@apifuse/provider-sdk 2.2.0-beta.60 → 2.2.0-beta.62

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 (85) hide show
  1. package/AUTHORING.md +17 -16
  2. package/CHANGELOG.md +8 -0
  3. package/bin/apifuse-dev.ts +5 -7
  4. package/bin/apifuse-pack-types.ts +26 -0
  5. package/bin/apifuse-perf.ts +4 -6
  6. package/bin/apifuse-record.ts +4 -7
  7. package/bin/apifuse-submit-check.ts +2 -2
  8. package/dist/define.d.ts +3 -2
  9. package/dist/define.js +1 -1
  10. package/dist/dev.js +3 -1
  11. package/dist/engine.d.ts +7 -6
  12. package/dist/engine.js +3 -4
  13. package/dist/error-resolution.js +21 -4
  14. package/dist/errors.d.ts +3 -0
  15. package/dist/errors.js +9 -0
  16. package/dist/handle-meta.d.ts +55 -0
  17. package/dist/handle-meta.js +57 -0
  18. package/dist/handle.d.ts +208 -0
  19. package/dist/handle.js +299 -0
  20. package/dist/i18n/catalog.js +21 -1
  21. package/dist/index.d.ts +7 -4
  22. package/dist/index.js +6 -3
  23. package/dist/lint.d.ts +3 -0
  24. package/dist/lint.js +372 -47
  25. package/dist/provider.d.ts +5 -3
  26. package/dist/provider.js +4 -2
  27. package/dist/runtime/handle-wordlist.d.ts +43 -0
  28. package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
  29. package/dist/runtime/handle.d.ts +71 -0
  30. package/dist/runtime/handle.js +900 -0
  31. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  32. package/dist/runtime/http-telemetry-binding.js +152 -0
  33. package/dist/runtime/http-telemetry-guard.d.ts +6 -0
  34. package/dist/runtime/http-telemetry-guard.js +150 -0
  35. package/dist/runtime/http-telemetry.d.ts +104 -0
  36. package/dist/runtime/http-telemetry.js +190 -0
  37. package/dist/runtime/http.d.ts +31 -0
  38. package/dist/runtime/http.js +222 -146
  39. package/dist/runtime/native-network.d.ts +10 -1
  40. package/dist/runtime/native-network.js +230 -29
  41. package/dist/runtime/native-telemetry.d.ts +142 -0
  42. package/dist/runtime/native-telemetry.js +231 -0
  43. package/dist/runtime/state.d.ts +4 -1
  44. package/dist/runtime/state.js +32 -17
  45. package/dist/server/index.d.ts +3 -0
  46. package/dist/server/index.js +2 -0
  47. package/dist/server/sensitive-values.d.ts +1 -1
  48. package/dist/server/sensitive-values.js +0 -3
  49. package/dist/server/serve-implementation.d.ts +6 -11
  50. package/dist/server/serve-implementation.js +119 -52
  51. package/dist/testing/run.js +10 -7
  52. package/dist/types.d.ts +4 -107
  53. package/package.json +1 -1
  54. package/src/define.ts +4 -3
  55. package/src/dev.ts +3 -1
  56. package/src/engine.ts +5 -6
  57. package/src/error-resolution.ts +21 -4
  58. package/src/errors.ts +13 -0
  59. package/src/handle-meta.ts +93 -0
  60. package/src/handle.ts +612 -0
  61. package/src/i18n/catalog.ts +23 -1
  62. package/src/index.ts +45 -13
  63. package/src/lint.ts +437 -55
  64. package/src/provider.ts +19 -20
  65. package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
  66. package/src/runtime/handle.ts +1172 -0
  67. package/src/runtime/http-telemetry-binding.ts +162 -0
  68. package/src/runtime/http-telemetry-guard.ts +155 -0
  69. package/src/runtime/http-telemetry.ts +292 -0
  70. package/src/runtime/http.ts +295 -173
  71. package/src/runtime/native-network.ts +281 -33
  72. package/src/runtime/native-telemetry.ts +366 -0
  73. package/src/runtime/state.ts +44 -16
  74. package/src/server/index.ts +30 -0
  75. package/src/server/sensitive-values.ts +0 -3
  76. package/src/server/serve-implementation.ts +127 -89
  77. package/src/testing/run.ts +11 -7
  78. package/src/types.ts +4 -137
  79. package/dist/choice-token.d.ts +0 -24
  80. package/dist/choice-token.js +0 -74
  81. package/dist/runtime/choice-wordlist.d.ts +0 -9
  82. package/dist/runtime/choice.d.ts +0 -27
  83. package/dist/runtime/choice.js +0 -801
  84. package/src/choice-token.ts +0 -165
  85. package/src/runtime/choice.ts +0 -1197
package/dist/lint.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "./error-resolution.js";
3
+ import { APIFUSE_HANDLE_META_KEY, handleFieldDescription, handleIssuerList, isHandleIssuedBy, isHandleFieldMeta, } from "./handle-meta.js";
3
4
  import { lintPublicSchemaFieldNames } from "./public-schema-field-lint.js";
4
5
  import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY } from "./schema.js";
5
6
  const requireModule = createRequire(import.meta.url);
@@ -56,12 +57,7 @@ const AUTH_LIFECYCLE_SEGMENT_WORDS = new Set([
56
57
  "auth",
57
58
  ]);
58
59
  // Ambiguous as a prefix, unambiguous when they are the whole operation id.
59
- const AUTH_LIFECYCLE_WHOLE_ID_WORDS = new Set([
60
- "authorize",
61
- "revoke",
62
- "unlink",
63
- "disconnect",
64
- ]);
60
+ const AUTH_LIFECYCLE_WHOLE_ID_WORDS = new Set(["authorize", "revoke", "unlink", "disconnect"]);
65
61
  // A verb stem followed by a direction word across two segments: `sign-out`,
66
62
  // `user_sign_up_flow`, and the spelled-out `log-in` / `shop-log-out` forms
67
63
  // (their fused equivalents `login`/`logout` live in the segment set above).
@@ -304,15 +300,18 @@ function getObjectShape(schema) {
304
300
  }
305
301
  function getChildSchemas(schema) {
306
302
  const seen = new Map();
303
+ const origins = new Map();
307
304
  const def = getSchemaDef(schema);
308
- const add = (key, value) => {
305
+ const add = (key, value, origin = "structural") => {
309
306
  if (!isSchema(value)) {
310
307
  return;
311
308
  }
312
- seen.set(`${key}:${seen.size}`, value);
309
+ const id = `${key}:${seen.size}`;
310
+ seen.set(id, value);
311
+ origins.set(id, origin);
313
312
  };
314
313
  for (const [key, value] of Object.entries(getObjectShape(schema))) {
315
- add(key, value);
314
+ add(key, value, "shape");
316
315
  }
317
316
  add("element", schema.element);
318
317
  add("innerType", schema.innerType);
@@ -352,6 +351,9 @@ function getChildSchemas(schema) {
352
351
  "schema",
353
352
  "innerType",
354
353
  "type",
354
+ // zod v4 intersections keep their branches on the definition.
355
+ "left",
356
+ "right",
355
357
  "valueType",
356
358
  "keyType",
357
359
  "item",
@@ -375,6 +377,7 @@ function getChildSchemas(schema) {
375
377
  return Array.from(seen.entries()).map(([entryKey, child]) => ({
376
378
  key: entryKey.split(":")[0] ?? entryKey,
377
379
  schema: child,
380
+ origin: origins.get(entryKey) ?? "structural",
378
381
  }));
379
382
  }
380
383
  function uniqueFields(fields) {
@@ -441,6 +444,78 @@ function isDeclaredSensitiveField(key, schema) {
441
444
  return (declaration === true ||
442
445
  (declaration === false && PUBLIC_DECLARABLE_FIELD_NAMES.has(normalizeFieldName(key))));
443
446
  }
447
+ // Child keys produced by getChildSchemas() that wrap a schema without adding a
448
+ // property path segment (optional/nullable/default/pipe/effects/etc.).
449
+ const SCHEMA_WRAPPER_NODE_KEYS = [
450
+ "unwrap",
451
+ "innerType",
452
+ "sourceType",
453
+ "schema",
454
+ "type",
455
+ "in",
456
+ "out",
457
+ "option",
458
+ "pipe",
459
+ "payload",
460
+ "item",
461
+ "rest",
462
+ "catchall",
463
+ "keyType",
464
+ "valueType",
465
+ // zod v4 intersection branches: transparent, the enclosing property key applies.
466
+ "left",
467
+ "right",
468
+ ];
469
+ function isSchemaWrapperNodeKey(key) {
470
+ return SCHEMA_WRAPPER_NODE_KEYS.includes(key) || key.startsWith("pipe.");
471
+ }
472
+ function getOwnHandleFieldMeta(schema) {
473
+ const value = Reflect.get(getSchemaMetadata(schema), APIFUSE_HANDLE_META_KEY);
474
+ return isHandleFieldMeta(value) ? value : undefined;
475
+ }
476
+ /**
477
+ * Handle meta placed by `kind.field()` lives on the string schema itself; zod
478
+ * wrappers (`.optional()`, `.nullable()`, `.default()`, pipes) do not inherit
479
+ * registry metadata, so look through wrapper nodes until a non-wrapper is hit.
480
+ */
481
+ function getHandleFieldMeta(schema, seen = new Set()) {
482
+ if (!isSchema(schema) || seen.has(schema))
483
+ return undefined;
484
+ seen.add(schema);
485
+ const own = getOwnHandleFieldMeta(schema);
486
+ if (own)
487
+ return own;
488
+ for (const child of getChildSchemas(schema)) {
489
+ if (child.origin === "shape" || !isSchemaWrapperNodeKey(child.key))
490
+ continue;
491
+ const found = getHandleFieldMeta(child.schema, seen);
492
+ if (found)
493
+ return found;
494
+ }
495
+ return undefined;
496
+ }
497
+ /**
498
+ * True when `schema` is a handle field (directly or through zod wrappers) whose
499
+ * description is still the SDK-generated wording and no wrapper adds its own
500
+ * description.
501
+ */
502
+ function isSdkOwnedHandleField(schema, seen = new Set()) {
503
+ if (!isSchema(schema) || seen.has(schema))
504
+ return false;
505
+ seen.add(schema);
506
+ const own = getOwnHandleFieldMeta(schema);
507
+ if (own)
508
+ return schema.description === handleFieldDescription(own);
509
+ if (schema.description)
510
+ return false;
511
+ for (const child of getChildSchemas(schema)) {
512
+ if (child.origin === "shape" || !isSchemaWrapperNodeKey(child.key))
513
+ continue;
514
+ if (isSdkOwnedHandleField(child.schema, seen))
515
+ return true;
516
+ }
517
+ return false;
518
+ }
444
519
  function collectUnmarkedSensitiveFields(schema, basePath, seen = new Set()) {
445
520
  if (!isSchema(schema) || seen.has(schema)) {
446
521
  return [];
@@ -457,23 +532,7 @@ function collectUnmarkedSensitiveFields(schema, basePath, seen = new Set()) {
457
532
  for (const child of getChildSchemas(schema)) {
458
533
  if (Object.hasOwn(getObjectShape(schema), child.key))
459
534
  continue;
460
- const isWrapperNode = [
461
- "unwrap",
462
- "innerType",
463
- "sourceType",
464
- "schema",
465
- "type",
466
- "in",
467
- "out",
468
- "option",
469
- "pipe",
470
- "payload",
471
- "item",
472
- "rest",
473
- "catchall",
474
- "keyType",
475
- "valueType",
476
- ].includes(child.key);
535
+ const isWrapperNode = SCHEMA_WRAPPER_NODE_KEYS.includes(child.key);
477
536
  const childPath = child.key === "element" || child.key.startsWith("element.")
478
537
  ? `${basePath}[]`
479
538
  : isWrapperNode || child.key.startsWith("pipe.")
@@ -493,6 +552,14 @@ function collectSchemaDescriptionKeyDiagnostics(schema, basePath, seen = new Set
493
552
  const diagnostics = [];
494
553
  const currentPath = basePath || "schema";
495
554
  const hasDescriptionKey = getSchemaDescriptionKey(schema) !== undefined;
555
+ // Handle fields (`kind.field()`, ADR-0012) carry SDK-owned wording via
556
+ // .describe() plus x-apifuse-handle meta; they count as described and are
557
+ // leaves, so neither this node nor its wrapper chain needs a describeKey.
558
+ // A provider override of that wording (`.describe("…")` on the field or on
559
+ // a wrapper) is provider prose again and stays subject to the rules below.
560
+ if (isSdkOwnedHandleField(schema)) {
561
+ return diagnostics;
562
+ }
496
563
  if (schema.description && !hasDescriptionKey) {
497
564
  diagnostics.push({
498
565
  rule: "schema-description-raw-prose",
@@ -512,23 +579,7 @@ function collectSchemaDescriptionKeyDiagnostics(schema, basePath, seen = new Set
512
579
  });
513
580
  }
514
581
  for (const child of getChildSchemas(schema)) {
515
- const isWrapperNode = [
516
- "unwrap",
517
- "innerType",
518
- "sourceType",
519
- "schema",
520
- "type",
521
- "in",
522
- "out",
523
- "option",
524
- "pipe",
525
- "payload",
526
- "item",
527
- "rest",
528
- "catchall",
529
- "keyType",
530
- "valueType",
531
- ].includes(child.key);
582
+ const isWrapperNode = SCHEMA_WRAPPER_NODE_KEYS.includes(child.key);
532
583
  const isStructuralNode = isWrapperNode ||
533
584
  child.key.startsWith("pipe.") ||
534
585
  child.key === "element" ||
@@ -992,7 +1043,8 @@ function collectLiteralErrorCodeValues(args) {
992
1043
  after++;
993
1044
  }
994
1045
  const nextChar = after < args.length ? (args[after] ?? "") : "";
995
- if (!value.includes("\\") && (nextChar === "," || nextChar === "}" || nextChar === "")) {
1046
+ if (!value.includes("\\") &&
1047
+ (nextChar === "," || nextChar === "}" || nextChar === "")) {
996
1048
  codes.push(value);
997
1049
  }
998
1050
  index = end;
@@ -1082,6 +1134,279 @@ function lintUndeclaredThrownErrorCodes(provider) {
1082
1134
  }
1083
1135
  return diagnostics;
1084
1136
  }
1137
+ function collectHandleFields(schema, basePath, key = undefined, seen = new Set()) {
1138
+ if (!isSchema(schema))
1139
+ return [];
1140
+ // Record before the cycle guard so one `kind.field()` instance reused under
1141
+ // two property keys is still reported at both positions.
1142
+ const own = getOwnHandleFieldMeta(schema);
1143
+ if (own && key !== undefined) {
1144
+ return [{ path: basePath, key, meta: own }];
1145
+ }
1146
+ // Path-local cycle guard: the same wrapped schema instance may legitimately
1147
+ // appear under several property keys (`Kind.field().optional()` reused), and
1148
+ // each position must be checked. Only true ancestor cycles are cut.
1149
+ if (seen.has(schema))
1150
+ return [];
1151
+ seen.add(schema);
1152
+ try {
1153
+ return collectHandleFieldChildren(schema, basePath, key, seen);
1154
+ }
1155
+ finally {
1156
+ seen.delete(schema);
1157
+ }
1158
+ }
1159
+ function collectHandleFieldChildren(schema, basePath, key, seen) {
1160
+ const out = [];
1161
+ // zod exposes the same inner schema under several structural edges
1162
+ // (`unwrap`/`innerType`, `element`/`def.element`); traverse each structural
1163
+ // child object once. Shape children are always traversed: the same instance
1164
+ // under two property keys is two fields.
1165
+ const structuralSeen = new Set();
1166
+ for (const child of getChildSchemas(schema)) {
1167
+ if (child.origin !== "shape") {
1168
+ if (structuralSeen.has(child.schema))
1169
+ continue;
1170
+ structuralSeen.add(child.schema);
1171
+ }
1172
+ if (child.origin === "shape") {
1173
+ // Provider-chosen property key: always a real field, even when it is
1174
+ // spelled like a structural edge (`item`, `type`, `schema`, ...).
1175
+ out.push(...collectHandleFields(child.schema, `${basePath}.${child.key}`, child.key, seen));
1176
+ }
1177
+ else if (isSchemaWrapperNodeKey(child.key)) {
1178
+ out.push(...collectHandleFields(child.schema, basePath, key, seen));
1179
+ }
1180
+ else if (child.key === "element" || child.key.startsWith("element.")) {
1181
+ out.push(...collectHandleFields(child.schema, `${basePath}[]`, key, seen));
1182
+ }
1183
+ else if (/^\d+$/.test(child.key)) {
1184
+ out.push(...collectHandleFields(child.schema, `${basePath}[${child.key}]`, key, seen));
1185
+ }
1186
+ else {
1187
+ out.push(...collectHandleFields(child.schema, `${basePath}.${child.key}`, child.key, seen));
1188
+ }
1189
+ }
1190
+ return out;
1191
+ }
1192
+ /**
1193
+ * Reads `provider.handle` structurally. The declaration type is owned by
1194
+ * define.ts/types.ts; lint only needs the HandleKindDeclaration view, so
1195
+ * malformed entries are skipped rather than reported.
1196
+ */
1197
+ function readHandleKindDeclarations(value) {
1198
+ if (!Array.isArray(value))
1199
+ return [];
1200
+ return value.filter((entry) => {
1201
+ if (typeof entry !== "object" || entry === null)
1202
+ return false;
1203
+ const record = entry;
1204
+ return (typeof record.name === "string" &&
1205
+ (record.type === "cursor" || record.type === "draft") &&
1206
+ typeof record.fieldName === "string" &&
1207
+ (record.issuedBy === undefined || isHandleIssuedBy(record.issuedBy)));
1208
+ });
1209
+ }
1210
+ /**
1211
+ * ADR-0012 handle rules: every `x-apifuse-handle` schema field must belong to
1212
+ * a declared kind under its declared property key; every declared kind must be
1213
+ * both issued (some output) and accepted (some input); every `issuedBy` entry
1214
+ * must be an operation key and at least one of them must output the field; and
1215
+ * `ctx.handle` needs `state`.
1216
+ */
1217
+ function lintHandleDeclarations(provider) {
1218
+ const diagnostics = [];
1219
+ const providerLabel = provider.id ? `Provider "${provider.id}"` : "Provider";
1220
+ const kinds = readHandleKindDeclarations(provider.handle);
1221
+ const kindByName = new Map(kinds.map((kind) => [kind.name, kind]));
1222
+ const operations = provider.operations ?? {};
1223
+ if (kinds.length > 0 && !provider.state) {
1224
+ diagnostics.push({
1225
+ rule: "handle-requires-state",
1226
+ level: "warn",
1227
+ field: "handle",
1228
+ message: `${providerLabel} declares handle kinds but not the state capability; ctx.handle stores records through ctx.state, so declare state: true.`,
1229
+ });
1230
+ }
1231
+ const kindsInInputs = new Set();
1232
+ const kindsInOutputs = new Set();
1233
+ const outputKindsByOperation = new Map();
1234
+ for (const [operationKey, operation] of Object.entries(operations)) {
1235
+ const positions = [
1236
+ { side: "input", fields: collectHandleFields(operation.input, "input") },
1237
+ { side: "output", fields: collectHandleFields(operation.output, "output") },
1238
+ ];
1239
+ for (const { side, fields } of positions) {
1240
+ for (const occurrence of fields) {
1241
+ const field = `operations.${operationKey}.${occurrence.path}`;
1242
+ const { kind, fieldName } = occurrence.meta;
1243
+ if (!kindByName.has(kind)) {
1244
+ diagnostics.push({
1245
+ rule: "handle-kind-undeclared",
1246
+ level: "error",
1247
+ field,
1248
+ message: `${providerLabel} operation "${operationKey}" ${side} field "${occurrence.path}" is a handle of kind "${kind}", which is not declared in provider.handle. Add the kind to handle: [...] or remove the field.`,
1249
+ });
1250
+ }
1251
+ if (occurrence.key !== fieldName) {
1252
+ diagnostics.push({
1253
+ rule: "handle-field-name",
1254
+ level: "error",
1255
+ field,
1256
+ message: `${providerLabel} operation "${operationKey}" ${side} declares handle kind "${kind}" under property "${occurrence.key}" but the kind's fieldName is "${fieldName}". Use the same key in every schema so the LLM sees one name.`,
1257
+ });
1258
+ }
1259
+ if (side === "input") {
1260
+ kindsInInputs.add(kind);
1261
+ }
1262
+ else {
1263
+ kindsInOutputs.add(kind);
1264
+ let outputKinds = outputKindsByOperation.get(operationKey);
1265
+ if (!outputKinds) {
1266
+ outputKinds = new Set();
1267
+ outputKindsByOperation.set(operationKey, outputKinds);
1268
+ }
1269
+ outputKinds.add(kind);
1270
+ }
1271
+ }
1272
+ }
1273
+ }
1274
+ for (const kind of kinds) {
1275
+ const field = `handle.${kind.name}`;
1276
+ const issued = kindsInOutputs.has(kind.name);
1277
+ const accepted = kindsInInputs.has(kind.name);
1278
+ if (!issued) {
1279
+ diagnostics.push({
1280
+ rule: "handle-field-parity",
1281
+ level: "error",
1282
+ field,
1283
+ message: `${providerLabel} handle kind "${kind.name}" is never issued: no operation output contains its field "${kind.fieldName}". Add ${kind.name}.field() to the issuing operation's output.`,
1284
+ });
1285
+ }
1286
+ if (!accepted) {
1287
+ diagnostics.push({
1288
+ rule: "handle-field-parity",
1289
+ level: "error",
1290
+ field,
1291
+ message: `${providerLabel} handle kind "${kind.name}" is never accepted: no operation input contains its field "${kind.fieldName}". Add ${kind.name}.field() to the consuming operation's input.`,
1292
+ });
1293
+ }
1294
+ if (kind.issuedBy === undefined)
1295
+ continue;
1296
+ const issuers = handleIssuerList(kind.issuedBy);
1297
+ const known = issuers.filter((operationKey) => Object.hasOwn(operations, operationKey));
1298
+ for (const operationKey of issuers) {
1299
+ if (known.includes(operationKey))
1300
+ continue;
1301
+ diagnostics.push({
1302
+ rule: "handle-issued-by",
1303
+ level: "error",
1304
+ field,
1305
+ message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${operationKey}", which is not an operation key.`,
1306
+ });
1307
+ }
1308
+ if (known.length === 0)
1309
+ continue;
1310
+ const issuing = known.filter((operationKey) => outputKindsByOperation.get(operationKey)?.has(kind.name));
1311
+ if (issuing.length > 0)
1312
+ continue;
1313
+ if (known.length === 1) {
1314
+ diagnostics.push({
1315
+ rule: "handle-issued-by",
1316
+ level: "error",
1317
+ field,
1318
+ message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${known[0]}", but that operation's output has no "${kind.fieldName}" handle field.`,
1319
+ });
1320
+ }
1321
+ else {
1322
+ diagnostics.push({
1323
+ rule: "handle-issued-by",
1324
+ level: "error",
1325
+ field,
1326
+ message: `${providerLabel} handle kind "${kind.name}" declares issuedBy ${known
1327
+ .map((operationKey) => `"${operationKey}"`)
1328
+ .join(", ")}, but none of those operations' outputs has a "${kind.fieldName}" handle field. At least one listed issuer must output it.`,
1329
+ });
1330
+ }
1331
+ }
1332
+ return diagnostics;
1333
+ }
1334
+ const LEGACY_CHOICE_USAGE_PATTERN = /\bctx\.choice\b|\b(?:createProviderChoiceToken|parseProviderChoiceToken|ProviderChoiceTokenError|createTestProviderChoiceContext)\b/;
1335
+ const LEGACY_CHOICE_USAGE_MESSAGE = "Choice tokens were replaced by handles (ADR-0012). See docs/migrations/handle.md.";
1336
+ /**
1337
+ * The inline choice-token API was removed outright (no aliases). Any surviving
1338
+ * reference in provider source would fail at runtime, so report it at check time.
1339
+ */
1340
+ const LEGACY_CHOICE_IDENTIFIERS = new Set([
1341
+ "createProviderChoiceToken",
1342
+ "parseProviderChoiceToken",
1343
+ "ProviderChoiceTokenError",
1344
+ "createTestProviderChoiceContext",
1345
+ ]);
1346
+ /**
1347
+ * Finds the first legacy choice API reference in executable code. Uses the
1348
+ * TypeScript AST when available so comments, string and template literal text
1349
+ * never count (a migration note such as `// moved from ctx.choice` must not fail
1350
+ * the provider) while code inside `${}` interpolations still does. Falls back
1351
+ * to a raw-source regex when TypeScript is not installed.
1352
+ */
1353
+ function findLegacyChoiceUsage(source) {
1354
+ let ts;
1355
+ try {
1356
+ ts = getTypeScript();
1357
+ }
1358
+ catch {
1359
+ return LEGACY_CHOICE_USAGE_PATTERN.exec(source)?.[0];
1360
+ }
1361
+ const file = ts.createSourceFile("provider.ts", source, ts.ScriptTarget.Latest, false, ts.ScriptKind.TSX);
1362
+ let found;
1363
+ const visit = (node) => {
1364
+ if (found)
1365
+ return;
1366
+ if (ts.isIdentifier(node) && LEGACY_CHOICE_IDENTIFIERS.has(node.text)) {
1367
+ found = node.text;
1368
+ return;
1369
+ }
1370
+ if (ts.isPropertyAccessExpression(node) &&
1371
+ ts.isIdentifier(node.expression) &&
1372
+ node.expression.text === "ctx" &&
1373
+ node.name.text === "choice") {
1374
+ found = "ctx.choice";
1375
+ return;
1376
+ }
1377
+ ts.forEachChild(node, visit);
1378
+ };
1379
+ visit(file);
1380
+ return found;
1381
+ }
1382
+ function lintLegacyChoiceUsage(provider) {
1383
+ const sources = [];
1384
+ if (provider.authFlowSource) {
1385
+ sources.push({ field: "auth.flow", source: provider.authFlowSource });
1386
+ }
1387
+ for (const [filePath, source] of Object.entries(provider.providerSourceFiles ?? {})) {
1388
+ sources.push({ field: `sourceFiles.${filePath}`, source });
1389
+ }
1390
+ for (const [operationKey, operation] of Object.entries(provider.operations ?? {})) {
1391
+ const source = getOperationSource(operation);
1392
+ if (source) {
1393
+ sources.push({ field: `operations.${operationKey}.handler`, source });
1394
+ }
1395
+ }
1396
+ return sources.flatMap(({ field, source }) => {
1397
+ const match = findLegacyChoiceUsage(source);
1398
+ if (!match)
1399
+ return [];
1400
+ return [
1401
+ {
1402
+ rule: "legacy-choice-usage",
1403
+ level: "error",
1404
+ field,
1405
+ message: `Legacy choice API "${match}" is no longer supported. ${LEGACY_CHOICE_USAGE_MESSAGE}`,
1406
+ },
1407
+ ];
1408
+ });
1409
+ }
1085
1410
  export function lintOperation(op) {
1086
1411
  const diagnostics = [];
1087
1412
  const hasDescriptionKey = typeof op.descriptionKey === "string" && op.descriptionKey.length > 0;
@@ -1181,6 +1506,8 @@ export function lintProviderWithInformation(provider, options = {}) {
1181
1506
  ...lintSelfHostedBrowserPatterns(provider, options),
1182
1507
  ...lintBrowserVersionLiterals(provider),
1183
1508
  ...lintUndeclaredThrownErrorCodes(provider),
1509
+ ...lintLegacyChoiceUsage(provider),
1510
+ ...lintHandleDeclarations(provider),
1184
1511
  ];
1185
1512
  if (provider.operations) {
1186
1513
  const authMode = provider.auth?.mode;
@@ -1207,9 +1534,7 @@ export function lintProviderWithInformation(provider, options = {}) {
1207
1534
  // Every authenticated mode owns an auth.flow; `oauth2_proxied` was
1208
1535
  // previously exempt, which let auth-lifecycle operations ship on
1209
1536
  // proxied providers unchecked.
1210
- if (authMode === "credentials" ||
1211
- authMode === "oauth2" ||
1212
- authMode === "oauth2_proxied") {
1537
+ if (authMode === "credentials" || authMode === "oauth2" || authMode === "oauth2_proxied") {
1213
1538
  for (const operationKey of Object.keys(provider.operations)) {
1214
1539
  if (isAuthLifecycleOperationId(operationKey, authMode)) {
1215
1540
  diagnostics.push({
@@ -1,7 +1,6 @@
1
1
  export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, defineCredentialsAuth, } from "./auth.js";
2
2
  export type { CredentialsAuthChallengeDefinition, CredentialsAuthChallengeRequest, CredentialsAuthCompleteResult, CredentialsAuthCredential, CredentialsAuthField, CredentialsAuthFields, CredentialsAuthFieldType, CredentialsAuthInput, CredentialsAuthLoginResult, DefineCredentialsAuthOptions, DefinedCredentialsAuth, } from "./auth.js";
3
3
  export { createFormCeremony } from "./ceremonies/index.js";
4
- export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, type ProviderChoiceTokenErrorReason, type ProviderChoiceTokenPayload, parseProviderChoiceToken, } from "./choice-token.js";
5
4
  export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define.js";
6
5
  export type { OperationDefinitionFor, ProviderBuilder, ProviderContextOf, ProviderDeclaration, ProviderDefinitionFor, } from "./define.js";
7
6
  export type { JsonPrimitive, JsonValue } from "./contract-json.js";
@@ -11,9 +10,12 @@ export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, i
11
10
  export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RUNTIME_ENV_NAMES, ENGINE_OWNED_TELEMETRY_ENV_NAMES, isEngineOwnedEnvName, isEngineOwnedProxyCredentialName, isEngineOwnedResolverCredentialName, isEngineOwnedRuntimeEnvName, isEngineOwnedTelemetryEnvName, PROVIDER_CAPABILITY_KEYS, PROVIDER_ENGINE_PROTOCOL_VERSION, readEngineProxyCredentials, } from "./engine.js";
12
11
  export type { ProviderCapabilityKey, ProviderEngine, ProviderEngineAttachmentInput, ProviderEngineBindingCandidates, ProviderEngineCapabilitySurface, ProviderEngineRequest, ProviderEngineResidentSurface, ProviderEngineSession, ProviderEngineTransport, } from "./engine.js";
13
12
  export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
14
- export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
13
+ export * from "./handle.js";
14
+ export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
15
+ export { type CreateHandleContextOptions, type CreateTestHandleContextOptions, createHandleContext, createTestHandleContext, type HandleNormalizationClass, type NormalizedHandle, normalizeHandle, } from "./runtime/handle.js";
16
+ export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, } from "./runtime/state.js";
15
17
  export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, untrustedContent, z, } from "./schema.js";
16
- export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationExample, OperationErrorCode, ProviderErrorStatus, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceConsumeMode, ProviderChoiceConsumeResult, ProviderChoiceContext, ProviderChoiceExplicitParseResult, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderContextFor, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProxiedOAuthConfig, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeTarget, ProviderRuntimeState, ProviderStealthConfig, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateNamespaceScope, StateValue, StateWriteOptions, StealthChallengeClassification, } from "./types.js";
18
+ export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationExample, OperationErrorCode, ProviderErrorStatus, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationTransport, ProviderAccessVisibility, ProviderContext, ProviderContextFor, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProxiedOAuthConfig, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeTarget, ProviderRuntimeState, ProviderStealthConfig, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateNamespaceScope, StateValue, StateWriteOptions, StealthChallengeClassification, } from "./types.js";
17
19
  export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
18
20
  export type { NativeGatewayProxy, NativeGatewayProxyResolutionInput, NativeGatewayProxySkipReason, NativeGatewayProxySynthesizer, NativeGatewayProxySynthesisResult, NativeGatewayProxySynthesisInput, NativeNetworkClientOptions, NativeNetworkErrorCode, VendorCredentialLookup, VendorCredentialResolver, } from "./runtime/native-network.js";
19
21
  export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";
package/dist/provider.js CHANGED
@@ -1,12 +1,14 @@
1
1
  export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, defineCredentialsAuth, } from "./auth.js";
2
2
  export { createFormCeremony } from "./ceremonies/index.js";
3
- export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, parseProviderChoiceToken, } from "./choice-token.js";
4
3
  export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define.js";
5
4
  export { AssertionExpressionSchema, AssertionPredicateSchema, AssertStepSchema, BoundedJsonPathSchema, CandidateBlockSchema, CandidatePolicySchema, CredentialRefDeclarationSchema, defineHealthScenario, HealthScenarioSchema, HealthStepSchema, ExtractStepSchema, FindFirstSchema, GuardStepSchema, JournalPolicySchema, JsonTemplateSchema, ManualTriggerPolicySchema, OperandSchema, OperationStepSchema, QuantifierSchema, ReferenceSchema, RetryPolicySchema, SafeRegexSchema, ScopedAssertionExpressionSchema, ScopedAssertionPredicateSchema, ScopedItemReferenceSchema, ScopedOperandSchema, StepReferenceSchema, RelativeDateNodeSchema, ValueTypeSchema, } from "./health-scenario.js";
6
5
  export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, isTransportError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors.js";
7
6
  export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RUNTIME_ENV_NAMES, ENGINE_OWNED_TELEMETRY_ENV_NAMES, isEngineOwnedEnvName, isEngineOwnedProxyCredentialName, isEngineOwnedResolverCredentialName, isEngineOwnedRuntimeEnvName, isEngineOwnedTelemetryEnvName, PROVIDER_CAPABILITY_KEYS, PROVIDER_ENGINE_PROTOCOL_VERSION, readEngineProxyCredentials, } from "./engine.js";
8
7
  export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
9
- export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
8
+ export * from "./handle.js";
9
+ export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
10
+ export { createHandleContext, createTestHandleContext, normalizeHandle, } from "./runtime/handle.js";
11
+ export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, } from "./runtime/state.js";
10
12
  export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, sensitive, untrustedContent, z, } from "./schema.js";
11
13
  export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
12
14
  export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";
@@ -0,0 +1,43 @@
1
+ export declare const HANDLE_WORDLIST_SIZE = 1296;
2
+ export declare const HANDLE_WORD_ENTROPY_BITS: number;
3
+ /**
4
+ * Bound handles live under `state.forConnection(connectionId)`: another
5
+ * connection cannot address them at all, so no guessing analysis applies. The
6
+ * only requirement is uniqueness among live handles of one connection
7
+ * (1,296^2 = 1.68M keys, CAS-if-absent with retries), which two words satisfy.
8
+ */
9
+ export declare const BOUND_HANDLE_WORD_COUNT = 2;
10
+ /** Public handles are addressable by anyone; ADR 0006 §2 guessing numbers apply. */
11
+ export declare const PUBLIC_HANDLE_WORD_COUNT = 4;
12
+ /** Used when a public kind declares `strength: "high"` or its ttl exceeds one hour. */
13
+ export declare const HIGH_PUBLIC_HANDLE_WORD_COUNT = 5;
14
+ export declare const PUBLIC_HANDLE_ENTROPY_BITS: number;
15
+ export declare const HIGH_PUBLIC_HANDLE_ENTROPY_BITS: number;
16
+ export declare const EFF_SHORT_WORDLIST_2: readonly string[];
17
+ /**
18
+ * Words the runtime may issue. The handle grammar joins words with `-`, so the
19
+ * one hyphenated official entry (`yo-yo`) would fuse with the separator and is
20
+ * never issued. The official list itself stays embedded unchanged.
21
+ */
22
+ export declare const ISSUABLE_HANDLE_WORDS: readonly string[];
23
+ export declare function handleWordAt(index: number): string;
24
+ export declare function isHandleWord(value: string): boolean;
25
+ /**
26
+ * Symmetric-deletion index (SymSpell, max distance 1). Two strings at
27
+ * Levenshtein distance <= 1 always share a form reachable by deleting at most
28
+ * one character from each side, so a lookup of the query and its one-deletion
29
+ * variants yields every candidate; a real distance check then removes the false
30
+ * positives (e.g. transpositions). ~10k entries, O(len) per lookup.
31
+ */
32
+ /** Shortest and longest words in the list (3 and 10 for the official list). */
33
+ export declare const MIN_HANDLE_WORD_LENGTH: number;
34
+ export declare const MAX_HANDLE_WORD_LENGTH: number;
35
+ /**
36
+ * Resolves one handle segment to a wordlist entry: the exact word, or the
37
+ * unique word at edit distance <= `maxDistance`. Returns null when no word or
38
+ * more than one word qualifies. The official list has pairwise edit distance
39
+ * >= 3, so at distance 1 every non-null answer is unique; the uniqueness check
40
+ * is kept as a guard so a wordlist change cannot silently make normalization
41
+ * map one input onto two different canonical handles.
42
+ */
43
+ export declare function findHandleWordWithinDistance(segment: string, maxDistance: 1): string | null;