@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.
Files changed (105) hide show
  1. package/dist/app-runtime/index.d.ts +3 -0
  2. package/dist/app-runtime/index.d.ts.map +1 -1
  3. package/dist/app-runtime/index.js +3 -0
  4. package/dist/app-runtime/index.js.map +1 -1
  5. package/dist/app-runtime/lib/app-channel.d.ts +7 -0
  6. package/dist/app-runtime/lib/app-channel.d.ts.map +1 -0
  7. package/dist/app-runtime/lib/app-channel.js +11 -0
  8. package/dist/app-runtime/lib/app-channel.js.map +1 -0
  9. package/dist/app-runtime/lib/app-release.d.ts +24 -0
  10. package/dist/app-runtime/lib/app-release.d.ts.map +1 -0
  11. package/dist/app-runtime/lib/app-release.js +29 -0
  12. package/dist/app-runtime/lib/app-release.js.map +1 -0
  13. package/dist/app-runtime/lib/app.d.ts +3 -0
  14. package/dist/app-runtime/lib/app.d.ts.map +1 -1
  15. package/dist/app-runtime/lib/app.js +19 -4
  16. package/dist/app-runtime/lib/app.js.map +1 -1
  17. package/dist/app-runtime/lib/branding-config.d.ts.map +1 -1
  18. package/dist/app-runtime/lib/branding-config.js.map +1 -1
  19. package/dist/app-runtime/lib/delegated-session.js +1 -1
  20. package/dist/app-runtime/lib/delegated-session.js.map +1 -1
  21. package/dist/app-runtime/lib/surface-kind.d.ts +8 -0
  22. package/dist/app-runtime/lib/surface-kind.d.ts.map +1 -0
  23. package/dist/app-runtime/lib/surface-kind.js +12 -0
  24. package/dist/app-runtime/lib/surface-kind.js.map +1 -0
  25. package/dist/capability/index.d.ts +1 -0
  26. package/dist/capability/index.d.ts.map +1 -1
  27. package/dist/capability/index.js +1 -0
  28. package/dist/capability/index.js.map +1 -1
  29. package/dist/capability/lib/first-party-capability-domains.d.ts +29 -0
  30. package/dist/capability/lib/first-party-capability-domains.d.ts.map +1 -0
  31. package/dist/capability/lib/first-party-capability-domains.js +35 -0
  32. package/dist/capability/lib/first-party-capability-domains.js.map +1 -0
  33. package/dist/connector/index.d.ts +1 -0
  34. package/dist/connector/index.d.ts.map +1 -1
  35. package/dist/connector/index.js +1 -0
  36. package/dist/connector/index.js.map +1 -1
  37. package/dist/connector/lib/connector-adapter-kind.d.ts +1 -0
  38. package/dist/connector/lib/connector-adapter-kind.d.ts.map +1 -1
  39. package/dist/connector/lib/connector-adapter-kind.js +1 -0
  40. package/dist/connector/lib/connector-adapter-kind.js.map +1 -1
  41. package/dist/connector/lib/connector-descriptor.d.ts +29 -1
  42. package/dist/connector/lib/connector-descriptor.d.ts.map +1 -1
  43. package/dist/connector/lib/connector-descriptor.js +22 -1
  44. package/dist/connector/lib/connector-descriptor.js.map +1 -1
  45. package/dist/connector/lib/connector-http-request.d.ts +56 -0
  46. package/dist/connector/lib/connector-http-request.d.ts.map +1 -0
  47. package/dist/connector/lib/connector-http-request.js +86 -0
  48. package/dist/connector/lib/connector-http-request.js.map +1 -0
  49. package/dist/connector/lib/integration-provider-contribution.d.ts +17 -1
  50. package/dist/connector/lib/integration-provider-contribution.d.ts.map +1 -1
  51. package/dist/connector/lib/integration-provider-contribution.js +11 -0
  52. package/dist/connector/lib/integration-provider-contribution.js.map +1 -1
  53. package/dist/connector/lib/provider-descriptor.d.ts +3 -0
  54. package/dist/connector/lib/provider-descriptor.d.ts.map +1 -1
  55. package/dist/connector/lib/provider-descriptor.js +1 -0
  56. package/dist/connector/lib/provider-descriptor.js.map +1 -1
  57. package/dist/contribution/lib/contribution-kind.d.ts.map +1 -1
  58. package/dist/contribution/lib/contribution-kind.js.map +1 -1
  59. package/dist/principal/index.d.ts +6 -0
  60. package/dist/principal/index.d.ts.map +1 -0
  61. package/dist/principal/index.js +22 -0
  62. package/dist/principal/index.js.map +1 -0
  63. package/dist/principal/lib/app-invocation-identity.d.ts +22 -0
  64. package/dist/principal/lib/app-invocation-identity.d.ts.map +1 -0
  65. package/dist/principal/lib/app-invocation-identity.js +36 -0
  66. package/dist/principal/lib/app-invocation-identity.js.map +1 -0
  67. package/dist/principal/lib/clamp-authority.d.ts +4 -0
  68. package/dist/principal/lib/clamp-authority.d.ts.map +1 -0
  69. package/dist/principal/lib/clamp-authority.js +23 -0
  70. package/dist/principal/lib/clamp-authority.js.map +1 -0
  71. package/dist/principal/lib/delegation-chain.d.ts +26 -0
  72. package/dist/principal/lib/delegation-chain.d.ts.map +1 -0
  73. package/dist/principal/lib/delegation-chain.js +81 -0
  74. package/dist/principal/lib/delegation-chain.js.map +1 -0
  75. package/dist/principal/lib/delegation-grant.d.ts +10 -0
  76. package/dist/principal/lib/delegation-grant.d.ts.map +1 -0
  77. package/dist/principal/lib/delegation-grant.js +12 -0
  78. package/dist/principal/lib/delegation-grant.js.map +1 -0
  79. package/dist/principal/lib/execution-principal.d.ts +20 -0
  80. package/dist/principal/lib/execution-principal.d.ts.map +1 -0
  81. package/dist/principal/lib/execution-principal.js +22 -0
  82. package/dist/principal/lib/execution-principal.js.map +1 -0
  83. package/package.json +1 -1
  84. package/src/app-runtime/index.ts +3 -0
  85. package/src/app-runtime/lib/app-channel.ts +32 -0
  86. package/src/app-runtime/lib/app-release.ts +90 -0
  87. package/src/app-runtime/lib/app.ts +48 -17
  88. package/src/app-runtime/lib/branding-config.ts +12 -6
  89. package/src/app-runtime/lib/delegated-session.ts +2 -2
  90. package/src/app-runtime/lib/surface-kind.ts +30 -0
  91. package/src/capability/index.ts +1 -0
  92. package/src/capability/lib/first-party-capability-domains.ts +76 -0
  93. package/src/connector/index.ts +1 -0
  94. package/src/connector/lib/connector-adapter-kind.ts +9 -1
  95. package/src/connector/lib/connector-descriptor.ts +44 -8
  96. package/src/connector/lib/connector-http-request.ts +187 -0
  97. package/src/connector/lib/integration-provider-contribution.ts +21 -0
  98. package/src/connector/lib/provider-descriptor.ts +10 -0
  99. package/src/contribution/lib/contribution-kind.ts +111 -19
  100. package/src/principal/index.ts +5 -0
  101. package/src/principal/lib/app-invocation-identity.ts +95 -0
  102. package/src/principal/lib/clamp-authority.ts +64 -0
  103. package/src/principal/lib/delegation-chain.ts +145 -0
  104. package/src/principal/lib/delegation-grant.ts +36 -0
  105. 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
