@eduwork/dsh-oidc 0.1.0-alpha.11

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 (50) hide show
  1. package/CHANGELOG.en.md +120 -0
  2. package/CHANGELOG.md +120 -0
  3. package/LICENSE +21 -0
  4. package/README.en.md +184 -0
  5. package/README.md +143 -0
  6. package/THIRD_PARTY_NOTICES.en.md +51 -0
  7. package/THIRD_PARTY_NOTICES.md +53 -0
  8. package/cordis.patch.yml +8 -0
  9. package/docs/EDUWORK-MIGRATION.en.md +23 -0
  10. package/docs/EDUWORK-MIGRATION.md +23 -0
  11. package/docs/architecture.en.md +125 -0
  12. package/docs/architecture.md +125 -0
  13. package/docs/compatibility.en.md +61 -0
  14. package/docs/compatibility.md +61 -0
  15. package/docs/decisions/0001-one-plugin-composition-boundary.en.md +34 -0
  16. package/docs/decisions/0001-one-plugin-composition-boundary.md +34 -0
  17. package/docs/dsh-integration.en.md +171 -0
  18. package/docs/dsh-integration.md +171 -0
  19. package/docs/ecnu-reference.en.md +66 -0
  20. package/docs/ecnu-reference.md +66 -0
  21. package/docs/enterprise-profile.en.md +159 -0
  22. package/docs/enterprise-profile.md +159 -0
  23. package/docs/getting-started.en.md +137 -0
  24. package/docs/getting-started.md +239 -0
  25. package/docs/key-binding-protocol.en.md +227 -0
  26. package/docs/key-binding-protocol.md +227 -0
  27. package/docs/oidc-interoperability.en.md +100 -0
  28. package/docs/oidc-interoperability.md +100 -0
  29. package/docs/release-checklist.en.md +60 -0
  30. package/docs/release-checklist.md +60 -0
  31. package/docs/security-model.en.md +138 -0
  32. package/docs/security-model.md +138 -0
  33. package/docs/server-integration-contract.en.md +706 -0
  34. package/docs/server-integration-contract.md +706 -0
  35. package/examples/ecnu.enterprise-profile.example.json +81 -0
  36. package/examples/enterprise-profile.example.json +77 -0
  37. package/lib/client.js +5855 -0
  38. package/lib/index.js +59 -0
  39. package/lib/oidc.js +649 -0
  40. package/lib/profile.js +371 -0
  41. package/lib/provider/core.js +138 -0
  42. package/lib/provider/index.js +156 -0
  43. package/lib/provider/transform-adapter.js +65 -0
  44. package/lib/provider/transforms.js +59 -0
  45. package/lib/typert-schemas.js +84 -0
  46. package/lib/typert.host.js +38 -0
  47. package/lib/typert.remote-client.js +37 -0
  48. package/package.json +120 -0
  49. package/protocol/openapi.yaml +257 -0
  50. package/schema/enterprise-profile.v1alpha1.schema.json +174 -0
