@apifuse/provider-sdk 2.2.0-beta.56 → 2.2.0-beta.58
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/CHANGELOG.md +8 -0
- package/README.md +4 -3
- package/SUBMISSION.md +2 -1
- package/dist/cli/migrate-operation-declaration.d.ts +7 -0
- package/dist/cli/migrate-operation-declaration.js +52 -12
- package/dist/cli/templates/provider/README.md.tpl +4 -2
- package/dist/define.d.ts +1 -0
- package/dist/engine.js +2 -0
- package/dist/lint.js +1 -1
- package/dist/types.d.ts +9 -2
- package/package.json +1 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +3 -0
- package/src/cli/migrate-operation-declaration.ts +75 -10
- package/src/cli/templates/provider/README.md.tpl +4 -2
- package/src/define.ts +1 -0
- package/src/engine.ts +1 -0
- package/src/lint.ts +1 -1
- package/src/types.ts +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.58
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit e137940caa8bc32ce768571a6197651e581c2270.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.57
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit 4087dafdeab771eb91691884ce7315b8c399f863.
|
|
10
|
+
|
|
3
11
|
## 2.2.0-beta.56
|
|
4
12
|
|
|
5
13
|
- Release candidate for main commit 390167b3f51488be0beba25a1ea5eb425d3bf839.
|
package/README.md
CHANGED
|
@@ -115,9 +115,10 @@ the bad request path; provider/runtime failures include `code`, `message`, and
|
|
|
115
115
|
- **`invalid_request` on `/v1/{operation}`**: confirm the request body includes
|
|
116
116
|
`requestId` and `input`. Omit `connection` for public/no-auth operations;
|
|
117
117
|
never send `connection: null`.
|
|
118
|
-
- **Credential-backed operations**: declare `credential.keys`,
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
- **Credential-backed operations**: declare `credential.keys`, except when
|
|
119
|
+
`auth.mode` is `platform-managed`; that auth declaration implies an unfiltered
|
|
120
|
+
credential capability. Pass local-only values through `connection.secrets` and
|
|
121
|
+
read them with `ctx.credential.get("key")` or `ctx.credential.getAccessToken()`.
|
|
121
122
|
- **Provider env secrets**: declare `secrets[]`, set values in your shell or
|
|
122
123
|
`.env`, and read only those names through `ctx.env.get("NAME")`. The SDK
|
|
123
124
|
enforces presence of `required: true` declarations before handlers and auth
|
package/SUBMISSION.md
CHANGED
|
@@ -46,7 +46,8 @@ Fix all blockers before submitting:
|
|
|
46
46
|
- Missing fixture request or response.
|
|
47
47
|
- Fixture data does not parse against schemas.
|
|
48
48
|
- Missing `healthCheck` or `healthCheckUnsupported` on any Operation.
|
|
49
|
-
-
|
|
49
|
+
- User-managed credential auth mode without declared credential keys
|
|
50
|
+
(`platform-managed` auth instead implies the credential capability and forbids keys).
|
|
50
51
|
- High-confidence secret or token material in source, README, package metadata, or fixtures.
|
|
51
52
|
- SDK-native source blockers: prefixed Provider ids, `vendor/` SDK shims or imports, raw `.describe()` prose instead of `describeKey`, raw global `fetch()` calls, and excessive `as Type` assertions.
|
|
52
53
|
|
|
@@ -45,6 +45,7 @@ export type OperationDeclarationRepositoryResult = {
|
|
|
45
45
|
readonly providerRoot: string;
|
|
46
46
|
readonly changedFiles: readonly string[];
|
|
47
47
|
readonly operationCount: number;
|
|
48
|
+
readonly notes: readonly OperationDeclarationNote[];
|
|
48
49
|
readonly sidecar?: string;
|
|
49
50
|
readonly localeTodoCount: number;
|
|
50
51
|
} | {
|
|
@@ -53,9 +54,15 @@ export type OperationDeclarationRepositoryResult = {
|
|
|
53
54
|
readonly refusals: readonly OperationDeclarationRefusal[];
|
|
54
55
|
/** Operations that were independently migratable before repository-atomic refusal. */
|
|
55
56
|
readonly operationCount: number;
|
|
57
|
+
readonly notes: readonly OperationDeclarationNote[];
|
|
56
58
|
readonly changedFiles: readonly string[];
|
|
57
59
|
readonly localeTodoCount: number;
|
|
58
60
|
};
|
|
61
|
+
export type OperationDeclarationNote = {
|
|
62
|
+
readonly code: "runtime_composed_registry";
|
|
63
|
+
readonly path: string;
|
|
64
|
+
readonly initializer: string;
|
|
65
|
+
};
|
|
59
66
|
/** Run the file transform repository-wide, committing writes only if every file is provable. */
|
|
60
67
|
export declare function migrateOperationDeclarationRepository(providerRootInput: string, options?: {
|
|
61
68
|
readonly check?: boolean;
|
|
@@ -68,7 +68,8 @@ function migrateOperationDeclarationInternal(sourceText, fileName, options, cont
|
|
|
68
68
|
}
|
|
69
69
|
const constObjects = collectModuleConstObjects(source);
|
|
70
70
|
const constArrays = collectModuleConstArrays(source);
|
|
71
|
-
const discovery = discoverOperationSites(source, fileName, constObjects, options.operationIds, context.operationSites, context.excludedBindings);
|
|
71
|
+
const discovery = discoverOperationSites(source, fileName, constObjects, options.operationIds, context.operationSites, context.excludedBindings, context.runtimeComposedInitializers);
|
|
72
|
+
context.recordDiscoveredSites?.(discovery.discoveredCount);
|
|
72
73
|
if (discovery.refusals.length > 0) {
|
|
73
74
|
return { status: "refused", refusals: discovery.refusals };
|
|
74
75
|
}
|
|
@@ -599,14 +600,15 @@ function replaceExampleLocaleMember(member, newName, localeKey, source) {
|
|
|
599
600
|
text: `${newName}: ${JSON.stringify(localeKey)}`,
|
|
600
601
|
};
|
|
601
602
|
}
|
|
602
|
-
function discoverOperationSites(source, fileName, constObjects, operationIds, operationSites, excludedBindings) {
|
|
603
|
+
function discoverOperationSites(source, fileName, constObjects, operationIds, operationSites, excludedBindings, runtimeComposedInitializers) {
|
|
603
604
|
const sitesByStart = new Map();
|
|
605
|
+
const discoveredStarts = new Set();
|
|
604
606
|
const localIds = new Map(operationIds ?? []);
|
|
605
607
|
const localExcludedBindings = new Set(excludedBindings ?? []);
|
|
606
608
|
const refusals = [];
|
|
607
609
|
const operationFactories = collectSimpleOperationFactories(source);
|
|
608
610
|
const factoryCallIds = new Map();
|
|
609
|
-
for (const map of collectOperationsMaps(source, constObjects, fileName, refusals)) {
|
|
611
|
+
for (const map of collectOperationsMaps(source, constObjects, fileName, refusals, runtimeComposedInitializers)) {
|
|
610
612
|
for (const property of map.properties) {
|
|
611
613
|
if (ts.isSpreadAssignment(property))
|
|
612
614
|
continue;
|
|
@@ -680,12 +682,15 @@ function discoverOperationSites(source, fileName, constObjects, operationIds, op
|
|
|
680
682
|
if (ts.isCallExpression(node) && isOperationHelperCall(node)) {
|
|
681
683
|
const argument = operationArgument(node);
|
|
682
684
|
const bindingName = enclosingBindingName(node);
|
|
685
|
+
const unwrappedArgument = unwrapExpression(argument);
|
|
686
|
+
if (unwrappedArgument !== undefined && ts.isObjectLiteralExpression(unwrappedArgument)) {
|
|
687
|
+
discoveredStarts.add(unwrappedArgument.getStart(source));
|
|
688
|
+
}
|
|
683
689
|
if (bindingName !== undefined && localExcludedBindings.has(bindingName))
|
|
684
690
|
return;
|
|
685
691
|
const operationKey = (bindingName === undefined ? undefined : localIds.get(bindingName)) ??
|
|
686
692
|
bindingName ??
|
|
687
693
|
"<anonymous>";
|
|
688
|
-
const unwrappedArgument = unwrapExpression(argument);
|
|
689
694
|
if (unwrappedArgument === undefined || !ts.isObjectLiteralExpression(unwrappedArgument)) {
|
|
690
695
|
refusals.push(refusal(fileName, operationKey, "non_literal", "Operation helper argument must be an object literal."));
|
|
691
696
|
}
|
|
@@ -711,8 +716,11 @@ function discoverOperationSites(source, fileName, constObjects, operationIds, op
|
|
|
711
716
|
ts.forEachChild(node, visit);
|
|
712
717
|
};
|
|
713
718
|
visit(source);
|
|
719
|
+
for (const start of sitesByStart.keys())
|
|
720
|
+
discoveredStarts.add(start);
|
|
714
721
|
return {
|
|
715
722
|
sites: [...sitesByStart.values()].sort((left, right) => left.object.getStart(source) - right.object.getStart(source)),
|
|
723
|
+
discoveredCount: discoveredStarts.size,
|
|
716
724
|
refusals,
|
|
717
725
|
};
|
|
718
726
|
}
|
|
@@ -741,7 +749,7 @@ function collectSimpleOperationFactories(source) {
|
|
|
741
749
|
}
|
|
742
750
|
return factories;
|
|
743
751
|
}
|
|
744
|
-
function collectOperationsMaps(source, constObjects, fileName, refusals) {
|
|
752
|
+
function collectOperationsMaps(source, constObjects, fileName, refusals, runtimeComposedInitializers) {
|
|
745
753
|
const maps = new Map();
|
|
746
754
|
const inspect = (expression, label) => {
|
|
747
755
|
const unwrapped = unwrapExpression(expression);
|
|
@@ -758,6 +766,8 @@ function collectOperationsMaps(source, constObjects, fileName, refusals) {
|
|
|
758
766
|
return;
|
|
759
767
|
}
|
|
760
768
|
if (ts.isCallExpression(unwrapped)) {
|
|
769
|
+
if (runtimeComposedInitializers?.has(expression.getStart(source)) === true)
|
|
770
|
+
return;
|
|
761
771
|
refusals.push(refusal(fileName, label, "factory_composed_operations", "The operations map is produced by a factory call and cannot be statically enumerated."));
|
|
762
772
|
}
|
|
763
773
|
};
|
|
@@ -1274,6 +1284,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
|
|
|
1274
1284
|
const todos = [];
|
|
1275
1285
|
const refusals = [...repositoryIndex.refusals];
|
|
1276
1286
|
let operationCount = 0;
|
|
1287
|
+
let repositoryDiscoveredCount = 0;
|
|
1277
1288
|
for (const sourcePath of sourceFiles) {
|
|
1278
1289
|
const relativePath = slash(relative(providerRoot, sourcePath));
|
|
1279
1290
|
const result = migrateOperationDeclarationInternal(readFileSync(sourcePath, "utf8"), relativePath, {
|
|
@@ -1283,6 +1294,10 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
|
|
|
1283
1294
|
operationSites: repositoryIndex.operationSites.get(sourcePath),
|
|
1284
1295
|
excludedBindings: repositoryIndex.excludedBindings.get(sourcePath),
|
|
1285
1296
|
staticObjectResolver: repositoryIndex.staticObjectResolverFor(sourcePath),
|
|
1297
|
+
runtimeComposedInitializers: repositoryIndex.runtimeComposedInitializers.get(sourcePath),
|
|
1298
|
+
recordDiscoveredSites: (count) => {
|
|
1299
|
+
repositoryDiscoveredCount += count;
|
|
1300
|
+
},
|
|
1286
1301
|
});
|
|
1287
1302
|
if (result.status === "refused") {
|
|
1288
1303
|
refusals.push(...result.refusals);
|
|
@@ -1295,7 +1310,18 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
|
|
|
1295
1310
|
todos.push(...result.localeTodos);
|
|
1296
1311
|
}
|
|
1297
1312
|
}
|
|
1298
|
-
|
|
1313
|
+
const notes = repositoryDiscoveredCount === 0
|
|
1314
|
+
? []
|
|
1315
|
+
: repositoryIndex.declarations.flatMap((declaration) => declaration.runtimeInitializer === undefined
|
|
1316
|
+
? []
|
|
1317
|
+
: [
|
|
1318
|
+
{
|
|
1319
|
+
code: "runtime_composed_registry",
|
|
1320
|
+
path: slash(relative(providerRoot, declaration.path)),
|
|
1321
|
+
initializer: declaration.runtimeInitializer,
|
|
1322
|
+
},
|
|
1323
|
+
]);
|
|
1324
|
+
if (repositoryIndex.declarations.length > 0 && repositoryDiscoveredCount === 0) {
|
|
1299
1325
|
for (const declaration of repositoryIndex.declarations) {
|
|
1300
1326
|
refusals.push(refusal(slash(relative(providerRoot, declaration.path)), "<operations>", "no_operations_discovered", `Provider construct ${declaration.construct} declares operations via unresolved initializer ${JSON.stringify(declaration.initializer.getText(declaration.initializer.getSourceFile()))}, but repository-wide discovery found zero operation sites.`));
|
|
1301
1327
|
}
|
|
@@ -1309,6 +1335,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
|
|
|
1309
1335
|
providerRoot,
|
|
1310
1336
|
refusals,
|
|
1311
1337
|
operationCount,
|
|
1338
|
+
notes,
|
|
1312
1339
|
changedFiles,
|
|
1313
1340
|
localeTodoCount: todos.length,
|
|
1314
1341
|
};
|
|
@@ -1322,6 +1349,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
|
|
|
1322
1349
|
providerRoot,
|
|
1323
1350
|
changedFiles: [],
|
|
1324
1351
|
operationCount,
|
|
1352
|
+
notes,
|
|
1325
1353
|
localeTodoCount: 0,
|
|
1326
1354
|
};
|
|
1327
1355
|
}
|
|
@@ -1338,6 +1366,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
|
|
|
1338
1366
|
providerRoot,
|
|
1339
1367
|
changedFiles,
|
|
1340
1368
|
operationCount,
|
|
1369
|
+
notes,
|
|
1341
1370
|
sidecar,
|
|
1342
1371
|
localeTodoCount: todos.length,
|
|
1343
1372
|
};
|
|
@@ -1489,12 +1518,12 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
|
|
|
1489
1518
|
const operationIds = buildOperationIdIndex(sourceFiles);
|
|
1490
1519
|
const operationSites = new Map();
|
|
1491
1520
|
const excludedBindings = new Map();
|
|
1521
|
+
const runtimeComposedInitializers = new Map();
|
|
1492
1522
|
const refusals = [];
|
|
1493
1523
|
const declarations = [];
|
|
1494
1524
|
const indexedProviderProperties = new Set();
|
|
1495
1525
|
const factoryIds = new Map();
|
|
1496
1526
|
const bindingCandidates = new Map();
|
|
1497
|
-
let discoveredCount = 0;
|
|
1498
1527
|
const relativePath = (path) => slash(relative(providerRoot, path));
|
|
1499
1528
|
const resolutionRefusal = (path, operationKey, detail) => {
|
|
1500
1529
|
refusals.push(refusal(relativePath(path), operationKey, "non_literal", detail));
|
|
@@ -1749,7 +1778,6 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
|
|
|
1749
1778
|
}
|
|
1750
1779
|
sites.set(start, operationId);
|
|
1751
1780
|
operationSites.set(path, sites);
|
|
1752
|
-
discoveredCount += 1;
|
|
1753
1781
|
};
|
|
1754
1782
|
const classifyEntry = (entry) => {
|
|
1755
1783
|
const resolved = resolveLocatedExpression(entry.value, new Set());
|
|
@@ -1793,7 +1821,6 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
|
|
|
1793
1821
|
};
|
|
1794
1822
|
factory.ids.add(entry.operationId);
|
|
1795
1823
|
factoryIds.set(key, factory);
|
|
1796
|
-
discoveredCount += 1;
|
|
1797
1824
|
return;
|
|
1798
1825
|
}
|
|
1799
1826
|
resolutionRefusal(resolved.value.path, entry.operationId, `Operation initializer ${JSON.stringify(expression.getText(resolved.value.source))} is not a raw object, operation helper, or simple same-file factory call.`);
|
|
@@ -1803,10 +1830,23 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
|
|
|
1803
1830
|
if (indexedProviderProperties.has(key))
|
|
1804
1831
|
return;
|
|
1805
1832
|
indexedProviderProperties.add(key);
|
|
1806
|
-
|
|
1833
|
+
const resolvedInitializer = resolveLocatedExpression({ path, source, expression: node.initializer }, new Set());
|
|
1834
|
+
let runtimeInitializer;
|
|
1835
|
+
if (resolvedInitializer.status === "resolved") {
|
|
1836
|
+
const resolvedExpression = unwrapExpression(resolvedInitializer.value.expression);
|
|
1837
|
+
if (resolvedExpression !== undefined && ts.isCallExpression(resolvedExpression)) {
|
|
1838
|
+
runtimeInitializer = resolvedExpression.getText(resolvedInitializer.value.source);
|
|
1839
|
+
const initializers = runtimeComposedInitializers.get(path) ?? new Set();
|
|
1840
|
+
initializers.add(node.initializer.getStart(source));
|
|
1841
|
+
runtimeComposedInitializers.set(path, initializers);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
declarations.push({ path, construct, initializer: node.initializer, runtimeInitializer });
|
|
1807
1845
|
const flattened = flattenRegistry({ path, source, expression: node.initializer }, new Set());
|
|
1808
1846
|
if ("detail" in flattened) {
|
|
1809
|
-
|
|
1847
|
+
if (runtimeInitializer === undefined) {
|
|
1848
|
+
resolutionRefusal(path, "<operations>", flattened.detail);
|
|
1849
|
+
}
|
|
1810
1850
|
}
|
|
1811
1851
|
else {
|
|
1812
1852
|
for (const entry of flattened.entries.values())
|
|
@@ -1919,9 +1959,9 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
|
|
|
1919
1959
|
operationIds,
|
|
1920
1960
|
operationSites,
|
|
1921
1961
|
excludedBindings,
|
|
1962
|
+
runtimeComposedInitializers,
|
|
1922
1963
|
refusals,
|
|
1923
1964
|
declarations,
|
|
1924
|
-
discoveredCount,
|
|
1925
1965
|
staticObjectResolverFor: (currentPath) => (expression, source) => resolveStaticObject(expression, sources.has(source.fileName) ? source.fileName : currentPath),
|
|
1926
1966
|
};
|
|
1927
1967
|
}
|
|
@@ -109,8 +109,10 @@ Structured errors return an `error` object with `code`, `message`,
|
|
|
109
109
|
|
|
110
110
|
- `invalid_request`: include `requestId` and `input`; omit `connection` for
|
|
111
111
|
public/no-auth operations and never send `connection: null`.
|
|
112
|
-
- Credentials: declare `credential.keys
|
|
113
|
-
|
|
112
|
+
- Credentials: declare `credential.keys` unless `auth.mode` is `platform-managed`;
|
|
113
|
+
that mode implies an unfiltered credential capability and forbids key declarations.
|
|
114
|
+
Pass local-only values through `connection.secrets` and read them with
|
|
115
|
+
`ctx.credential`.
|
|
114
116
|
- Auth flow: call `/auth/start`, then `/auth/continue` with the same `flowId`;
|
|
115
117
|
carry returned `contextPatch` values into the next request's `context`.
|
|
116
118
|
- Stealth/browser runtime: keep access-sensitive operations on `ctx.stealth.fetch()` with
|
package/dist/define.d.ts
CHANGED
|
@@ -78,6 +78,7 @@ export interface ProviderDeclaration {
|
|
|
78
78
|
secrets?: ProviderSecretDeclaration[];
|
|
79
79
|
/** Declares the environment capability binding. A bare object states use without configuration. */
|
|
80
80
|
env?: Record<string, never> | true;
|
|
81
|
+
/** Declares credential keys; platform-managed auth declares this capability without key filtering. */
|
|
81
82
|
credential?: CredentialDeclaration;
|
|
82
83
|
/** Declares provider context metadata; this does not add a `ctx.context` member. */
|
|
83
84
|
context?: ContextDeclaration;
|
package/dist/engine.js
CHANGED
|
@@ -75,6 +75,8 @@ export const PROVIDER_CAPABILITY_KEYS = [
|
|
|
75
75
|
];
|
|
76
76
|
const CAPABILITY_KEY_SET = new Set(PROVIDER_CAPABILITY_KEYS);
|
|
77
77
|
function declaresCapability(provider, capability) {
|
|
78
|
+
if (capability === "credential" && provider.auth?.mode === "platform-managed")
|
|
79
|
+
return true;
|
|
78
80
|
return Object.hasOwn(provider, capability) && provider[capability] !== undefined;
|
|
79
81
|
}
|
|
80
82
|
function attachmentError(provider, capability) {
|
package/dist/lint.js
CHANGED
|
@@ -246,7 +246,7 @@ function lintAuthModel(provider) {
|
|
|
246
246
|
rule: "platform-managed-no-credential-keys",
|
|
247
247
|
level: "error",
|
|
248
248
|
field: "credential.keys",
|
|
249
|
-
message: `${providerLabel} must not declare credential.keys
|
|
249
|
+
message: `${providerLabel} must not declare credential.keys because platform-managed auth mode already implies the unfiltered credential capability.`,
|
|
250
250
|
});
|
|
251
251
|
}
|
|
252
252
|
const authFlowSource = getAuthFlowSource(provider);
|
package/dist/types.d.ts
CHANGED
|
@@ -1860,8 +1860,9 @@ export interface ProviderRuntimeState {
|
|
|
1860
1860
|
/**
|
|
1861
1861
|
* The operation context exposed for one provider declaration. Capability
|
|
1862
1862
|
* bindings are present only when their corresponding declaration is present;
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1863
|
+
* platform-managed auth itself declares the credential binding. Trace and
|
|
1864
|
+
* request remain ambient runtime bindings. Omitting the type parameter
|
|
1865
|
+
* preserves the legacy full context shape for existing annotations.
|
|
1865
1866
|
*/
|
|
1866
1867
|
export type ProviderContext<TConfig = Record<string, unknown>> = {
|
|
1867
1868
|
request?: ProviderRequestContext;
|
|
@@ -1870,6 +1871,12 @@ export type ProviderContext<TConfig = Record<string, unknown>> = {
|
|
|
1870
1871
|
env: EnvContext;
|
|
1871
1872
|
} : Record<never, never>) & ("credential" extends keyof TConfig ? {
|
|
1872
1873
|
credential: CredentialContext;
|
|
1874
|
+
} : TConfig extends {
|
|
1875
|
+
auth: {
|
|
1876
|
+
mode: "platform-managed";
|
|
1877
|
+
};
|
|
1878
|
+
} ? {
|
|
1879
|
+
credential: CredentialContext;
|
|
1873
1880
|
} : Record<never, never>) & ("http" extends keyof TConfig ? {
|
|
1874
1881
|
http: HttpClient;
|
|
1875
1882
|
} : Record<never, never>) & ("files" extends keyof TConfig ? string extends keyof TConfig ? {
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { makeRegistry } from "./operations/registry";
|
|
2
|
+
|
|
3
|
+
const literalOperation = defineOperation<ProviderContext>()({
|
|
4
|
+
annotations: { readOnly: true },
|
|
5
|
+
input: LiteralInputSchema,
|
|
6
|
+
output: LiteralOutputSchema,
|
|
7
|
+
handler: literalHandler,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const literalProvider = buildProvider({
|
|
11
|
+
operations: { literal: literalOperation },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export default buildProvider({ operations: makeRegistry({ publicOnly: true }) });
|
|
@@ -141,6 +141,8 @@ type RepositoryMigrationContext = {
|
|
|
141
141
|
readonly operationSites?: ReadonlyMap<number, string>;
|
|
142
142
|
readonly excludedBindings?: ReadonlySet<string>;
|
|
143
143
|
readonly staticObjectResolver?: StaticObjectResolver;
|
|
144
|
+
readonly runtimeComposedInitializers?: ReadonlySet<number>;
|
|
145
|
+
readonly recordDiscoveredSites?: (count: number) => void;
|
|
144
146
|
};
|
|
145
147
|
|
|
146
148
|
type OperationSite = {
|
|
@@ -194,7 +196,9 @@ function migrateOperationDeclarationInternal(
|
|
|
194
196
|
options.operationIds,
|
|
195
197
|
context.operationSites,
|
|
196
198
|
context.excludedBindings,
|
|
199
|
+
context.runtimeComposedInitializers,
|
|
197
200
|
);
|
|
201
|
+
context.recordDiscoveredSites?.(discovery.discoveredCount);
|
|
198
202
|
if (discovery.refusals.length > 0) {
|
|
199
203
|
return { status: "refused", refusals: discovery.refusals };
|
|
200
204
|
}
|
|
@@ -1047,18 +1051,27 @@ function discoverOperationSites(
|
|
|
1047
1051
|
operationIds: ReadonlyMap<string, string> | undefined,
|
|
1048
1052
|
operationSites: ReadonlyMap<number, string> | undefined,
|
|
1049
1053
|
excludedBindings: ReadonlySet<string> | undefined,
|
|
1054
|
+
runtimeComposedInitializers: ReadonlySet<number> | undefined,
|
|
1050
1055
|
): {
|
|
1051
1056
|
readonly sites: OperationSite[];
|
|
1057
|
+
readonly discoveredCount: number;
|
|
1052
1058
|
readonly refusals: OperationDeclarationRefusal[];
|
|
1053
1059
|
} {
|
|
1054
1060
|
const sitesByStart = new Map<number, OperationSite>();
|
|
1061
|
+
const discoveredStarts = new Set<number>();
|
|
1055
1062
|
const localIds = new Map(operationIds ?? []);
|
|
1056
1063
|
const localExcludedBindings = new Set(excludedBindings ?? []);
|
|
1057
1064
|
const refusals: OperationDeclarationRefusal[] = [];
|
|
1058
1065
|
const operationFactories = collectSimpleOperationFactories(source);
|
|
1059
1066
|
const factoryCallIds = new Map<string, Set<string>>();
|
|
1060
1067
|
|
|
1061
|
-
for (const map of collectOperationsMaps(
|
|
1068
|
+
for (const map of collectOperationsMaps(
|
|
1069
|
+
source,
|
|
1070
|
+
constObjects,
|
|
1071
|
+
fileName,
|
|
1072
|
+
refusals,
|
|
1073
|
+
runtimeComposedInitializers,
|
|
1074
|
+
)) {
|
|
1062
1075
|
for (const property of map.properties) {
|
|
1063
1076
|
if (ts.isSpreadAssignment(property)) continue;
|
|
1064
1077
|
const key = staticPropertyName(property.name);
|
|
@@ -1145,12 +1158,15 @@ function discoverOperationSites(
|
|
|
1145
1158
|
if (ts.isCallExpression(node) && isOperationHelperCall(node)) {
|
|
1146
1159
|
const argument = operationArgument(node);
|
|
1147
1160
|
const bindingName = enclosingBindingName(node);
|
|
1161
|
+
const unwrappedArgument = unwrapExpression(argument);
|
|
1162
|
+
if (unwrappedArgument !== undefined && ts.isObjectLiteralExpression(unwrappedArgument)) {
|
|
1163
|
+
discoveredStarts.add(unwrappedArgument.getStart(source));
|
|
1164
|
+
}
|
|
1148
1165
|
if (bindingName !== undefined && localExcludedBindings.has(bindingName)) return;
|
|
1149
1166
|
const operationKey =
|
|
1150
1167
|
(bindingName === undefined ? undefined : localIds.get(bindingName)) ??
|
|
1151
1168
|
bindingName ??
|
|
1152
1169
|
"<anonymous>";
|
|
1153
|
-
const unwrappedArgument = unwrapExpression(argument);
|
|
1154
1170
|
if (unwrappedArgument === undefined || !ts.isObjectLiteralExpression(unwrappedArgument)) {
|
|
1155
1171
|
refusals.push(
|
|
1156
1172
|
refusal(
|
|
@@ -1181,10 +1197,12 @@ function discoverOperationSites(
|
|
|
1181
1197
|
ts.forEachChild(node, visit);
|
|
1182
1198
|
};
|
|
1183
1199
|
visit(source);
|
|
1200
|
+
for (const start of sitesByStart.keys()) discoveredStarts.add(start);
|
|
1184
1201
|
return {
|
|
1185
1202
|
sites: [...sitesByStart.values()].sort(
|
|
1186
1203
|
(left, right) => left.object.getStart(source) - right.object.getStart(source),
|
|
1187
1204
|
),
|
|
1205
|
+
discoveredCount: discoveredStarts.size,
|
|
1188
1206
|
refusals,
|
|
1189
1207
|
};
|
|
1190
1208
|
}
|
|
@@ -1223,6 +1241,7 @@ function collectOperationsMaps(
|
|
|
1223
1241
|
constObjects: ReadonlyMap<string, TS.ObjectLiteralExpression>,
|
|
1224
1242
|
fileName: string,
|
|
1225
1243
|
refusals: OperationDeclarationRefusal[],
|
|
1244
|
+
runtimeComposedInitializers?: ReadonlySet<number>,
|
|
1226
1245
|
): TS.ObjectLiteralExpression[] {
|
|
1227
1246
|
const maps = new Map<number, TS.ObjectLiteralExpression>();
|
|
1228
1247
|
const inspect = (expression: TS.Expression, label: string): void => {
|
|
@@ -1238,6 +1257,7 @@ function collectOperationsMaps(
|
|
|
1238
1257
|
return;
|
|
1239
1258
|
}
|
|
1240
1259
|
if (ts.isCallExpression(unwrapped)) {
|
|
1260
|
+
if (runtimeComposedInitializers?.has(expression.getStart(source)) === true) return;
|
|
1241
1261
|
refusals.push(
|
|
1242
1262
|
refusal(
|
|
1243
1263
|
fileName,
|
|
@@ -1893,6 +1913,7 @@ export type OperationDeclarationRepositoryResult =
|
|
|
1893
1913
|
readonly providerRoot: string;
|
|
1894
1914
|
readonly changedFiles: readonly string[];
|
|
1895
1915
|
readonly operationCount: number;
|
|
1916
|
+
readonly notes: readonly OperationDeclarationNote[];
|
|
1896
1917
|
readonly sidecar?: string;
|
|
1897
1918
|
readonly localeTodoCount: number;
|
|
1898
1919
|
}
|
|
@@ -1902,10 +1923,17 @@ export type OperationDeclarationRepositoryResult =
|
|
|
1902
1923
|
readonly refusals: readonly OperationDeclarationRefusal[];
|
|
1903
1924
|
/** Operations that were independently migratable before repository-atomic refusal. */
|
|
1904
1925
|
readonly operationCount: number;
|
|
1926
|
+
readonly notes: readonly OperationDeclarationNote[];
|
|
1905
1927
|
readonly changedFiles: readonly string[];
|
|
1906
1928
|
readonly localeTodoCount: number;
|
|
1907
1929
|
};
|
|
1908
1930
|
|
|
1931
|
+
export type OperationDeclarationNote = {
|
|
1932
|
+
readonly code: "runtime_composed_registry";
|
|
1933
|
+
readonly path: string;
|
|
1934
|
+
readonly initializer: string;
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1909
1937
|
/** Run the file transform repository-wide, committing writes only if every file is provable. */
|
|
1910
1938
|
export function migrateOperationDeclarationRepository(
|
|
1911
1939
|
providerRootInput: string,
|
|
@@ -1920,6 +1948,7 @@ export function migrateOperationDeclarationRepository(
|
|
|
1920
1948
|
const todos: LocaleTodo[] = [];
|
|
1921
1949
|
const refusals: OperationDeclarationRefusal[] = [...repositoryIndex.refusals];
|
|
1922
1950
|
let operationCount = 0;
|
|
1951
|
+
let repositoryDiscoveredCount = 0;
|
|
1923
1952
|
|
|
1924
1953
|
for (const sourcePath of sourceFiles) {
|
|
1925
1954
|
const relativePath = slash(relative(providerRoot, sourcePath));
|
|
@@ -1934,6 +1963,10 @@ export function migrateOperationDeclarationRepository(
|
|
|
1934
1963
|
operationSites: repositoryIndex.operationSites.get(sourcePath),
|
|
1935
1964
|
excludedBindings: repositoryIndex.excludedBindings.get(sourcePath),
|
|
1936
1965
|
staticObjectResolver: repositoryIndex.staticObjectResolverFor(sourcePath),
|
|
1966
|
+
runtimeComposedInitializers: repositoryIndex.runtimeComposedInitializers.get(sourcePath),
|
|
1967
|
+
recordDiscoveredSites: (count) => {
|
|
1968
|
+
repositoryDiscoveredCount += count;
|
|
1969
|
+
},
|
|
1937
1970
|
},
|
|
1938
1971
|
);
|
|
1939
1972
|
if (result.status === "refused") {
|
|
@@ -1947,7 +1980,21 @@ export function migrateOperationDeclarationRepository(
|
|
|
1947
1980
|
todos.push(...result.localeTodos);
|
|
1948
1981
|
}
|
|
1949
1982
|
}
|
|
1950
|
-
|
|
1983
|
+
const notes: OperationDeclarationNote[] =
|
|
1984
|
+
repositoryDiscoveredCount === 0
|
|
1985
|
+
? []
|
|
1986
|
+
: repositoryIndex.declarations.flatMap((declaration) =>
|
|
1987
|
+
declaration.runtimeInitializer === undefined
|
|
1988
|
+
? []
|
|
1989
|
+
: [
|
|
1990
|
+
{
|
|
1991
|
+
code: "runtime_composed_registry" as const,
|
|
1992
|
+
path: slash(relative(providerRoot, declaration.path)),
|
|
1993
|
+
initializer: declaration.runtimeInitializer,
|
|
1994
|
+
},
|
|
1995
|
+
],
|
|
1996
|
+
);
|
|
1997
|
+
if (repositoryIndex.declarations.length > 0 && repositoryDiscoveredCount === 0) {
|
|
1951
1998
|
for (const declaration of repositoryIndex.declarations) {
|
|
1952
1999
|
refusals.push(
|
|
1953
2000
|
refusal(
|
|
@@ -1970,6 +2017,7 @@ export function migrateOperationDeclarationRepository(
|
|
|
1970
2017
|
providerRoot,
|
|
1971
2018
|
refusals,
|
|
1972
2019
|
operationCount,
|
|
2020
|
+
notes,
|
|
1973
2021
|
changedFiles,
|
|
1974
2022
|
localeTodoCount: todos.length,
|
|
1975
2023
|
};
|
|
@@ -1984,6 +2032,7 @@ export function migrateOperationDeclarationRepository(
|
|
|
1984
2032
|
providerRoot,
|
|
1985
2033
|
changedFiles: [],
|
|
1986
2034
|
operationCount,
|
|
2035
|
+
notes,
|
|
1987
2036
|
localeTodoCount: 0,
|
|
1988
2037
|
};
|
|
1989
2038
|
}
|
|
@@ -2000,6 +2049,7 @@ export function migrateOperationDeclarationRepository(
|
|
|
2000
2049
|
providerRoot,
|
|
2001
2050
|
changedFiles,
|
|
2002
2051
|
operationCount,
|
|
2052
|
+
notes,
|
|
2003
2053
|
sidecar,
|
|
2004
2054
|
localeTodoCount: todos.length,
|
|
2005
2055
|
};
|
|
@@ -2180,15 +2230,16 @@ type ProviderOperationsDeclaration = {
|
|
|
2180
2230
|
readonly path: string;
|
|
2181
2231
|
readonly construct: string;
|
|
2182
2232
|
readonly initializer: TS.Expression;
|
|
2233
|
+
readonly runtimeInitializer?: string;
|
|
2183
2234
|
};
|
|
2184
2235
|
|
|
2185
2236
|
type RepositoryOperationIndex = {
|
|
2186
2237
|
readonly operationIds: Map<string, Map<string, string>>;
|
|
2187
2238
|
readonly operationSites: Map<string, Map<number, string>>;
|
|
2188
2239
|
readonly excludedBindings: Map<string, Set<string>>;
|
|
2240
|
+
readonly runtimeComposedInitializers: Map<string, Set<number>>;
|
|
2189
2241
|
readonly refusals: OperationDeclarationRefusal[];
|
|
2190
2242
|
readonly declarations: ProviderOperationsDeclaration[];
|
|
2191
|
-
readonly discoveredCount: number;
|
|
2192
2243
|
readonly staticObjectResolverFor: (path: string) => StaticObjectResolver;
|
|
2193
2244
|
};
|
|
2194
2245
|
|
|
@@ -2205,12 +2256,12 @@ function buildRepositoryOperationIndex(
|
|
|
2205
2256
|
const operationIds = buildOperationIdIndex(sourceFiles);
|
|
2206
2257
|
const operationSites = new Map<string, Map<number, string>>();
|
|
2207
2258
|
const excludedBindings = new Map<string, Set<string>>();
|
|
2259
|
+
const runtimeComposedInitializers = new Map<string, Set<number>>();
|
|
2208
2260
|
const refusals: OperationDeclarationRefusal[] = [];
|
|
2209
2261
|
const declarations: ProviderOperationsDeclaration[] = [];
|
|
2210
2262
|
const indexedProviderProperties = new Set<string>();
|
|
2211
2263
|
const factoryIds = new Map<string, { path: string; name: string; ids: Set<string> }>();
|
|
2212
2264
|
const bindingCandidates = new Map<string, { path: string; name: string; ids: Set<string> }>();
|
|
2213
|
-
let discoveredCount = 0;
|
|
2214
2265
|
|
|
2215
2266
|
const relativePath = (path: string): string => slash(relative(providerRoot, path));
|
|
2216
2267
|
const resolutionRefusal = (path: string, operationKey: string, detail: string): void => {
|
|
@@ -2489,7 +2540,6 @@ function buildRepositoryOperationIndex(
|
|
|
2489
2540
|
}
|
|
2490
2541
|
sites.set(start, operationId);
|
|
2491
2542
|
operationSites.set(path, sites);
|
|
2492
|
-
discoveredCount += 1;
|
|
2493
2543
|
};
|
|
2494
2544
|
|
|
2495
2545
|
const classifyEntry = (entry: RegistryEntry): void => {
|
|
@@ -2543,7 +2593,6 @@ function buildRepositoryOperationIndex(
|
|
|
2543
2593
|
};
|
|
2544
2594
|
factory.ids.add(entry.operationId);
|
|
2545
2595
|
factoryIds.set(key, factory);
|
|
2546
|
-
discoveredCount += 1;
|
|
2547
2596
|
return;
|
|
2548
2597
|
}
|
|
2549
2598
|
resolutionRefusal(
|
|
@@ -2562,10 +2611,26 @@ function buildRepositoryOperationIndex(
|
|
|
2562
2611
|
const key = `${path}\0${node.getStart(source)}`;
|
|
2563
2612
|
if (indexedProviderProperties.has(key)) return;
|
|
2564
2613
|
indexedProviderProperties.add(key);
|
|
2565
|
-
|
|
2614
|
+
const resolvedInitializer = resolveLocatedExpression(
|
|
2615
|
+
{ path, source, expression: node.initializer },
|
|
2616
|
+
new Set(),
|
|
2617
|
+
);
|
|
2618
|
+
let runtimeInitializer: string | undefined;
|
|
2619
|
+
if (resolvedInitializer.status === "resolved") {
|
|
2620
|
+
const resolvedExpression = unwrapExpression(resolvedInitializer.value.expression);
|
|
2621
|
+
if (resolvedExpression !== undefined && ts.isCallExpression(resolvedExpression)) {
|
|
2622
|
+
runtimeInitializer = resolvedExpression.getText(resolvedInitializer.value.source);
|
|
2623
|
+
const initializers = runtimeComposedInitializers.get(path) ?? new Set<number>();
|
|
2624
|
+
initializers.add(node.initializer.getStart(source));
|
|
2625
|
+
runtimeComposedInitializers.set(path, initializers);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
declarations.push({ path, construct, initializer: node.initializer, runtimeInitializer });
|
|
2566
2629
|
const flattened = flattenRegistry({ path, source, expression: node.initializer }, new Set());
|
|
2567
2630
|
if ("detail" in flattened) {
|
|
2568
|
-
|
|
2631
|
+
if (runtimeInitializer === undefined) {
|
|
2632
|
+
resolutionRefusal(path, "<operations>", flattened.detail);
|
|
2633
|
+
}
|
|
2569
2634
|
} else {
|
|
2570
2635
|
for (const entry of flattened.entries.values()) classifyEntry(entry);
|
|
2571
2636
|
}
|
|
@@ -2691,9 +2756,9 @@ function buildRepositoryOperationIndex(
|
|
|
2691
2756
|
operationIds,
|
|
2692
2757
|
operationSites,
|
|
2693
2758
|
excludedBindings,
|
|
2759
|
+
runtimeComposedInitializers,
|
|
2694
2760
|
refusals,
|
|
2695
2761
|
declarations,
|
|
2696
|
-
discoveredCount,
|
|
2697
2762
|
staticObjectResolverFor: (currentPath) => (expression, source) =>
|
|
2698
2763
|
resolveStaticObject(expression, sources.has(source.fileName) ? source.fileName : currentPath),
|
|
2699
2764
|
};
|
|
@@ -109,8 +109,10 @@ Structured errors return an `error` object with `code`, `message`,
|
|
|
109
109
|
|
|
110
110
|
- `invalid_request`: include `requestId` and `input`; omit `connection` for
|
|
111
111
|
public/no-auth operations and never send `connection: null`.
|
|
112
|
-
- Credentials: declare `credential.keys
|
|
113
|
-
|
|
112
|
+
- Credentials: declare `credential.keys` unless `auth.mode` is `platform-managed`;
|
|
113
|
+
that mode implies an unfiltered credential capability and forbids key declarations.
|
|
114
|
+
Pass local-only values through `connection.secrets` and read them with
|
|
115
|
+
`ctx.credential`.
|
|
114
116
|
- Auth flow: call `/auth/start`, then `/auth/continue` with the same `flowId`;
|
|
115
117
|
carry returned `contextPatch` values into the next request's `context`.
|
|
116
118
|
- Stealth/browser runtime: keep access-sensitive operations on `ctx.stealth.fetch()` with
|
package/src/define.ts
CHANGED
|
@@ -600,6 +600,7 @@ export interface ProviderDeclaration {
|
|
|
600
600
|
secrets?: ProviderSecretDeclaration[];
|
|
601
601
|
/** Declares the environment capability binding. A bare object states use without configuration. */
|
|
602
602
|
env?: Record<string, never> | true;
|
|
603
|
+
/** Declares credential keys; platform-managed auth declares this capability without key filtering. */
|
|
603
604
|
credential?: CredentialDeclaration;
|
|
604
605
|
/** Declares provider context metadata; this does not add a `ctx.context` member. */
|
|
605
606
|
context?: ContextDeclaration;
|
package/src/engine.ts
CHANGED
|
@@ -199,6 +199,7 @@ function declaresCapability(
|
|
|
199
199
|
provider: ProviderDefinition,
|
|
200
200
|
capability: ProviderCapabilityKey,
|
|
201
201
|
): boolean {
|
|
202
|
+
if (capability === "credential" && provider.auth?.mode === "platform-managed") return true;
|
|
202
203
|
return Object.hasOwn(provider, capability) && provider[capability] !== undefined;
|
|
203
204
|
}
|
|
204
205
|
|
package/src/lint.ts
CHANGED
|
@@ -398,7 +398,7 @@ function lintAuthModel(provider: {
|
|
|
398
398
|
rule: "platform-managed-no-credential-keys",
|
|
399
399
|
level: "error",
|
|
400
400
|
field: "credential.keys",
|
|
401
|
-
message: `${providerLabel} must not declare credential.keys
|
|
401
|
+
message: `${providerLabel} must not declare credential.keys because platform-managed auth mode already implies the unfiltered credential capability.`,
|
|
402
402
|
});
|
|
403
403
|
}
|
|
404
404
|
|
package/src/types.ts
CHANGED
|
@@ -2266,8 +2266,9 @@ export interface ProviderRuntimeState {
|
|
|
2266
2266
|
/**
|
|
2267
2267
|
* The operation context exposed for one provider declaration. Capability
|
|
2268
2268
|
* bindings are present only when their corresponding declaration is present;
|
|
2269
|
-
*
|
|
2270
|
-
*
|
|
2269
|
+
* platform-managed auth itself declares the credential binding. Trace and
|
|
2270
|
+
* request remain ambient runtime bindings. Omitting the type parameter
|
|
2271
|
+
* preserves the legacy full context shape for existing annotations.
|
|
2271
2272
|
*/
|
|
2272
2273
|
export type ProviderContext<TConfig = Record<string, unknown>> = {
|
|
2273
2274
|
request?: ProviderRequestContext;
|
|
@@ -2276,7 +2277,9 @@ export type ProviderContext<TConfig = Record<string, unknown>> = {
|
|
|
2276
2277
|
& ("env" extends keyof TConfig ? { env: EnvContext } : Record<never, never>)
|
|
2277
2278
|
& ("credential" extends keyof TConfig
|
|
2278
2279
|
? { credential: CredentialContext }
|
|
2279
|
-
:
|
|
2280
|
+
: TConfig extends { auth: { mode: "platform-managed" } }
|
|
2281
|
+
? { credential: CredentialContext }
|
|
2282
|
+
: Record<never, never>)
|
|
2280
2283
|
& ("http" extends keyof TConfig ? { http: HttpClient } : Record<never, never>)
|
|
2281
2284
|
& ("files" extends keyof TConfig
|
|
2282
2285
|
? string extends keyof TConfig
|