- * (biome) may contribute to the Xema OS through the Contribution Protocol
6
- * (XSI plane 2). Extending it is a one-line PR to the kernel.
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 equals the SUPPORTED set: every member is either handled by a
9
- * biome-host `KindParser` (see `biome-host-api/src/contributions/parsers/`)
10
- * or is a first-class self-registration kind (`Capability`, `ResourceOwnership`,
11
- * `StageMachine`) whose manifest schema the kernel co-owns. Purely-decorative
12
- * members that duplicated a working non-contribution mechanism and had no
13
- * parser were culled in the H7 pass. Adding a member is a one-line PR AND
14
- * requires a parser (or a first-class handler) do not re-add a decorative
15
- * kind with no ingestion path.
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
- /** The biome-install JSON Schema declaration shipped by a biome. */
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
- /** A UI icon asset contributed by a biome. */
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
- /** A provisioning scaffold contributed by a biome. */
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
- /** A workspace-spec overlay contributed by a biome. */
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
- /** A system-overlay contribution shipped by a biome. */
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
- /** A workspace-manifest template contributed by a biome. */
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
- /** An OpenCode tool descriptor contributed by a biome. */
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
- /** An OpenCode plugin descriptor contributed by a biome. */
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,5 @@
1
+ export * from './lib/delegation-grant';
2
+ export * from './lib/delegation-chain';
3
+ export * from './lib/clamp-authority';
4
+ export * from './lib/execution-principal';
5
+ export * from './lib/app-invocation-identity';
@@ -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
+ }