@@ -0,0 +1,65 @@
1
+ /** Delegate one enterprise route while applying capabilities contributed by replaceable feature plugins. */
2
+ export class TransformingEnterpriseAdapter {
3
+ constructor(inner, transforms, modelPolicy = () => ({})) {
4
+ this.inner = inner
5
+ this.transforms = transforms
6
+ this.modelPolicy = modelPolicy
7
+ }
8
+
9
+ providerInfo(provider) { return this.inner.providerInfo(provider) }
10
+ providerRetryPolicy(provider) { return this.inner.providerRetryPolicy(provider) }
11
+
12
+ async listModels(provider) {
13
+ const models = await this.inner.listModels(provider)
14
+ return models.map(model => ({
15
+ ...model,
16
+ inputModalities: this.transforms.capabilities(provider, model.id, model.inputModalities),
17
+ }))
18
+ }
19
+
20
+ async resolveModel(provider, model, signal) {
21
+ return this.decorateModel(provider, model, await this.inner.resolveModel(provider, model, signal))
22
+ }
23
+
24
+ decorateModel(provider, model, resolved) {
25
+ const decorated = {
26
+ ...resolved,
27
+ inputModalities: this.transforms.capabilities(provider, model, resolved.inputModalities),
28
+ }
29
+ const policy = this.modelPolicy(provider, model)
30
+ if (policy.reasoning === false || policy.supportsReasoningEffort !== false) return decorated
31
+ const { reasoning: _unsupportedReasoningEfforts, ...thinkingOnly } = decorated
32
+ return thinkingOnly
33
+ }
34
+
35
+ async prepareCall(provider, model, signal) {
36
+ const prepared = typeof this.inner.prepareCall === 'function'
37
+ ? await this.inner.prepareCall(provider, model, signal)
38
+ : { model: await this.inner.resolveModel(provider, model, signal), stream: options => this.inner.stream(options) }
39
+ return {
40
+ model: this.decorateModel(provider, model, prepared.model),
41
+ stream: options => this.streamPrepared(options, prepared),
42
+ }
43
+ }
44
+
45
+ async * streamPrepared(options, prepared) {
46
+ const nativeModelInfo = prepared.model
47
+ let request = options
48
+ const policy = this.modelPolicy(options.provider, options.model)
49
+ if (policy.reasoning === false) {
50
+ request = { ...options, reasoningEffort: 'off' }
51
+ } else if (policy.supportsReasoningEffort === false) {
52
+ const { reasoningEffort: _unsupportedReasoningEffort, ...withoutEffort } = options
53
+ request = withoutEffort
54
+ }
55
+ yield* prepared.stream(await this.transforms.apply(request, nativeModelInfo))
56
+ }
57
+
58
+ async * stream(options) {
59
+ const prepared = typeof this.inner.prepareCall === 'function'
60
+ ? await this.inner.prepareCall(options.provider, options.model, options.signal)
61
+ : { model: await this.inner.resolveModel(options.provider, options.model, options.signal), stream: request => this.inner.stream(request) }
62
+ yield* this.streamPrepared(options, prepared)
63
+ }
64
+ }
65
+
@@ -0,0 +1,59 @@
1
+ import { Service } from '@deepseek-ai/cordis'
2
+
3
+ function routeKey(provider, model) {
4
+ return `${provider}\u0000${model ?? '*'}`
5
+ }
6
+
7
+ function normalizedModalities(inputModalities) {
8
+ return [...new Set(inputModalities ?? [])]
9
+ }
10
+
11
+ /**
12
+ * Stable extension seam for plugins that add a capability to an enterprise
13
+ * route without replacing the route owner or mutating the durable transcript.
14
+ */
15
+ export class EnterpriseModelTransforms extends Service {
16
+ constructor(ctx) {
17
+ super(ctx, 'enterpriseTransforms')
18
+ this.entries = new Map()
19
+ }
20
+
21
+ register({ provider, model, inputModalities = [], when = () => true, transform }) {
22
+ if (typeof provider !== 'string' || provider.length === 0) throw new Error('enterprise transform provider is required')
23
+ if (model !== undefined && (typeof model !== 'string' || model.length === 0)) throw new Error('enterprise transform model must be a non-empty string when provided')
24
+ if (typeof when !== 'function') throw new Error('enterprise transform condition must be a function')
25
+ if (typeof transform !== 'function') throw new Error('enterprise transform must be a function')
26
+ const key = routeKey(provider, model)
27
+ if (this.entries.has(key)) throw new Error(`enterprise transform already registered for ${provider}/${model ?? '*'}`)
28
+ const entry = Object.freeze({ provider, model, inputModalities: normalizedModalities(inputModalities), when, transform })
29
+ this.entries.set(key, entry)
30
+ this.ctx.emit('llm/adapters-updated')
31
+ return () => {
32
+ if (this.entries.get(key) !== entry) return
33
+ this.entries.delete(key)
34
+ this.ctx.emit('llm/adapters-updated')
35
+ }
36
+ }
37
+
38
+ matching(provider, model, nativeInputModalities) {
39
+ const native = normalizedModalities(nativeInputModalities)
40
+ return [
41
+ this.entries.get(routeKey(provider, undefined)),
42
+ this.entries.get(routeKey(provider, model)),
43
+ ].filter(entry => entry !== undefined && entry.when({ provider, model, inputModalities: native }))
44
+ }
45
+
46
+ capabilities(provider, model, inputModalities) {
47
+ const extra = this.matching(provider, model, inputModalities).flatMap(entry => entry.inputModalities)
48
+ return normalizedModalities([...(inputModalities ?? []), ...extra])
49
+ }
50
+
51
+ async apply(options, nativeModelInfo) {
52
+ let transformed = options
53
+ for (const entry of this.matching(options.provider, options.model, nativeModelInfo?.inputModalities)) {
54
+ transformed = await entry.transform(transformed, nativeModelInfo)
55
+ }
56
+ return transformed
57
+ }
58
+ }
59
+
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod'
2
+
3
+ const pkg = '@eduwork/dsh-oidc'
4
+ export const profileIdSchema = z.string().min(1).max(64)
5
+ export const reconcileOptionsSchema = z.object({ allowProvision: z.boolean().optional() }).strict()
6
+ export const baseURLSchema = z.string().min(1).max(2048)
7
+ export const modelCatalogModeSchema = z.enum(['discovery', 'manual'])
8
+
9
+ const brandSchema = z.object({
10
+ productName: z.string().optional(), organizationName: z.string().optional(), mark: z.string().optional(),
11
+ logoURL: z.string().optional(), primaryColor: z.string().optional(), loginTitle: z.string().optional(),
12
+ loginDescription: z.string().optional(), supportURL: z.string().optional(),
13
+ }).strict()
14
+
15
+ const publicProfileSchema = z.object({
16
+ id: z.string(), displayName: z.string(), organization: z.string(), brand: brandSchema,
17
+ credentialRef: z.string(),
18
+ provider: z.object({
19
+ id: z.string(), displayName: z.string(),
20
+ models: z.array(z.object({ id: z.string(), name: z.string(), input: z.array(z.enum(['text', 'image'])) }).strict()),
21
+ }).strict(),
22
+ }).strict()
23
+
24
+ const accountSchema = z.object({
25
+ profileID: z.string(), displayName: z.string(), organization: z.string(), state: z.string(),
26
+ userName: z.string().optional(), affiliation: z.string().optional(), accessExpiresAt: z.string().optional(),
27
+ credentialRef: z.string(), credentialReady: z.boolean(), credentialState: z.string().optional(),
28
+ capabilities: z.array(z.string()).optional(),
29
+ }).strict()
30
+
31
+ export const runtimeModelSchema = z.object({
32
+ id: z.string().min(1).max(256), name: z.string().max(256).optional(),
33
+ upstreamModelID: z.string().max(256).optional(), contextWindow: z.number().int().positive().optional(),
34
+ maxTokens: z.number().int().positive().optional(),
35
+ input: z.array(z.enum(['text', 'image', 'audio', 'video'])).optional(),
36
+ reasoning: z.boolean().optional(), reasoningEfforts: z.record(z.string(), z.string().nullable()).optional(),
37
+ compat: z.record(z.string(), z.unknown()).optional(),
38
+ }).strict()
39
+
40
+ export const runtimeModelsSchema = z.array(runtimeModelSchema).max(128)
41
+
42
+ const managementProfileSchema = z.object({
43
+ id: z.string(), displayName: z.string(), organization: z.string(), baseURL: z.string(),
44
+ builtIn: z.boolean(), configured: z.boolean(), enabled: z.boolean(), providerID: z.string(),
45
+ runtime: z.object({
46
+ displayName: z.string(), modelSource: z.string().optional(), reasoning: z.string().optional(),
47
+ defaultContextWindow: z.number().int().positive().optional(), defaultMaxTokens: z.number().int().positive().optional(),
48
+ compat: z.record(z.string(), z.unknown()).optional(), models: runtimeModelsSchema,
49
+ }).strict(),
50
+ }).strict()
51
+
52
+ export const configurationResult = Object.freeze({
53
+ mode: 'strict', typeSymbol: `${pkg}#Configuration`,
54
+ schema: z.object({ schemaVersion: z.literal('dsh-oidc/v1alpha1'), uiMode: z.enum(['standard', 'models-only', 'external']), profiles: z.array(publicProfileSchema) }).strict(),
55
+ })
56
+
57
+ export const managementResult = Object.freeze({
58
+ mode: 'strict', typeSymbol: `${pkg}#ManagementConfiguration`,
59
+ schema: z.object({
60
+ schemaVersion: z.literal('dsh-oidc/management/v1alpha1'), mode: z.enum(['profile', 'native']),
61
+ activeProfileID: z.string(), restartRequired: z.boolean(),
62
+ capabilities: z.object({ manageProfiles: z.boolean(), manageModels: z.boolean(), restart: z.boolean() }).strict(),
63
+ profiles: z.array(managementProfileSchema),
64
+ }).strict(),
65
+ })
66
+
67
+ export const restartResult = Object.freeze({
68
+ mode: 'strict', typeSymbol: `${pkg}#RestartResult`,
69
+ schema: z.object({ restarting: z.literal(true) }).strict(),
70
+ })
71
+
72
+ export const accountResult = Object.freeze({ mode: 'strict', typeSymbol: `${pkg}#AccountStatus`, schema: accountSchema })
73
+
74
+ export const beginResult = Object.freeze({
75
+ mode: 'strict', typeSymbol: `${pkg}#BeginResult`,
76
+ schema: z.union([
77
+ z.object({ mode: z.literal('redirect'), authorizationURL: z.string().url() }).strict(),
78
+ z.object({ mode: z.literal('completed'), status: accountSchema }).strict(),
79
+ ]),
80
+ })
81
+
82
+ export function jsonParameter(name, schema, typeSymbol) {
83
+ return Object.freeze({ name, wire: name, source: 'json', codec: Object.freeze({ mode: 'strict', typeSymbol, schema }) })
84
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ accountResult, baseURLSchema, beginResult, configurationResult, jsonParameter, managementResult,
3
+ modelCatalogModeSchema, profileIdSchema, reconcileOptionsSchema, restartResult, runtimeModelsSchema,
4
+ } from './typert-schemas.js'
5
+
6
+ const pkg = '@eduwork/dsh-oidc'
7
+ const source = { file: 'lib/index.js', line: 1, column: 1 }
8
+ const profile = () => jsonParameter('profileID', profileIdSchema, `${pkg}#ProfileID`)
9
+ const options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
10
+ const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
11
+ const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
12
+ const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
13
+ const descriptor = (method, parameters, result) => ({
14
+ id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
15
+ invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
16
+ })
17
+
18
+ export const TYPERT = {
19
+ package: pkg, face: 'host', schemas: [],
20
+ invocations: [
21
+ descriptor('configuration', [], configurationResult),
22
+ descriptor('status', [profile()], accountResult),
23
+ descriptor('begin', [profile()], beginResult),
24
+ descriptor('reconcile', [profile(), options()], accountResult),
25
+ descriptor('logout', [profile()], accountResult),
26
+ descriptor('management', [], managementResult),
27
+ descriptor('activate', [profile()], managementResult),
28
+ descriptor('configure', [profile()], managementResult),
29
+ descriptor('addCustom', [baseURL()], managementResult),
30
+ descriptor('updateCustom', [profile(), baseURL()], managementResult),
31
+ descriptor('removeProfile', [profile()], managementResult),
32
+ descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
33
+ descriptor('restart', [], restartResult),
34
+ ],
35
+ model: { services: [], events: [], objects: [] },
36
+ }
37
+
38
+ export default TYPERT
@@ -0,0 +1,37 @@
1
+ import {
2
+ accountResult, baseURLSchema, beginResult, configurationResult, jsonParameter, managementResult,
3
+ modelCatalogModeSchema, profileIdSchema, reconcileOptionsSchema, restartResult, runtimeModelsSchema,
4
+ } from './typert-schemas.js'
5
+
6
+ const pkg = '@eduwork/dsh-oidc'
7
+ const source = { file: 'lib/index.js', line: 1, column: 1 }
8
+ const profile = () => jsonParameter('profileID', profileIdSchema, `${pkg}#ProfileID`)
9
+ const options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
10
+ const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
11
+ const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
12
+ const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
13
+ const descriptor = (method, parameters, result) => ({
14
+ id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
15
+ invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
16
+ })
17
+
18
+ export const TYPERT_REMOTE = {
19
+ package: pkg,
20
+ descriptors: [
21
+ descriptor('configuration', [], configurationResult),
22
+ descriptor('status', [profile()], accountResult),
23
+ descriptor('begin', [profile()], beginResult),
24
+ descriptor('reconcile', [profile(), options()], accountResult),
25
+ descriptor('logout', [profile()], accountResult),
26
+ descriptor('management', [], managementResult),
27
+ descriptor('activate', [profile()], managementResult),
28
+ descriptor('configure', [profile()], managementResult),
29
+ descriptor('addCustom', [baseURL()], managementResult),
30
+ descriptor('updateCustom', [profile(), baseURL()], managementResult),
31
+ descriptor('removeProfile', [profile()], managementResult),
32
+ descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
33
+ descriptor('restart', [], restartResult),
34
+ ],
35
+ }
36
+
37
+ export default TYPERT_REMOTE
package/package.json ADDED
@@ -0,0 +1,120 @@
1
+ {
2
+ "name": "@eduwork/dsh-oidc",
3
+ "version": "0.1.0-alpha.11",
4
+ "description": "Standards-based OIDC identity, enterprise API-key binding, declarative branding, and OpenAI-compatible model integration for DeepSeek Harness",
5
+ "keywords": [
6
+ "deepseek-harness",
7
+ "dsh",
8
+ "dsh-plugin",
9
+ "oidc",
10
+ "openid-connect",
11
+ "enterprise",
12
+ "sso"
13
+ ],
14
+ "type": "module",
15
+ "main": "lib/index.js",
16
+ "exports": {
17
+ ".": "./lib/index.js",
18
+ "./client": "./lib/client.js",
19
+ "./profile": "./lib/profile.js",
20
+ "./oidc": "./lib/oidc.js",
21
+ "./provider": "./lib/provider/index.js",
22
+ "./transforms": "./lib/provider/transforms.js",
23
+ "./typert": "./lib/typert.host.js",
24
+ "./remote": "./lib/typert.remote-client.js",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "cordis.patch.yml",
29
+ "lib/**/*.js",
30
+ "docs/**/*.md",
31
+ "examples/**/*.json",
32
+ "schema/**/*.json",
33
+ "protocol/openapi.yaml",
34
+ "README.md",
35
+ "README.en.md",
36
+ "LICENSE",
37
+ "THIRD_PARTY_NOTICES.md",
38
+ "THIRD_PARTY_NOTICES.en.md",
39
+ "CHANGELOG.md",
40
+ "CHANGELOG.en.md"
41
+ ],
42
+ "dsh": {
43
+ "bundle": {
44
+ "patch": "./cordis.patch.yml"
45
+ },
46
+ "client": {
47
+ "inject": [
48
+ "@deepseek-ai/dsh-client-ui-renderer",
49
+ "@deepseek-ai/dsh-api-remotes",
50
+ "@deepseek-ai/dsh-client-ui-conversation",
51
+ "@deepseek-ai/dsh-client-ui-settings",
52
+ "@deepseek-ai/dsh-client-ui-settings-models",
53
+ "@deepseek-ai/dsh-client-ui-sidebar",
54
+ "@deepseek-ai/dsh-client-ui-theme"
55
+ ],
56
+ "platform": "web",
57
+ "immediately": true
58
+ }
59
+ },
60
+ "scripts": {
61
+ "build:host": "node scripts/build-host.mjs",
62
+ "build:client": "tsdown",
63
+ "build": "npm run build:host && npm run build:client",
64
+ "typecheck": "tsc --noEmit",
65
+ "test": "node --test",
66
+ "test:coverage": "node --test --experimental-test-coverage",
67
+ "check:schemas": "node scripts/check-schemas.mjs",
68
+ "check:secrets": "node scripts/check-secrets.mjs",
69
+ "check:licenses": "node scripts/check-licenses.mjs",
70
+ "check:docs": "node scripts/check-doc-links.mjs",
71
+ "check:build": "node scripts/smoke-build.mjs",
72
+ "check": "npm run typecheck && npm run build && npm run check:build && npm test && npm run check:schemas && npm run check:docs && npm run check:secrets && npm run check:licenses && npm pack --dry-run",
73
+ "prepare": "npm run build"
74
+ },
75
+ "engines": {
76
+ "node": ">=22.0.0"
77
+ },
78
+ "publishConfig": {
79
+ "access": "public",
80
+ "provenance": true
81
+ },
82
+ "repository": {
83
+ "type": "git",
84
+ "url": "git+https://github.com/freedomkk-qfeng/dsh-oidc.git"
85
+ },
86
+ "bugs": {
87
+ "url": "https://github.com/freedomkk-qfeng/dsh-oidc/issues"
88
+ },
89
+ "homepage": "https://github.com/freedomkk-qfeng/dsh-oidc#readme",
90
+ "license": "MIT",
91
+ "dependencies": {
92
+ "zod": "4.4.3"
93
+ },
94
+ "peerDependencies": {
95
+ "@deepseek-ai/cordis": "4.0.2",
96
+ "@deepseek-ai/dsh-api-remotes": "0.1.2-alpha.2",
97
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.2-alpha.2",
98
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.2",
99
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.2",
100
+ "@deepseek-ai/dsh-client-ui-settings-models": "0.1.2-alpha.2",
101
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.2-alpha.2",
102
+ "@deepseek-ai/dsh-client-ui-theme": "0.1.2-alpha.2",
103
+ "@deepseek-ai/dsh-credentials": "0.1.2-alpha.2",
104
+ "@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.2",
105
+ "@deepseek-ai/dsh-launch-environment": "0.1.2-alpha.2",
106
+ "@deepseek-ai/dsh-llm": "0.1.2-alpha.2",
107
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.2-alpha.2",
108
+ "@deepseek-ai/dsh-settings": "0.1.2-alpha.2",
109
+ "@deepseek-ai/dsh-typert-protocol": "0.1.2-alpha.2",
110
+ "@earendil-works/pi-ai": "^0.84.2",
111
+ "react": "^18.2.0"
112
+ },
113
+ "devDependencies": {
114
+ "@types/react": "^18.3.28",
115
+ "ajv": "^8.17.1",
116
+ "tsdown": "0.22.14",
117
+ "typescript": "^5.9.3",
118
+ "yaml": "^2.8.1"
119
+ }
120
+ }
@@ -0,0 +1,257 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: dsh-oidc Worker User Center Key Binding API
4
+ version: 1.0.0
5
+ description: >-
6
+ The fixed worker-user-center-v1 protocol used by dsh-oidc to exchange an
7
+ OIDC Bearer access token for a short-lived or revocable model runtime
8
+ credential. Endpoint paths and wire field names are deliberately not
9
+ configurable. See docs/key-binding-protocol.md for normative requirements.
10
+ license:
11
+ name: MIT
12
+ identifier: MIT
13
+ servers:
14
+ - url: https://ai.example.edu/api/worker/v1
15
+ description: Example baseURL from an Enterprise Profile
16
+ security:
17
+ - oidcAccessToken: []
18
+ paths:
19
+ /bootstrap:
20
+ get:
21
+ operationId: bootstrap
22
+ summary: Read subject-independent integration state and credential lifecycle state
23
+ responses:
24
+ '200':
25
+ description: Current provider and runtime credential state
26
+ headers:
27
+ Cache-Control:
28
+ schema: { type: string }
29
+ description: MUST contain no-store
30
+ content:
31
+ application/json:
32
+ schema: { $ref: '#/components/schemas/BootstrapResponse' }
33
+ '401': { $ref: '#/components/responses/Unauthorized' }
34
+ '403': { $ref: '#/components/responses/Forbidden' }
35
+ '429': { $ref: '#/components/responses/RateLimited' }
36
+ '5XX': { $ref: '#/components/responses/ServerError' }
37
+ /runtime-credential/provision:
38
+ post:
39
+ operationId: provisionRuntimeCredential
40
+ summary: Provision and return a runtime credential for the authenticated subject
41
+ parameters:
42
+ - $ref: '#/components/parameters/IdempotencyKey'
43
+ requestBody:
44
+ required: true
45
+ content:
46
+ application/json:
47
+ schema: { $ref: '#/components/schemas/RuntimeCredentialRequest' }
48
+ responses:
49
+ '200': { $ref: '#/components/responses/RuntimeCredential' }
50
+ '201': { $ref: '#/components/responses/RuntimeCredential' }
51
+ '400': { $ref: '#/components/responses/BadRequest' }
52
+ '401': { $ref: '#/components/responses/Unauthorized' }
53
+ '403': { $ref: '#/components/responses/Forbidden' }
54
+ '409': { $ref: '#/components/responses/Conflict' }
55
+ '429': { $ref: '#/components/responses/RateLimited' }
56
+ '5XX': { $ref: '#/components/responses/ServerError' }
57
+ /runtime-credential/resolve:
58
+ post:
59
+ operationId: resolveRuntimeCredential
60
+ summary: Resolve the active runtime credential for the authenticated subject
61
+ requestBody:
62
+ required: true
63
+ content:
64
+ application/json:
65
+ schema: { $ref: '#/components/schemas/RuntimeCredentialRequest' }
66
+ responses:
67
+ '200': { $ref: '#/components/responses/RuntimeCredential' }
68
+ '400': { $ref: '#/components/responses/BadRequest' }
69
+ '401': { $ref: '#/components/responses/Unauthorized' }
70
+ '403': { $ref: '#/components/responses/Forbidden' }
71
+ '404': { $ref: '#/components/responses/NotFound' }
72
+ '429': { $ref: '#/components/responses/RateLimited' }
73
+ '5XX': { $ref: '#/components/responses/ServerError' }
74
+ /runtime-credential/renew:
75
+ post:
76
+ operationId: renewRuntimeCredential
77
+ summary: Rotate an expiring or expired runtime credential
78
+ parameters:
79
+ - $ref: '#/components/parameters/IdempotencyKey'
80
+ requestBody:
81
+ required: true
82
+ content:
83
+ application/json:
84
+ schema:
85
+ allOf:
86
+ - $ref: '#/components/schemas/RuntimeCredentialRequest'
87
+ - type: object
88
+ required: [api_key_id]
89
+ responses:
90
+ '200': { $ref: '#/components/responses/RuntimeCredential' }
91
+ '201': { $ref: '#/components/responses/RuntimeCredential' }
92
+ '400': { $ref: '#/components/responses/BadRequest' }
93
+ '401': { $ref: '#/components/responses/Unauthorized' }
94
+ '403': { $ref: '#/components/responses/Forbidden' }
95
+ '404': { $ref: '#/components/responses/NotFound' }
96
+ '409': { $ref: '#/components/responses/Conflict' }
97
+ '429': { $ref: '#/components/responses/RateLimited' }
98
+ '5XX': { $ref: '#/components/responses/ServerError' }
99
+ components:
100
+ securitySchemes:
101
+ oidcAccessToken:
102
+ type: http
103
+ scheme: bearer
104
+ bearerFormat: JWT or opaque OIDC access token
105
+ description: >-
106
+ Access token issued to the public OIDC client declared in the matching
107
+ Enterprise Profile. The server validates issuer, audience, expiry,
108
+ required scopes, and the authenticated subject.
109
+ parameters:
110
+ IdempotencyKey:
111
+ name: Idempotency-Key
112
+ in: header
113
+ required: true
114
+ description: Opaque UUID generated for one logical mutating operation.
115
+ schema:
116
+ type: string
117
+ format: uuid
118
+ schemas:
119
+ BootstrapResponse:
120
+ type: object
121
+ additionalProperties: false
122
+ required: [provider, capabilities, runtime_credential]
123
+ properties:
124
+ protocol_version:
125
+ type: string
126
+ const: worker-user-center/v1
127
+ description: Optional during v1 adoption; if present it MUST equal this value.
128
+ subject:
129
+ $ref: '#/components/schemas/InformationalSubject'
130
+ provider:
131
+ type: object
132
+ additionalProperties: false
133
+ required: [id]
134
+ properties:
135
+ id:
136
+ type: string
137
+ pattern: '^[a-z][a-z0-9-]{0,63}$'
138
+ capabilities:
139
+ type: array
140
+ maxItems: 256
141
+ uniqueItems: true
142
+ items: { type: string, minLength: 1, maxLength: 128 }
143
+ runtime_credential:
144
+ $ref: '#/components/schemas/RuntimeCredentialState'
145
+ InformationalSubject:
146
+ type: object
147
+ additionalProperties: true
148
+ description: >-
149
+ Optional management-plane display data. dsh-oidc MUST NOT use it to
150
+ replace the identity established by the OIDC ID Token and UserInfo.
151
+ properties:
152
+ sub: { type: string }
153
+ name: { type: string }
154
+ affiliation: { type: string }
155
+ RuntimeCredentialState:
156
+ type: object
157
+ additionalProperties: false
158
+ required: [status]
159
+ properties:
160
+ status:
161
+ enum: [missing, active, expiring, expired, suspended, unavailable]
162
+ api_key_id:
163
+ type: string
164
+ minLength: 1
165
+ maxLength: 256
166
+ expires_at:
167
+ type: string
168
+ format: date-time
169
+ provisioning:
170
+ type: object
171
+ additionalProperties: false
172
+ properties:
173
+ allowed: { type: boolean }
174
+ reason: { type: string, maxLength: 500 }
175
+ RuntimeCredentialRequest:
176
+ type: object
177
+ additionalProperties: false
178
+ required: [provider_id]
179
+ properties:
180
+ provider_id:
181
+ type: string
182
+ pattern: '^[a-z][a-z0-9-]{0,63}$'
183
+ api_key_id:
184
+ type: string
185
+ minLength: 1
186
+ maxLength: 256
187
+ RuntimeCredentialResponse:
188
+ type: object
189
+ additionalProperties: false
190
+ required: [provider_id, api_key, status]
191
+ properties:
192
+ provider_id:
193
+ type: string
194
+ pattern: '^[a-z][a-z0-9-]{0,63}$'
195
+ api_key:
196
+ type: string
197
+ minLength: 1
198
+ maxLength: 16384
199
+ description: Secret value. It MUST appear only in this TLS response and MUST NOT be logged.
200
+ api_key_id:
201
+ type: string
202
+ minLength: 1
203
+ maxLength: 256
204
+ status:
205
+ const: active
206
+ expires_at:
207
+ type: string
208
+ format: date-time
209
+ Problem:
210
+ type: object
211
+ additionalProperties: true
212
+ required: [type, title, status]
213
+ properties:
214
+ type: { type: string, format: uri-reference }
215
+ title: { type: string }
216
+ status: { type: integer, minimum: 400, maximum: 599 }
217
+ detail: { type: string }
218
+ instance: { type: string, format: uri-reference }
219
+ code: { type: string, description: Stable machine-readable service code }
220
+ responses:
221
+ RuntimeCredential:
222
+ description: Active runtime credential
223
+ headers:
224
+ Cache-Control:
225
+ schema: { type: string }
226
+ description: MUST contain no-store
227
+ Pragma:
228
+ schema: { type: string }
229
+ description: SHOULD be no-cache for compatibility
230
+ content:
231
+ application/json:
232
+ schema: { $ref: '#/components/schemas/RuntimeCredentialResponse' }
233
+ BadRequest:
234
+ description: Invalid request
235
+ content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
236
+ Unauthorized:
237
+ description: Missing, invalid, expired, or wrong-audience access token
238
+ headers:
239
+ WWW-Authenticate: { schema: { type: string } }
240
+ content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
241
+ Forbidden:
242
+ description: Authenticated subject lacks the required scope or organization entitlement
243
+ content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
244
+ NotFound:
245
+ description: Requested runtime credential does not exist
246
+ content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
247
+ Conflict:
248
+ description: Idempotency conflict or incompatible credential lifecycle state
249
+ content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
250
+ RateLimited:
251
+ description: Rate limit exceeded
252
+ headers:
253
+ Retry-After: { schema: { type: string } }
254
+ content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
255
+ ServerError:
256
+ description: Server-side failure with no secret material
257
+ content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }