@xemahq/kernel-contracts 0.32.0 → 0.34.0
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/dist/app-runtime/index.d.ts +3 -0
- package/dist/app-runtime/index.d.ts.map +1 -1
- package/dist/app-runtime/index.js +3 -0
- package/dist/app-runtime/index.js.map +1 -1
- package/dist/app-runtime/lib/app-channel.d.ts +7 -0
- package/dist/app-runtime/lib/app-channel.d.ts.map +1 -0
- package/dist/app-runtime/lib/app-channel.js +11 -0
- package/dist/app-runtime/lib/app-channel.js.map +1 -0
- package/dist/app-runtime/lib/app-release.d.ts +24 -0
- package/dist/app-runtime/lib/app-release.d.ts.map +1 -0
- package/dist/app-runtime/lib/app-release.js +29 -0
- package/dist/app-runtime/lib/app-release.js.map +1 -0
- package/dist/app-runtime/lib/app.d.ts +3 -0
- package/dist/app-runtime/lib/app.d.ts.map +1 -1
- package/dist/app-runtime/lib/app.js +19 -4
- package/dist/app-runtime/lib/app.js.map +1 -1
- package/dist/app-runtime/lib/branding-config.d.ts.map +1 -1
- package/dist/app-runtime/lib/branding-config.js.map +1 -1
- package/dist/app-runtime/lib/delegated-session.js +1 -1
- package/dist/app-runtime/lib/delegated-session.js.map +1 -1
- package/dist/app-runtime/lib/surface-kind.d.ts +8 -0
- package/dist/app-runtime/lib/surface-kind.d.ts.map +1 -0
- package/dist/app-runtime/lib/surface-kind.js +12 -0
- package/dist/app-runtime/lib/surface-kind.js.map +1 -0
- package/dist/capability/index.d.ts +1 -0
- package/dist/capability/index.d.ts.map +1 -1
- package/dist/capability/index.js +1 -0
- package/dist/capability/index.js.map +1 -1
- package/dist/capability/lib/first-party-capability-domains.d.ts +29 -0
- package/dist/capability/lib/first-party-capability-domains.d.ts.map +1 -0
- package/dist/capability/lib/first-party-capability-domains.js +35 -0
- package/dist/capability/lib/first-party-capability-domains.js.map +1 -0
- package/dist/connector/index.d.ts +1 -0
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -0
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/lib/connector-adapter-kind.d.ts +1 -0
- package/dist/connector/lib/connector-adapter-kind.d.ts.map +1 -1
- package/dist/connector/lib/connector-adapter-kind.js +1 -0
- package/dist/connector/lib/connector-adapter-kind.js.map +1 -1
- package/dist/connector/lib/connector-descriptor.d.ts +29 -1
- package/dist/connector/lib/connector-descriptor.d.ts.map +1 -1
- package/dist/connector/lib/connector-descriptor.js +22 -1
- package/dist/connector/lib/connector-descriptor.js.map +1 -1
- package/dist/connector/lib/connector-http-request.d.ts +56 -0
- package/dist/connector/lib/connector-http-request.d.ts.map +1 -0
- package/dist/connector/lib/connector-http-request.js +86 -0
- package/dist/connector/lib/connector-http-request.js.map +1 -0
- package/dist/connector/lib/integration-provider-contribution.d.ts +17 -1
- package/dist/connector/lib/integration-provider-contribution.d.ts.map +1 -1
- package/dist/connector/lib/integration-provider-contribution.js +11 -0
- package/dist/connector/lib/integration-provider-contribution.js.map +1 -1
- package/dist/connector/lib/provider-descriptor.d.ts +3 -0
- package/dist/connector/lib/provider-descriptor.d.ts.map +1 -1
- package/dist/connector/lib/provider-descriptor.js +1 -0
- package/dist/connector/lib/provider-descriptor.js.map +1 -1
- package/dist/contribution/lib/contribution-kind.d.ts.map +1 -1
- package/dist/contribution/lib/contribution-kind.js.map +1 -1
- package/dist/principal/index.d.ts +6 -0
- package/dist/principal/index.d.ts.map +1 -0
- package/dist/principal/index.js +22 -0
- package/dist/principal/index.js.map +1 -0
- package/dist/principal/lib/app-invocation-identity.d.ts +22 -0
- package/dist/principal/lib/app-invocation-identity.d.ts.map +1 -0
- package/dist/principal/lib/app-invocation-identity.js +36 -0
- package/dist/principal/lib/app-invocation-identity.js.map +1 -0
- package/dist/principal/lib/clamp-authority.d.ts +4 -0
- package/dist/principal/lib/clamp-authority.d.ts.map +1 -0
- package/dist/principal/lib/clamp-authority.js +23 -0
- package/dist/principal/lib/clamp-authority.js.map +1 -0
- package/dist/principal/lib/delegation-chain.d.ts +26 -0
- package/dist/principal/lib/delegation-chain.d.ts.map +1 -0
- package/dist/principal/lib/delegation-chain.js +81 -0
- package/dist/principal/lib/delegation-chain.js.map +1 -0
- package/dist/principal/lib/delegation-grant.d.ts +10 -0
- package/dist/principal/lib/delegation-grant.d.ts.map +1 -0
- package/dist/principal/lib/delegation-grant.js +12 -0
- package/dist/principal/lib/delegation-grant.js.map +1 -0
- package/dist/principal/lib/execution-principal.d.ts +20 -0
- package/dist/principal/lib/execution-principal.d.ts.map +1 -0
- package/dist/principal/lib/execution-principal.js +22 -0
- package/dist/principal/lib/execution-principal.js.map +1 -0
- package/package.json +1 -1
- package/src/app-runtime/index.ts +3 -0
- package/src/app-runtime/lib/app-channel.ts +32 -0
- package/src/app-runtime/lib/app-release.ts +90 -0
- package/src/app-runtime/lib/app.ts +48 -17
- package/src/app-runtime/lib/branding-config.ts +12 -6
- package/src/app-runtime/lib/delegated-session.ts +2 -2
- package/src/app-runtime/lib/surface-kind.ts +30 -0
- package/src/capability/index.ts +1 -0
- package/src/capability/lib/first-party-capability-domains.ts +76 -0
- package/src/connector/index.ts +1 -0
- package/src/connector/lib/connector-adapter-kind.ts +9 -1
- package/src/connector/lib/connector-descriptor.ts +44 -8
- package/src/connector/lib/connector-http-request.ts +187 -0
- package/src/connector/lib/integration-provider-contribution.ts +21 -0
- package/src/connector/lib/provider-descriptor.ts +10 -0
- package/src/contribution/lib/contribution-kind.ts +111 -19
- package/src/principal/index.ts +5 -0
- package/src/principal/lib/app-invocation-identity.ts +95 -0
- package/src/principal/lib/clamp-authority.ts +64 -0
- package/src/principal/lib/delegation-chain.ts +145 -0
- package/src/principal/lib/delegation-grant.ts +36 -0
- package/src/principal/lib/execution-principal.ts +74 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
3
|
+
* Declarative HTTP request template — the DATA face of a connector operation
|
|
4
|
+
*
|
|
5
|
+
* A connector operation whose `ConnectorAdapterKind` is
|
|
6
|
+
* {@link ConnectorAdapterKind.DeclarativeHttp} carries this template instead of
|
|
7
|
+
* a compiled code module. It is everything a GENERIC executor needs to turn a
|
|
8
|
+
* capability invocation into a single outbound HTTP call:
|
|
9
|
+
*
|
|
10
|
+
* - `method` + `pathTemplate` (joined onto the provider's `apiBaseUrl`),
|
|
11
|
+
* - `{placeholder}` substitution from the invocation input (deterministic,
|
|
12
|
+
* fail-fast on a missing placeholder — never a guess),
|
|
13
|
+
* - static/placeholder `query` + `headers`,
|
|
14
|
+
* - a `bodyMode` selecting whether the (placeholder-stripped) input becomes a
|
|
15
|
+
* JSON body,
|
|
16
|
+
* - an {@link ConnectorAuthInjection} describing WHERE the resolved
|
|
17
|
+
* credential is injected (the credential VALUE is never here — it is
|
|
18
|
+
* resolved at execution time through the credential-broker custody seam).
|
|
19
|
+
*
|
|
20
|
+
* This is the "third-party ships a signed connector biome with a descriptor and
|
|
21
|
+
* it works with NO sidecar code" fast-path. Code adapters remain the escape
|
|
22
|
+
* hatch (a different adapter kind, hosted by `biome-adapter-host`).
|
|
23
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The HTTP verb a declarative operation issues. Closed set — a connector
|
|
30
|
+
* operation is exactly one request.
|
|
31
|
+
*/
|
|
32
|
+
export enum ConnectorHttpMethod {
|
|
33
|
+
Get = 'GET',
|
|
34
|
+
Post = 'POST',
|
|
35
|
+
Put = 'PUT',
|
|
36
|
+
Patch = 'PATCH',
|
|
37
|
+
Delete = 'DELETE',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Zod mirror of {@link ConnectorHttpMethod}. */
|
|
41
|
+
export const ConnectorHttpMethodSchema = z.nativeEnum(ConnectorHttpMethod);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* How the generic executor injects the resolved connection credential into the
|
|
45
|
+
* outbound request. The credential VALUE comes from the custody seam at
|
|
46
|
+
* execution time; this only says WHERE it goes. Closed set.
|
|
47
|
+
*/
|
|
48
|
+
export enum ConnectorAuthInjectionKind {
|
|
49
|
+
/** `Authorization: Bearer <accessToken>`. */
|
|
50
|
+
BearerHeader = 'bearer_header',
|
|
51
|
+
/** `Authorization: Basic <accessToken>` (the token is already base64 `user:pass`). */
|
|
52
|
+
BasicHeader = 'basic_header',
|
|
53
|
+
/** `<headerName>: <valuePrefix?><accessToken>` — e.g. `X-Api-Key: <token>`. */
|
|
54
|
+
Header = 'header',
|
|
55
|
+
/** `?<queryParamName>=<accessToken>` — e.g. `?api_key=<token>`. */
|
|
56
|
+
QueryParam = 'query_param',
|
|
57
|
+
/**
|
|
58
|
+
* No credential injected — a public operation. Only valid when the operation
|
|
59
|
+
* declares `requiresConnection: false`; the executor asserts the pairing.
|
|
60
|
+
*/
|
|
61
|
+
None = 'none',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Zod mirror of {@link ConnectorAuthInjectionKind}. */
|
|
65
|
+
export const ConnectorAuthInjectionKindSchema = z.nativeEnum(
|
|
66
|
+
ConnectorAuthInjectionKind,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether the (placeholder-stripped) invocation input becomes an outbound
|
|
71
|
+
* request body. Closed set — kept minimal; a richer body-mapping language is a
|
|
72
|
+
* future additive member, never a free-form template string.
|
|
73
|
+
*/
|
|
74
|
+
export enum ConnectorHttpBodyMode {
|
|
75
|
+
/** No request body (typical for GET/DELETE). */
|
|
76
|
+
None = 'none',
|
|
77
|
+
/**
|
|
78
|
+
* The invocation input object (minus reserved/tenant keys and minus keys
|
|
79
|
+
* consumed as path/query/header placeholders) is sent as a JSON body.
|
|
80
|
+
*/
|
|
81
|
+
JsonInput = 'json_input',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Zod mirror of {@link ConnectorHttpBodyMode}. */
|
|
85
|
+
export const ConnectorHttpBodyModeSchema = z.nativeEnum(ConnectorHttpBodyMode);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Where the resolved credential is injected into the outbound request. `kind`
|
|
89
|
+
* selects the injection style; the extra fields are required only for the
|
|
90
|
+
* styles that need them (validated cross-field, fail-fast).
|
|
91
|
+
*/
|
|
92
|
+
export interface ConnectorAuthInjection {
|
|
93
|
+
readonly kind: ConnectorAuthInjectionKind;
|
|
94
|
+
/** Header name — REQUIRED for `Header`, forbidden otherwise. */
|
|
95
|
+
readonly headerName?: string;
|
|
96
|
+
/** Optional value prefix for `Header` (e.g. `token ` → `token <secret>`). */
|
|
97
|
+
readonly valuePrefix?: string;
|
|
98
|
+
/** Query-param name — REQUIRED for `QueryParam`, forbidden otherwise. */
|
|
99
|
+
readonly queryParamName?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Zod mirror of {@link ConnectorAuthInjection}. Strict + cross-field: the
|
|
104
|
+
* placement-specific field MUST be present for its kind and absent for others,
|
|
105
|
+
* so a malformed injection can never be silently under-specified.
|
|
106
|
+
*/
|
|
107
|
+
export const ConnectorAuthInjectionSchema = z
|
|
108
|
+
.strictObject({
|
|
109
|
+
kind: ConnectorAuthInjectionKindSchema,
|
|
110
|
+
headerName: z.string().min(1).optional(),
|
|
111
|
+
valuePrefix: z.string().optional(),
|
|
112
|
+
queryParamName: z.string().min(1).optional(),
|
|
113
|
+
})
|
|
114
|
+
.superRefine((value, ctx) => {
|
|
115
|
+
if (value.kind === ConnectorAuthInjectionKind.Header) {
|
|
116
|
+
if (value.headerName === undefined) {
|
|
117
|
+
ctx.addIssue({
|
|
118
|
+
code: 'custom',
|
|
119
|
+
path: ['headerName'],
|
|
120
|
+
message: 'auth.kind="header" requires headerName.',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
} else if (value.headerName !== undefined) {
|
|
124
|
+
ctx.addIssue({
|
|
125
|
+
code: 'custom',
|
|
126
|
+
path: ['headerName'],
|
|
127
|
+
message: `headerName is only valid for auth.kind="header" (got "${value.kind}").`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
value.kind !== ConnectorAuthInjectionKind.Header &&
|
|
132
|
+
value.valuePrefix !== undefined
|
|
133
|
+
) {
|
|
134
|
+
ctx.addIssue({
|
|
135
|
+
code: 'custom',
|
|
136
|
+
path: ['valuePrefix'],
|
|
137
|
+
message: `valuePrefix is only valid for auth.kind="header" (got "${value.kind}").`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (value.kind === ConnectorAuthInjectionKind.QueryParam) {
|
|
141
|
+
if (value.queryParamName === undefined) {
|
|
142
|
+
ctx.addIssue({
|
|
143
|
+
code: 'custom',
|
|
144
|
+
path: ['queryParamName'],
|
|
145
|
+
message: 'auth.kind="query_param" requires queryParamName.',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
} else if (value.queryParamName !== undefined) {
|
|
149
|
+
ctx.addIssue({
|
|
150
|
+
code: 'custom',
|
|
151
|
+
path: ['queryParamName'],
|
|
152
|
+
message: `queryParamName is only valid for auth.kind="query_param" (got "${value.kind}").`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A single declarative HTTP request. `pathTemplate` is joined onto the
|
|
159
|
+
* provider's `apiBaseUrl` at execution time. `pathTemplate`, `query` values and
|
|
160
|
+
* `headers` values may contain `{name}` placeholders substituted from the
|
|
161
|
+
* invocation input — a placeholder with no matching input key is a fail-fast
|
|
162
|
+
* error at execution, never a silent empty string.
|
|
163
|
+
*/
|
|
164
|
+
export interface ConnectorHttpRequestTemplate {
|
|
165
|
+
readonly method: ConnectorHttpMethod;
|
|
166
|
+
/** Path (joined onto provider `apiBaseUrl`); MUST start with `/`. */
|
|
167
|
+
readonly pathTemplate: string;
|
|
168
|
+
/** Static or `{placeholder}` query params. */
|
|
169
|
+
readonly query?: Readonly<Record<string, string>>;
|
|
170
|
+
/** Static or `{placeholder}` request headers (never the auth header — that is `auth`). */
|
|
171
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
172
|
+
readonly bodyMode: ConnectorHttpBodyMode;
|
|
173
|
+
readonly auth: ConnectorAuthInjection;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Zod mirror of {@link ConnectorHttpRequestTemplate} — value-identical to the
|
|
178
|
+
* interface. Strict: unknown keys fail fast.
|
|
179
|
+
*/
|
|
180
|
+
export const ConnectorHttpRequestTemplateSchema = z.strictObject({
|
|
181
|
+
method: ConnectorHttpMethodSchema,
|
|
182
|
+
pathTemplate: z.string().min(1).startsWith('/'),
|
|
183
|
+
query: z.record(z.string(), z.string()).readonly().optional(),
|
|
184
|
+
headers: z.record(z.string(), z.string()).readonly().optional(),
|
|
185
|
+
bodyMode: ConnectorHttpBodyModeSchema,
|
|
186
|
+
auth: ConnectorAuthInjectionSchema,
|
|
187
|
+
});
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { z } from 'zod';
|
|
26
26
|
|
|
27
|
+
import { ConnectorAdapterKind } from './connector-adapter-kind';
|
|
27
28
|
import { ConnectorDescriptorSchema } from './connector-descriptor';
|
|
28
29
|
import {
|
|
29
30
|
ProviderDescriptorBaseSchema,
|
|
@@ -111,6 +112,26 @@ export const IntegrationProviderContributionManifestSchema = z
|
|
|
111
112
|
}
|
|
112
113
|
seenConnectorKeys.add(connector.connectorKey);
|
|
113
114
|
});
|
|
115
|
+
|
|
116
|
+
// A declarative-http operation is executed by joining its pathTemplate onto
|
|
117
|
+
// the provider's apiBaseUrl. If ANY connector ships one, the provider MUST
|
|
118
|
+
// declare apiBaseUrl — otherwise the generic executor has no base URL and
|
|
119
|
+
// would fail at invocation time. Enforce it at ingest instead (fail-fast).
|
|
120
|
+
const hasDeclarativeOperation = manifest.connectors.some((connector) =>
|
|
121
|
+
(connector.operations ?? []).some(
|
|
122
|
+
(op) => op.adapterKind === ConnectorAdapterKind.DeclarativeHttp,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
if (hasDeclarativeOperation && manifest.provider.apiBaseUrl === undefined) {
|
|
126
|
+
ctx.addIssue({
|
|
127
|
+
code: 'custom',
|
|
128
|
+
path: ['provider', 'apiBaseUrl'],
|
|
129
|
+
message:
|
|
130
|
+
`Provider "${manifest.provider.providerKey}" ships a declarative-http ` +
|
|
131
|
+
'connector operation but declares no apiBaseUrl — the generic ' +
|
|
132
|
+
'executor joins every operation path onto it, so it is required.',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
114
135
|
});
|
|
115
136
|
|
|
116
137
|
export type IntegrationProviderContributionManifest = z.infer<
|
|
@@ -147,6 +147,15 @@ export interface ProviderDescriptor {
|
|
|
147
147
|
/** Catalog grouping label (e.g. `Email`, `Source control`, `Chat`). */
|
|
148
148
|
readonly category: string;
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Base URL every `declarative-http` operation under this provider is joined
|
|
152
|
+
* onto (e.g. `https://api.hubspot.com`). REQUIRED when any connector under
|
|
153
|
+
* this provider declares a `declarative-http` operation (enforced at the
|
|
154
|
+
* contribution boundary); absent for code / MCP / mail providers. Never
|
|
155
|
+
* carries a trailing slash by convention — the executor joins defensively.
|
|
156
|
+
*/
|
|
157
|
+
readonly apiBaseUrl?: string;
|
|
158
|
+
|
|
150
159
|
/** How the shared app credential authenticates. */
|
|
151
160
|
readonly appAuthKind: ProviderAppAuthKind;
|
|
152
161
|
/** Who shipped the provider — drives requirement scope + which admin fills. */
|
|
@@ -201,6 +210,7 @@ export const ProviderDescriptorBaseSchema = z.strictObject({
|
|
|
201
210
|
description: z.string().min(1),
|
|
202
211
|
iconName: z.string().min(1),
|
|
203
212
|
category: z.string().min(1),
|
|
213
|
+
apiBaseUrl: z.string().url().optional(),
|
|
204
214
|
appAuthKind: z.nativeEnum(ProviderAppAuthKind),
|
|
205
215
|
origin: z.nativeEnum(ProviderOrigin),
|
|
206
216
|
appCredentialSchema: z.array(CredentialFieldSpecSchema).readonly().optional(),
|
|
@@ -2,31 +2,93 @@ import { z } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `ContributionKind` is the closed enum that names every kind of thing a biome
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* may contribute to the Xema OS through the Contribution Protocol (XSI plane 2).
|
|
6
|
+
* Extending it is a one-line PR to the kernel.
|
|
7
7
|
*
|
|
8
|
-
* The enum
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
8
|
+
* The enum is a CATALOG, not a promise that biome-host parses every member.
|
|
9
|
+
* Each member falls into exactly ONE ingestion category, stated by its
|
|
10
|
+
* per-member doc-comment:
|
|
11
|
+
*
|
|
12
|
+
* 1. biome-host `KindParser` — discovered from a biome's `contributions/`
|
|
13
|
+
* directory by `biome-host-api` and forwarded to the owning service via
|
|
14
|
+
* the contribution-sync outbox. This is the majority; see
|
|
15
|
+
* `biome-host-api/src/contributions/parsers/`.
|
|
16
|
+
* 2. Domain-service handler / first-class self-registration — ingested by a
|
|
17
|
+
* service OTHER than biome-host, through its own
|
|
18
|
+
* `BootstrapContributionsService` handler or a kernel registry adapter:
|
|
19
|
+
* • `Capability`, `ResourceOwnership`, `SearchType` — kernel-owned
|
|
20
|
+
* manifest schema, validated fail-fast by BOTH the biome-host parser
|
|
21
|
+
* and the owning domain service.
|
|
22
|
+
* • `StageMachine`, `ArtifactType`, `InquiryKind` — host-owned manifest
|
|
23
|
+
* schema, biome-host parses and forwards to the owning host.
|
|
24
|
+
* • `ModelResolutionDimension` — `llm-registry-api`'s
|
|
25
|
+
* `ModelStrategyBootstrapService`, from kernel-shipped strategy YAML;
|
|
26
|
+
* NO biome-host parser.
|
|
27
|
+
* • `MountSource` — the kernel `@xemahq/backend-slot-registry-adapters`
|
|
28
|
+
* adapter projecting the `MountSourceKindRegistry`; NO biome-host
|
|
29
|
+
* parser.
|
|
30
|
+
* 3. RESERVED — declared for protocol/catalog completeness but with NO
|
|
31
|
+
* contribution-protocol ingestion path today. Where the underlying feature
|
|
32
|
+
* exists it ships via the convention CONTENT-DIRECTORY seeder (on-disk
|
|
33
|
+
* presence — see the manifest reference), NOT the enum-dispatched
|
|
34
|
+
* contribution protocol; a few members have no delivery path at all yet.
|
|
35
|
+
* Do NOT assume a biome-host parser exists for a RESERVED member, and do
|
|
36
|
+
* not ship a biome contribution of that kind expecting it to be ingested.
|
|
37
|
+
*
|
|
38
|
+
* This doc-comment is kept HONEST by review: a member with no biome-host parser
|
|
39
|
+
* MUST be annotated with its true category (2 or 3). An earlier revision falsely
|
|
40
|
+
* asserted "the enum equals the SUPPORTED set: every member is handled by a
|
|
41
|
+
* parser or is a first-class self-registration kind" and that decorative
|
|
42
|
+
* members "were culled in the H7 pass" — neither was true (13 members were
|
|
43
|
+
* parser-less). Adding a member is a one-line PR, but it MUST arrive with a
|
|
44
|
+
* parser OR an honest category-2/3 note.
|
|
16
45
|
*
|
|
17
46
|
* Enum string values match the slug spoken by the rest of the protocol
|
|
18
47
|
* (Capability References, manifest declarations) so that round-tripping
|
|
19
|
-
* between the enum and the wire is lossless.
|
|
48
|
+
* between the enum and the wire is lossless. The values themselves are a WIRE
|
|
49
|
+
* CONTRACT — never rename or delete a member without a migration plan.
|
|
20
50
|
*/
|
|
21
51
|
export enum ContributionKind {
|
|
22
52
|
// -- v1 seed ---------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* Category 2 (domain-service handler, NOT biome-host). Ingested by the kernel
|
|
55
|
+
* `@xemahq/backend-slot-registry-adapters` adapter
|
|
56
|
+
* (`adaptMountSourceRegistryIntoSlots`), which projects the existing
|
|
57
|
+
* `MountSourceKindRegistry` into the `BackendSlotRegistry`. That registry
|
|
58
|
+
* stays the authoritative owner and read path; there is NO biome-host parser.
|
|
59
|
+
*/
|
|
23
60
|
MountSource = 'mount-source',
|
|
61
|
+
/**
|
|
62
|
+
* RESERVED (category 3). No contribution-protocol parser. MCP tool
|
|
63
|
+
* descriptors ship today via the convention `mcpTools/` content directory
|
|
64
|
+
* (on-disk presence seeder). The enum-dispatched contribution path for MCP is
|
|
65
|
+
* `McpCatalog`, which HAS a parser — do not expect one here.
|
|
66
|
+
*/
|
|
24
67
|
McpTool = 'mcp-tool',
|
|
25
68
|
WorkflowStep = 'workflow-step',
|
|
69
|
+
/**
|
|
70
|
+
* RESERVED (category 3). No ingestion path today — no parser, no
|
|
71
|
+
* content-directory seeder, no domain-service handler. The only in-tree use
|
|
72
|
+
* is a deliberate PLACEHOLDER reuse of this value in the kernel
|
|
73
|
+
* `inquiry-kind-adapter` (its own comment flags it as a placeholder, not an
|
|
74
|
+
* equivalence). A biome contribution of this kind is NOT ingested.
|
|
75
|
+
*/
|
|
26
76
|
GateAction = 'gate-action',
|
|
77
|
+
/**
|
|
78
|
+
* RESERVED (category 3). No ingestion path today; referenced only as a worked
|
|
79
|
+
* "a third party adds a new kind" example in the contribution test-suite.
|
|
80
|
+
*/
|
|
27
81
|
ChartRuntime = 'chart-runtime',
|
|
28
82
|
AgentSkill = 'agent-skill',
|
|
29
83
|
AgentKernel = 'agent-kernel',
|
|
84
|
+
/**
|
|
85
|
+
* Category 2 (domain-service handler, NOT biome-host). Ingested by
|
|
86
|
+
* `llm-registry-api`'s `ModelStrategyBootstrapService` — a
|
|
87
|
+
* `BootstrapContributionsService` handler that loads the kernel-shipped
|
|
88
|
+
* `strategies/*.yaml` and upserts model-resolution strategies. No biome-host
|
|
89
|
+
* parser; the manifest shape is service-local (`StrategyFileSchema`), not a
|
|
90
|
+
* kernel-owned contribution schema.
|
|
91
|
+
*/
|
|
30
92
|
ModelResolutionDimension = 'model-resolution-dimension',
|
|
31
93
|
WidgetKind = 'widget-kind',
|
|
32
94
|
|
|
@@ -54,17 +116,34 @@ export enum ContributionKind {
|
|
|
54
116
|
|
|
55
117
|
/** A role capability bundle contributed by a biome. */
|
|
56
118
|
RoleCapability = 'role-capability',
|
|
57
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* RESERVED (category 3). No contribution-protocol parser. The install-wizard
|
|
121
|
+
* resource-selection schema ships via the convention `biomeInstallSchema/`
|
|
122
|
+
* content directory (on-disk presence seeder), not the enum-dispatched path.
|
|
123
|
+
*/
|
|
58
124
|
BiomeInstallSchema = 'biome-install-schema',
|
|
59
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* RESERVED (category 3). No parser. Icon assets ship via the convention
|
|
127
|
+
* `icons/` content directory (on-disk presence seeder).
|
|
128
|
+
*/
|
|
60
129
|
Icon = 'icon',
|
|
61
130
|
/** A project-kit bundle contributed by a biome. */
|
|
62
131
|
ProjectKit = 'project-kit',
|
|
63
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* RESERVED (category 3). No parser. Provisioning scaffolds ship via the
|
|
134
|
+
* convention `provisioning/` content directory (declared in
|
|
135
|
+
* `xema.provisioning[]`, parity-checked at boot), not the contribution path.
|
|
136
|
+
*/
|
|
64
137
|
ProvisioningScaffold = 'provisioning-scaffold',
|
|
65
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* RESERVED (category 3). No ingestion path today — no parser, no
|
|
140
|
+
* content-directory seeder, no domain-service handler.
|
|
141
|
+
*/
|
|
66
142
|
WorkspaceSpecOverlay = 'workspace-spec-overlay',
|
|
67
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* RESERVED (category 3). No ingestion path today — no parser, no
|
|
145
|
+
* content-directory seeder, no domain-service handler.
|
|
146
|
+
*/
|
|
68
147
|
SystemOverlayContribution = 'system-overlay-contribution',
|
|
69
148
|
/** A concrete integration provider contributed by a biome inside an
|
|
70
149
|
* adapter kind. */
|
|
@@ -77,7 +156,11 @@ export enum ContributionKind {
|
|
|
77
156
|
DeliverableSpec = 'deliverable-spec',
|
|
78
157
|
/** A workspace manifest contributed by a biome. */
|
|
79
158
|
WorkspaceManifest = 'workspace-manifest',
|
|
80
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* RESERVED (category 3). No parser (distinct from `WorkspaceManifest`, which
|
|
161
|
+
* HAS one). Reusable workspace-manifest templates ship via the convention
|
|
162
|
+
* `workspaceManifestTemplates/` content directory (on-disk presence seeder).
|
|
163
|
+
*/
|
|
81
164
|
WorkspaceManifestTemplate = 'workspace-manifest-template',
|
|
82
165
|
/** A tool-profile contributed by a biome. */
|
|
83
166
|
ToolProfile = 'tool-profile',
|
|
@@ -85,9 +168,18 @@ export enum ContributionKind {
|
|
|
85
168
|
* which is a single tool descriptor; an `McpCatalog` entry is a catalog
|
|
86
169
|
* manifest. */
|
|
87
170
|
McpCatalog = 'mcp-catalog',
|
|
88
|
-
/**
|
|
171
|
+
/**
|
|
172
|
+
* RESERVED (category 3). No contribution-protocol parser. Agent-runtime
|
|
173
|
+
* custom tools ship via the convention `openCodeTools/` content directory,
|
|
174
|
+
* baked into the opencode-worker image at build time — not the
|
|
175
|
+
* enum-dispatched contribution path.
|
|
176
|
+
*/
|
|
89
177
|
OpenCodeTool = 'opencode-tool',
|
|
90
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* RESERVED (category 3). No parser — the contribution-registrar explicitly
|
|
180
|
+
* does NOT route this kind. Agent-runtime plugins ship via the convention
|
|
181
|
+
* `openCodePlugins/` content directory, baked into the opencode-worker image.
|
|
182
|
+
*/
|
|
91
183
|
OpenCodePlugin = 'opencode-plugin',
|
|
92
184
|
// -- Capability self-registration ------------------------------------------
|
|
93
185
|
/**
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
SubjectKind,
|
|
4
|
+
SubjectRefSchema,
|
|
5
|
+
type SubjectRef,
|
|
6
|
+
} from '../../subject';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The two ways an app invocation names its `actor`.
|
|
10
|
+
*
|
|
11
|
+
* `AppInvocationIdentity` is SUGAR over the `ExecutionPrincipal.actor` field —
|
|
12
|
+
* it resolves to exactly one `SubjectRef` (via `resolveInvocationActor`). It
|
|
13
|
+
* adds no authority of its own; it only says WHOSE authority the app is
|
|
14
|
+
* exercising.
|
|
15
|
+
*
|
|
16
|
+
* **`AsApp` is not a bypass.** A scheduled app runs as `executor = app:<id>`
|
|
17
|
+
* WITH `actor = app:<id>` — the app IS the actor, acting purely as itself. It
|
|
18
|
+
* must hold an explicit app role IN ITS OWN app to do anything; there is no
|
|
19
|
+
* ambient "the scheduler said so" authority. A scheduled job does NOT skip row
|
|
20
|
+
* policy and does NOT skip field policy. `AsApp` narrows the app to its own
|
|
21
|
+
* granted authority exactly like any other principal — running unattended buys
|
|
22
|
+
* it nothing that running on behalf of a user would not have to earn through the
|
|
23
|
+
* same `clampAuthority` intersection.
|
|
24
|
+
*/
|
|
25
|
+
export enum AppInvocationMode {
|
|
26
|
+
/** The app acts on behalf of a signed-in user; `actor` is that user. */
|
|
27
|
+
OnBehalfOfUser = 'on-behalf-of-user',
|
|
28
|
+
/** The app acts as itself (e.g. a schedule); `actor === executor === app:<id>`. */
|
|
29
|
+
AsApp = 'as-app',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const AppInvocationModeSchema = z.nativeEnum(AppInvocationMode);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `OnBehalfOfUser` — the app is exercising a specific user's authority. `user`
|
|
36
|
+
* becomes `ExecutionPrincipal.actor`; the app is the `executor`.
|
|
37
|
+
*/
|
|
38
|
+
export interface OnBehalfOfUserIdentity {
|
|
39
|
+
mode: AppInvocationMode.OnBehalfOfUser;
|
|
40
|
+
/** The signed-in subject whose authority the app exercises. */
|
|
41
|
+
user: SubjectRef;
|
|
42
|
+
/** The app doing the acting (becomes `executor`). */
|
|
43
|
+
appId: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* `AsApp` — the app acts as itself. `actor` and `executor` are BOTH
|
|
48
|
+
* `app:<appId>`; see the `AppInvocationMode` docblock — this is not a bypass.
|
|
49
|
+
*/
|
|
50
|
+
export interface AsAppIdentity {
|
|
51
|
+
mode: AppInvocationMode.AsApp;
|
|
52
|
+
/** The app acting as its own actor and executor. */
|
|
53
|
+
appId: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type AppInvocationIdentity = OnBehalfOfUserIdentity | AsAppIdentity;
|
|
57
|
+
|
|
58
|
+
const onBehalfOfUserShape = z.object({
|
|
59
|
+
mode: z.literal(AppInvocationMode.OnBehalfOfUser),
|
|
60
|
+
user: SubjectRefSchema,
|
|
61
|
+
appId: z.string().min(1),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const asAppShape = z.object({
|
|
65
|
+
mode: z.literal(AppInvocationMode.AsApp),
|
|
66
|
+
appId: z.string().min(1),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const OnBehalfOfUserIdentitySchema =
|
|
70
|
+
onBehalfOfUserShape as z.ZodType<OnBehalfOfUserIdentity>;
|
|
71
|
+
|
|
72
|
+
export const AsAppIdentitySchema = asAppShape as z.ZodType<AsAppIdentity>;
|
|
73
|
+
|
|
74
|
+
export const AppInvocationIdentitySchema = z.discriminatedUnion('mode', [
|
|
75
|
+
onBehalfOfUserShape,
|
|
76
|
+
asAppShape,
|
|
77
|
+
]) as z.ZodType<AppInvocationIdentity>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve an `AppInvocationIdentity` to the `ExecutionPrincipal.actor` it is
|
|
81
|
+
* sugar for. `OnBehalfOfUser` → the named user. `AsApp` → the app itself
|
|
82
|
+
* (`app:<appId>`), which is ALSO the executor — an app acting as itself has no
|
|
83
|
+
* elevated authority (see the `AppInvocationMode` docblock). Total over the
|
|
84
|
+
* closed union; a new mode is a compile error here, never a silent default.
|
|
85
|
+
*/
|
|
86
|
+
export function resolveInvocationActor(
|
|
87
|
+
identity: AppInvocationIdentity,
|
|
88
|
+
): SubjectRef {
|
|
89
|
+
switch (identity.mode) {
|
|
90
|
+
case AppInvocationMode.OnBehalfOfUser:
|
|
91
|
+
return identity.user;
|
|
92
|
+
case AppInvocationMode.AsApp:
|
|
93
|
+
return { kind: SubjectKind.App, id: identity.appId };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CapabilityRef } from '../../capability';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `clampAuthority` — the delegation intersection rule (plan D6).
|
|
5
|
+
*
|
|
6
|
+
* effective(P) = grants(P.executor) ∩ effective(P.actor)
|
|
7
|
+
*
|
|
8
|
+
* The effective authority of a principal is the INTERSECTION of what the
|
|
9
|
+
* executor is granted with what the actor already effectively holds. It is
|
|
10
|
+
* NEVER a union: a delegate can only ever NARROW authority, never widen it. An
|
|
11
|
+
* app can never do more than its caller.
|
|
12
|
+
*
|
|
13
|
+
* Structurally it cannot widen, and this is the load-bearing property: every
|
|
14
|
+
* element of the returned set is drawn from `executorGrants` AND must also be
|
|
15
|
+
* present in `actorEffective`, so the result is a subset of BOTH inputs
|
|
16
|
+
* (`clamp(A, B) ⊆ A` and `clamp(A, B) ⊆ B`). There is no code path that adds a
|
|
17
|
+
* ref present in only one operand.
|
|
18
|
+
*
|
|
19
|
+
* An EMPTY intersection is the default and correct outcome — not an error, not a
|
|
20
|
+
* signal to fall back to either operand. A principal whose executor shares no
|
|
21
|
+
* capability with its actor is authorized for nothing, and that denial is the
|
|
22
|
+
* whole point (it is THE test that must exist before the Wave 10 function
|
|
23
|
+
* sandbox: a delegate holding a capability its actor lacks is denied).
|
|
24
|
+
*
|
|
25
|
+
* Pure and side-effect free. Inputs are read-only sets of `CapabilityRef`; the
|
|
26
|
+
* result is a fresh set (the smaller operand is iterated for efficiency, but the
|
|
27
|
+
* membership rule is symmetric so the result is identical either way).
|
|
28
|
+
*/
|
|
29
|
+
export function clampAuthority(
|
|
30
|
+
executorGrants: ReadonlySet<CapabilityRef>,
|
|
31
|
+
actorEffective: ReadonlySet<CapabilityRef>,
|
|
32
|
+
): Set<CapabilityRef> {
|
|
33
|
+
const [smaller, larger] =
|
|
34
|
+
executorGrants.size <= actorEffective.size
|
|
35
|
+
? [executorGrants, actorEffective]
|
|
36
|
+
: [actorEffective, executorGrants];
|
|
37
|
+
|
|
38
|
+
const intersection = new Set<CapabilityRef>();
|
|
39
|
+
for (const ref of smaller) {
|
|
40
|
+
if (larger.has(ref)) {
|
|
41
|
+
intersection.add(ref);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return intersection;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Fold `clampAuthority` down a whole chain of authority sets, in order.
|
|
49
|
+
* `clampAuthorityChain([grants(executor), effective(actor), …])` reduces every
|
|
50
|
+
* operand by intersection, so ANY empty operand collapses the whole result to
|
|
51
|
+
* empty. Every operand narrows; none widens. An empty input array is itself an
|
|
52
|
+
* empty authority set (nothing to be authorized for) — never a wildcard.
|
|
53
|
+
*/
|
|
54
|
+
export function clampAuthorityChain(
|
|
55
|
+
authoritySets: ReadonlyArray<ReadonlySet<CapabilityRef>>,
|
|
56
|
+
): Set<CapabilityRef> {
|
|
57
|
+
if (authoritySets.length === 0) {
|
|
58
|
+
return new Set<CapabilityRef>();
|
|
59
|
+
}
|
|
60
|
+
return authoritySets.reduce<Set<CapabilityRef>>(
|
|
61
|
+
(acc, next) => clampAuthority(acc, next),
|
|
62
|
+
new Set<CapabilityRef>(authoritySets[0]),
|
|
63
|
+
);
|
|
64
|
+
}
|