@xemahq/kernel-contracts 0.50.0 → 0.57.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/biome/lib/biome-manifest.d.ts +9 -0
- package/dist/biome/lib/biome-manifest.d.ts.map +1 -1
- package/dist/biome/lib/biome-manifest.js +2 -0
- package/dist/biome/lib/biome-manifest.js.map +1 -1
- package/dist/capability/index.d.ts +3 -1
- package/dist/capability/index.d.ts.map +1 -1
- package/dist/capability/index.js +3 -1
- package/dist/capability/index.js.map +1 -1
- package/dist/capability/lib/authority-effect.d.ts +8 -0
- package/dist/capability/lib/authority-effect.d.ts.map +1 -0
- package/dist/capability/lib/authority-effect.js +17 -0
- package/dist/capability/lib/authority-effect.js.map +1 -0
- package/dist/capability/lib/capability-contribution.d.ts +4 -0
- package/dist/capability/lib/capability-contribution.d.ts.map +1 -1
- package/dist/capability/lib/capability-contribution.js +4 -0
- package/dist/capability/lib/capability-contribution.js.map +1 -1
- package/dist/capability/lib/capability-domain.d.ts +8 -0
- package/dist/capability/lib/capability-domain.d.ts.map +1 -0
- package/dist/capability/lib/capability-domain.js +24 -0
- package/dist/capability/lib/capability-domain.js.map +1 -0
- package/dist/capability/lib/capability-reach.d.ts +13 -0
- package/dist/capability/lib/capability-reach.d.ts.map +1 -0
- package/dist/capability/lib/capability-reach.js +34 -0
- package/dist/capability/lib/capability-reach.js.map +1 -0
- package/dist/capability/lib/meta-tool.d.ts +51 -14
- package/dist/capability/lib/meta-tool.d.ts.map +1 -1
- package/dist/capability/lib/meta-tool.js +66 -12
- package/dist/capability/lib/meta-tool.js.map +1 -1
- package/dist/capability-projection/index.d.ts +4 -0
- package/dist/capability-projection/index.d.ts.map +1 -0
- package/dist/capability-projection/index.js +20 -0
- package/dist/capability-projection/index.js.map +1 -0
- package/dist/capability-projection/lib/capability-invocation-binding.d.ts +72 -0
- package/dist/capability-projection/lib/capability-invocation-binding.d.ts.map +1 -0
- package/dist/capability-projection/lib/capability-invocation-binding.js +101 -0
- package/dist/capability-projection/lib/capability-invocation-binding.js.map +1 -0
- package/dist/capability-projection/lib/capability-sync-manifest.d.ts +8 -0
- package/dist/capability-projection/lib/capability-sync-manifest.d.ts.map +1 -0
- package/dist/capability-projection/lib/capability-sync-manifest.js +8 -0
- package/dist/capability-projection/lib/capability-sync-manifest.js.map +1 -0
- package/dist/capability-projection/lib/projected-capability.d.ts +15 -0
- package/dist/capability-projection/lib/projected-capability.d.ts.map +1 -0
- package/dist/capability-projection/lib/projected-capability.js +17 -0
- package/dist/capability-projection/lib/projected-capability.js.map +1 -0
- package/dist/connector/lib/connector-adapter-contribution.d.ts +2 -0
- package/dist/connector/lib/connector-adapter-contribution.d.ts.map +1 -1
- package/dist/connector/lib/onboarding-manifest.d.ts +2 -0
- package/dist/connector/lib/onboarding-manifest.d.ts.map +1 -1
- package/dist/connector/lib/onboarding-manifest.js +1 -0
- package/dist/connector/lib/onboarding-manifest.js.map +1 -1
- package/dist/connector/lib/provider-descriptor.d.ts +2 -0
- package/dist/connector/lib/provider-descriptor.d.ts.map +1 -1
- package/dist/contribution/lib/contribution-kind.d.ts +1 -6
- package/dist/contribution/lib/contribution-kind.d.ts.map +1 -1
- package/dist/contribution/lib/contribution-kind.js +1 -6
- package/dist/contribution/lib/contribution-kind.js.map +1 -1
- package/dist/distribution/index.d.ts +1 -0
- package/dist/distribution/index.d.ts.map +1 -1
- package/dist/distribution/index.js +1 -0
- package/dist/distribution/index.js.map +1 -1
- package/dist/distribution/lib/distribution-lock.d.ts +6 -0
- package/dist/distribution/lib/distribution-lock.d.ts.map +1 -1
- package/dist/distribution/lib/distribution-lock.js +9 -1
- package/dist/distribution/lib/distribution-lock.js.map +1 -1
- package/dist/distribution/lib/first-party-capability-domains.d.ts +15 -0
- package/dist/distribution/lib/first-party-capability-domains.d.ts.map +1 -0
- package/dist/distribution/lib/first-party-capability-domains.js +52 -0
- package/dist/distribution/lib/first-party-capability-domains.js.map +1 -0
- package/dist/object/lib/xema-object-ref.d.ts +0 -5
- package/dist/object/lib/xema-object-ref.d.ts.map +1 -1
- package/dist/object/lib/xema-object-ref.js +12 -62
- package/dist/object/lib/xema-object-ref.js.map +1 -1
- package/dist/output-surface-inspect/index.d.ts +1 -0
- package/dist/output-surface-inspect/index.d.ts.map +1 -1
- package/dist/output-surface-inspect/index.js +1 -0
- package/dist/output-surface-inspect/index.js.map +1 -1
- package/dist/output-surface-inspect/lib/buffers.d.ts +52 -0
- package/dist/output-surface-inspect/lib/buffers.d.ts.map +1 -0
- package/dist/output-surface-inspect/lib/buffers.js +132 -0
- package/dist/output-surface-inspect/lib/buffers.js.map +1 -0
- package/dist/output-surface-inspect/lib/output-surface-inspect.d.ts +6 -1
- package/dist/output-surface-inspect/lib/output-surface-inspect.d.ts.map +1 -1
- package/dist/output-surface-inspect/lib/output-surface-inspect.js +12 -1
- package/dist/output-surface-inspect/lib/output-surface-inspect.js.map +1 -1
- package/dist/output-surface-inspect/lib/step-schema.d.ts +2 -1
- package/dist/output-surface-inspect/lib/step-schema.d.ts.map +1 -1
- package/dist/output-surface-inspect/lib/step-schema.js +9 -2
- package/dist/output-surface-inspect/lib/step-schema.js.map +1 -1
- package/dist/output-surface-inspect/runtime/index.d.ts +1 -0
- package/dist/output-surface-inspect/runtime/index.d.ts.map +1 -1
- package/dist/output-surface-inspect/runtime/index.js +1 -0
- package/dist/output-surface-inspect/runtime/index.js.map +1 -1
- package/dist/search/index.d.ts +1 -0
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -0
- package/dist/search/index.js.map +1 -1
- package/dist/search/lib/route-template.d.ts +31 -0
- package/dist/search/lib/route-template.d.ts.map +1 -0
- package/dist/search/lib/route-template.js +142 -0
- package/dist/search/lib/route-template.js.map +1 -0
- package/dist/search/lib/search-contribution.d.ts +4 -2
- package/dist/search/lib/search-contribution.d.ts.map +1 -1
- package/dist/search/lib/search-contribution.js +46 -4
- package/dist/search/lib/search-contribution.js.map +1 -1
- package/dist/search-source/lib/indexable-document.d.ts +22 -0
- package/dist/search-source/lib/indexable-document.d.ts.map +1 -1
- package/dist/search-source/lib/indexable-document.js +10 -1
- package/dist/search-source/lib/indexable-document.js.map +1 -1
- package/dist/search-source/lib/search-index-event.d.ts +10 -0
- package/dist/search-source/lib/search-index-event.d.ts.map +1 -1
- package/dist/search-source/lib/search-ref.d.ts.map +1 -1
- package/dist/search-source/lib/search-ref.js +11 -2
- package/dist/search-source/lib/search-ref.js.map +1 -1
- package/dist/search-source/lib/search-replay.d.ts +10 -0
- package/dist/search-source/lib/search-replay.d.ts.map +1 -1
- package/dist/service-registry/lib/service-descriptor.d.ts +1 -0
- package/dist/service-registry/lib/service-descriptor.d.ts.map +1 -1
- package/dist/space/lib/space-ref-parser.d.ts +6 -4
- package/dist/space/lib/space-ref-parser.d.ts.map +1 -1
- package/dist/space/lib/space-ref-parser.js +90 -126
- package/dist/space/lib/space-ref-parser.js.map +1 -1
- package/dist/space/lib/space-traversal.d.ts.map +1 -1
- package/dist/space/lib/space-traversal.js +9 -13
- package/dist/space/lib/space-traversal.js.map +1 -1
- package/dist/space/lib/space.d.ts +7 -0
- package/dist/space/lib/space.d.ts.map +1 -1
- package/dist/space/lib/space.js +48 -29
- package/dist/space/lib/space.js.map +1 -1
- package/dist/subject/lib/subject.d.ts +1 -0
- package/dist/subject/lib/subject.d.ts.map +1 -1
- package/dist/subject/lib/subject.js +1 -0
- package/dist/subject/lib/subject.js.map +1 -1
- package/dist/surface/index.d.ts +4 -0
- package/dist/surface/index.d.ts.map +1 -0
- package/dist/surface/index.js +20 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/surface/lib/surface-contribution.d.ts +24 -0
- package/dist/surface/lib/surface-contribution.d.ts.map +1 -0
- package/dist/surface/lib/surface-contribution.js +23 -0
- package/dist/surface/lib/surface-contribution.js.map +1 -0
- package/dist/surface/lib/surface-kind.d.ts +23 -0
- package/dist/surface/lib/surface-kind.d.ts.map +1 -0
- package/dist/surface/lib/surface-kind.js +27 -0
- package/dist/surface/lib/surface-kind.js.map +1 -0
- package/dist/surface/lib/vista-surface.d.ts +11 -0
- package/dist/surface/lib/vista-surface.d.ts.map +1 -0
- package/dist/surface/lib/vista-surface.js +13 -0
- package/dist/surface/lib/vista-surface.js.map +1 -0
- package/dist/widget/lib/chat-widget-envelope.d.ts.map +1 -1
- package/dist/widget/lib/chat-widget-envelope.js.map +1 -1
- package/dist/workflow/lib/enums.d.ts.map +1 -1
- package/dist/workflow/lib/enums.js.map +1 -1
- package/package.json +1 -1
- package/src/biome/lib/biome-manifest.ts +41 -1
- package/src/capability/index.ts +3 -1
- package/src/capability/lib/authority-effect.ts +88 -0
- package/src/capability/lib/capability-contribution.ts +44 -0
- package/src/capability/lib/capability-domain.ts +51 -0
- package/src/capability/lib/capability-grant.ts +1 -1
- package/src/capability/lib/capability-reach.ts +119 -0
- package/src/capability/lib/meta-tool.ts +172 -39
- package/src/capability-projection/index.ts +3 -0
- package/src/capability-projection/lib/capability-invocation-binding.ts +223 -0
- package/src/capability-projection/lib/capability-sync-manifest.ts +52 -0
- package/src/capability-projection/lib/projected-capability.ts +63 -0
- package/src/connector/lib/onboarding-manifest.ts +16 -0
- package/src/contribution/lib/contribution-kind.ts +165 -61
- package/src/distribution/index.ts +1 -0
- package/src/distribution/lib/distribution-lock.ts +36 -0
- package/src/distribution/lib/first-party-capability-domains.ts +166 -0
- package/src/object/lib/xema-object-ref.ts +27 -82
- package/src/output-surface-inspect/index.ts +2 -0
- package/src/output-surface-inspect/lib/buffers.ts +252 -0
- package/src/output-surface-inspect/lib/output-surface-inspect.ts +65 -6
- package/src/output-surface-inspect/lib/step-schema.ts +36 -3
- package/src/output-surface-inspect/runtime/index.ts +1 -0
- package/src/search/index.ts +1 -0
- package/src/search/lib/route-template.ts +381 -0
- package/src/search/lib/search-contribution.ts +138 -31
- package/src/search-source/lib/indexable-document.ts +44 -0
- package/src/search-source/lib/search-ref.ts +22 -9
- package/src/service-registry/lib/service-descriptor.ts +16 -0
- package/src/space/lib/skill-mirror-path.ts +1 -1
- package/src/space/lib/space-ref-parser.ts +190 -165
- package/src/space/lib/space-traversal.ts +34 -17
- package/src/space/lib/space.ts +110 -35
- package/src/subject/lib/subject.ts +16 -0
- package/src/surface/index.ts +3 -0
- package/src/surface/lib/surface-contribution.ts +101 -0
- package/src/surface/lib/surface-kind.ts +65 -0
- package/src/surface/lib/vista-surface.ts +44 -0
- package/src/widget/lib/chat-widget-envelope.ts +6 -2
- package/src/workflow/lib/enums.ts +6 -7
- package/src/capability/lib/first-party-capability-domains.ts +0 -77
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CapabilityContributionManifestSchema,
|
|
5
|
+
type CapabilityContributionManifest,
|
|
6
|
+
} from '../../capability';
|
|
7
|
+
import {
|
|
8
|
+
CapabilityInvocationBindingSchema,
|
|
9
|
+
type CapabilityInvocationBinding,
|
|
10
|
+
} from './capability-invocation-binding';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Where a projected capability came from.
|
|
14
|
+
*
|
|
15
|
+
* `sourceKey` scopes the projector's PRUNE set: a projector may only ever
|
|
16
|
+
* delete refs it previously projected, and a ref is attributed to exactly one
|
|
17
|
+
* source per reconcile. `biomeId` / `biomeVersion` are the same provenance the
|
|
18
|
+
* contribution path stamps — authors never declare them inline.
|
|
19
|
+
*/
|
|
20
|
+
export interface ProjectedCapabilityProvenance {
|
|
21
|
+
readonly sourceKey: string;
|
|
22
|
+
readonly biomeId: string;
|
|
23
|
+
readonly biomeVersion: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const ProjectedCapabilityProvenanceSchema: z.ZodType<ProjectedCapabilityProvenance> =
|
|
27
|
+
z.object({
|
|
28
|
+
sourceKey: z.string().min(1).max(200),
|
|
29
|
+
biomeId: z.string().min(1).max(200),
|
|
30
|
+
biomeVersion: z.string().min(1).max(200),
|
|
31
|
+
}) as unknown as z.ZodType<ProjectedCapabilityProvenance>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
35
|
+
* `ProjectedCapability` — one capability DERIVED by a producer, rather than
|
|
36
|
+
* hand-authored as a contribution file.
|
|
37
|
+
*
|
|
38
|
+
* It deliberately EXTENDS {@link CapabilityContributionManifest} instead of
|
|
39
|
+
* restating its fields: the hand-authored path, the decorator path and the
|
|
40
|
+
* projection path must all end up on the SAME `POST /capabilities/register`
|
|
41
|
+
* wire shape, so there is exactly one place that shape is described. A
|
|
42
|
+
* projection adds only the two things a hand-authored descriptor does not
|
|
43
|
+
* carry:
|
|
44
|
+
*
|
|
45
|
+
* - {@link CapabilityInvocationBinding} — HOW to invoke it, as data, so the
|
|
46
|
+
* router resolves a target by lookup instead of a hardcoded switch;
|
|
47
|
+
* - {@link ProjectedCapabilityProvenance} — WHICH source owns it, which is
|
|
48
|
+
* what scopes the reconcile engine's prune set.
|
|
49
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
50
|
+
*/
|
|
51
|
+
export interface ProjectedCapability extends CapabilityContributionManifest {
|
|
52
|
+
readonly invocation: CapabilityInvocationBinding;
|
|
53
|
+
readonly provenance: ProjectedCapabilityProvenance;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const ProjectedCapabilitySchema: z.ZodType<ProjectedCapability> = z
|
|
57
|
+
.intersection(
|
|
58
|
+
CapabilityContributionManifestSchema,
|
|
59
|
+
z.object({
|
|
60
|
+
invocation: CapabilityInvocationBindingSchema,
|
|
61
|
+
provenance: ProjectedCapabilityProvenanceSchema,
|
|
62
|
+
}),
|
|
63
|
+
) as unknown as z.ZodType<ProjectedCapability>;
|
|
@@ -99,6 +99,21 @@ export interface CredentialFieldSpec {
|
|
|
99
99
|
readonly type: CredentialFieldType;
|
|
100
100
|
/** When true, the wizard refuses submission unless the field is filled. */
|
|
101
101
|
readonly required: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* DECLARED secrecy of the field's value. `true` ⇒ the value is a secret and
|
|
104
|
+
* MUST travel to credential custody (the broker's opaque `secretValue`
|
|
105
|
+
* bundle); `false` ⇒ the value is non-secret configuration and is stored in
|
|
106
|
+
* the provision's plain JSON config.
|
|
107
|
+
*
|
|
108
|
+
* This is a DECLARATION, never an inference. Secrecy was previously derived
|
|
109
|
+
* from `type === CredentialFieldType.PASSWORD`, which made a *rendering*
|
|
110
|
+
* affordance load-bearing for a *custody* decision: a secret rendered as
|
|
111
|
+
* `TEXT` (an API key an author wanted visible) silently landed in
|
|
112
|
+
* non-secret storage, and a non-secret masked for ergonomics silently landed
|
|
113
|
+
* in custody. `type` now means "how to render it" and `isSecret` means
|
|
114
|
+
* "where it may be stored" — the two are independent and both required.
|
|
115
|
+
*/
|
|
116
|
+
readonly isSecret: boolean;
|
|
102
117
|
/** Optional placeholder copy. */
|
|
103
118
|
readonly placeholder?: string;
|
|
104
119
|
/** Optional secondary hint shown beneath the input. */
|
|
@@ -119,6 +134,7 @@ export const CredentialFieldSpecSchema = z.strictObject({
|
|
|
119
134
|
label: z.string().min(1),
|
|
120
135
|
type: z.nativeEnum(CredentialFieldType),
|
|
121
136
|
required: z.boolean(),
|
|
137
|
+
isSecret: z.boolean(),
|
|
122
138
|
placeholder: z.string().optional(),
|
|
123
139
|
hint: z.string().optional(),
|
|
124
140
|
transform: z.nativeEnum(CredentialFieldTransform),
|
|
@@ -28,12 +28,23 @@ import { z } from 'zod';
|
|
|
28
28
|
* adapter projecting the `MountSourceKindRegistry`; NO biome-host
|
|
29
29
|
* parser.
|
|
30
30
|
* 3. RESERVED — declared for protocol/catalog completeness but with NO
|
|
31
|
-
* contribution-protocol ingestion path today.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
31
|
+
* contribution-protocol ingestion path today. Every remaining RESERVED
|
|
32
|
+
* member has a REAL, verified delivery path that is deliberately NOT the
|
|
33
|
+
* contribution protocol — either a biome-host-api HTTP content endpoint
|
|
34
|
+
* that reads the biome's convention directory off disk ON DEMAND
|
|
35
|
+
* (`Icon`, `BiomeInstallSchema`, `ProvisioningScaffold`), or a build-time
|
|
36
|
+
* bake into the agent-runtime image (`OpenCodeTool`, `OpenCodePlugin`).
|
|
37
|
+
* None of them is "seeded": nothing walks those directories at boot.
|
|
38
|
+
* A member with NO delivery path at all is DELETED rather than left as
|
|
39
|
+
* decoration — `GateAction`, `ChartRuntime`, `WorkspaceSpecOverlay` and
|
|
40
|
+
* `SystemOverlayContribution` in the first cleanup; `McpTool` and
|
|
41
|
+
* `WorkspaceManifestTemplate` in the second (each had zero readers, zero
|
|
42
|
+
* on-disk instances anywhere in the tree, no manifest schema, and a live
|
|
43
|
+
* superseding kind — `McpCatalog` and `AgentSkill` respectively). Do NOT
|
|
44
|
+
* assume a biome-host parser exists for a RESERVED member, and do not
|
|
45
|
+
* ship a biome contribution of that kind expecting it to be ingested —
|
|
46
|
+
* biome-host's `assertContributionKindsAreIngestable` coverage guard
|
|
47
|
+
* rejects it fail-fast, naming the file.
|
|
37
48
|
*
|
|
38
49
|
* This doc-comment is kept HONEST by review: a member with no biome-host parser
|
|
39
50
|
* MUST be annotated with its true category (2 or 3). An earlier revision falsely
|
|
@@ -46,7 +57,16 @@ import { z } from 'zod';
|
|
|
46
57
|
* Enum string values match the slug spoken by the rest of the protocol
|
|
47
58
|
* (Capability References, manifest declarations) so that round-tripping
|
|
48
59
|
* 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.
|
|
60
|
+
* CONTRACT — never rename or delete a member without a migration plan. The
|
|
61
|
+
* migration plan for the two deletions above is EMPTY BY CONSTRUCTION, and that
|
|
62
|
+
* is the bar any future deletion must clear: neither value had a producer (no
|
|
63
|
+
* biome ships the directory, no parser emits the envelope), a consumer (no
|
|
64
|
+
* routing-table row, no sync handler), or a persisted row (the contribution-sync
|
|
65
|
+
* store's `kind` column is an open `string`, and biome-host's coverage guard has
|
|
66
|
+
* been rejecting envelopes of both kinds fail-fast). The only downstream churn is
|
|
67
|
+
* mechanical regeneration: biome-host-api's `openapi.public.json`, the generated
|
|
68
|
+
* `@xemahq/biome-host-api-client` `contributionKind` model, and the frontend
|
|
69
|
+
* mirror of both (via `pnpm sync:frontend`).
|
|
50
70
|
*/
|
|
51
71
|
export enum ContributionKind {
|
|
52
72
|
// -- v1 seed ---------------------------------------------------------------
|
|
@@ -58,27 +78,16 @@ export enum ContributionKind {
|
|
|
58
78
|
* stays the authoritative owner and read path; there is NO biome-host parser.
|
|
59
79
|
*/
|
|
60
80
|
MountSource = 'mount-source',
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
// `McpTool = 'mcp-tool'` was DELETED here. It claimed delivery through a
|
|
82
|
+
// `mcp-tools/` convention directory that NO biome ships and NO code reads:
|
|
83
|
+
// the only references were the `CONTENT_KIND_DIR` / `CONTENT_KIND_EXTENSIONS`
|
|
84
|
+
// table rows and biome-host's generic presence loop. There was no manifest
|
|
85
|
+
// schema (kernel or SDK), no owning registry, and no sync handler. The live
|
|
86
|
+
// MCP contribution path is `McpCatalog` (parser + routes to BOTH
|
|
87
|
+
// llm-registry-api and mcp-gateway-api); the live per-biome code-tool
|
|
88
|
+
// declaration is the manifest block `xema.mcpTools[]`, which is unrelated to
|
|
89
|
+
// this enum despite the shared name.
|
|
68
90
|
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
|
-
*/
|
|
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
|
-
*/
|
|
81
|
-
ChartRuntime = 'chart-runtime',
|
|
82
91
|
AgentSkill = 'agent-skill',
|
|
83
92
|
AgentKernel = 'agent-kernel',
|
|
84
93
|
/**
|
|
@@ -91,6 +100,28 @@ export enum ContributionKind {
|
|
|
91
100
|
*/
|
|
92
101
|
ModelResolutionDimension = 'model-resolution-dimension',
|
|
93
102
|
WidgetKind = 'widget-kind',
|
|
103
|
+
/**
|
|
104
|
+
* Category 1 (biome-host `KindParser` — parser pending). A VISTA SURFACE
|
|
105
|
+
* KIND contributed by a biome: the render kind a Vista preview tab can hold,
|
|
106
|
+
* declared as a bare biome-local slug plus a version, display name, summary,
|
|
107
|
+
* and an opaque JSON `payloadSchema`. The catalogue namespaces the slug to
|
|
108
|
+
* `<biomeId>:<kind>` at ingest so a biome can never shadow a first-party
|
|
109
|
+
* `SurfaceKind`.
|
|
110
|
+
*
|
|
111
|
+
* The kernel owns this member AND the manifest schema
|
|
112
|
+
* (`SurfaceContributionManifestSchema`, exported from
|
|
113
|
+
* `@xemahq/kernel-contracts/surface`) — the 2-file rule. The per-kind
|
|
114
|
+
* ingestion parser belongs to the consuming service (`biome-host-api`), not
|
|
115
|
+
* the kernel, and is NOT shipped yet: a later Vista unit adds
|
|
116
|
+
* `surface-kind.parser.ts` alongside the catalogue endpoint. Until then this
|
|
117
|
+
* member is declared but not ingested — do not ship a biome contribution of
|
|
118
|
+
* this kind expecting it to be picked up.
|
|
119
|
+
*
|
|
120
|
+
* Structurally identical to {@link ContributionKind.WidgetKind} on purpose:
|
|
121
|
+
* the in-chat widget plane and the Vista render plane share ONE contribution
|
|
122
|
+
* shape, one frontend registry core, and one three-layer renderer fallback.
|
|
123
|
+
*/
|
|
124
|
+
SurfaceKind = 'surface-kind',
|
|
94
125
|
|
|
95
126
|
// -- Registry gaps closed --------------------------------------------------
|
|
96
127
|
/**
|
|
@@ -117,34 +148,48 @@ export enum ContributionKind {
|
|
|
117
148
|
/** A role capability bundle contributed by a biome. */
|
|
118
149
|
RoleCapability = 'role-capability',
|
|
119
150
|
/**
|
|
120
|
-
* RESERVED (category 3). No
|
|
121
|
-
* resource-selection schema
|
|
122
|
-
*
|
|
151
|
+
* RESERVED (category 3). No parser, and none is wanted: the install-wizard
|
|
152
|
+
* resource-selection schema is an OPAQUE, browser-facing JSON Schema that
|
|
153
|
+
* biome-host-api serves on demand from the biome's convention
|
|
154
|
+
* `install-schema/` directory (`install.json` plus an optional
|
|
155
|
+
* `ui-schema.json`) via `GET /platform/biomes/server/:id/install-schema`. It
|
|
156
|
+
* is not seeded at boot and never crosses a service boundary as a
|
|
157
|
+
* contribution — the only consumer is the frontend install wizard, which
|
|
158
|
+
* pulls it over HTTP and renders it with rjsf. There is nothing for a kernel
|
|
159
|
+
* manifest schema to validate: the body is an arbitrary draft-2020-12 JSON
|
|
160
|
+
* Schema, and a parser could only pass it through verbatim to a registry
|
|
161
|
+
* that does not exist.
|
|
123
162
|
*/
|
|
124
163
|
BiomeInstallSchema = 'biome-install-schema',
|
|
125
164
|
/**
|
|
126
|
-
* RESERVED (category 3). No parser
|
|
127
|
-
*
|
|
165
|
+
* RESERVED (category 3). No parser, and none is wanted: icons are BYTE
|
|
166
|
+
* ASSETS, not manifests — biome-host-api serves each one straight off disk
|
|
167
|
+
* from the biome's convention `icons/` directory via the public CDN-asset
|
|
168
|
+
* route `GET /platform/biomes/server/:id/icons/:slug.svg`, discovered by
|
|
169
|
+
* on-disk presence and never indexed. Nothing queries "which icons exist":
|
|
170
|
+
* the caller already knows the slug (it is the workflow activity kind) and
|
|
171
|
+
* builds the URL deterministically, so a third-party biome overrides an icon
|
|
172
|
+
* simply by shipping the file. Routing SVG bytes through the contribution
|
|
173
|
+
* outbox would mean base64-in-JSON into a database row only to re-serve it.
|
|
128
174
|
*/
|
|
129
175
|
Icon = 'icon',
|
|
130
176
|
/** A project-kit bundle contributed by a biome. */
|
|
131
177
|
ProjectKit = 'project-kit',
|
|
132
178
|
/**
|
|
133
|
-
* RESERVED (category 3). No parser
|
|
134
|
-
*
|
|
135
|
-
* `
|
|
179
|
+
* RESERVED (category 3). No parser: a provisioning scaffold is declared in
|
|
180
|
+
* the manifest's `xema.provisioning[]` and authored as
|
|
181
|
+
* `provisioning/<id>.yaml` plus an optional `provisioning/<id>/` template
|
|
182
|
+
* tree, and agent-session-api's `provisioning-plan-resolver` PULLS it over
|
|
183
|
+
* HTTP per session bootstrap from biome-host-api's
|
|
184
|
+
* `GET /platform/biomes/server/:id/provisioning-scaffolds`. The render
|
|
185
|
+
* depends on that session's runtime `customConfig`, so there is no static
|
|
186
|
+
* descriptor to upsert into a registry, and agent-session-api has no
|
|
187
|
+
* contribution-sync seam. NOTE the declaration-vs-disk parity check is LAZY,
|
|
188
|
+
* not boot-time: it runs inside `loadProvisioningScaffolds` when the
|
|
189
|
+
* scaffolds are fetched, so a biome whose `provisioning[]` disagrees with its
|
|
190
|
+
* `provisioning/` directory boots green and fails at first provision.
|
|
136
191
|
*/
|
|
137
192
|
ProvisioningScaffold = 'provisioning-scaffold',
|
|
138
|
-
/**
|
|
139
|
-
* RESERVED (category 3). No ingestion path today — no parser, no
|
|
140
|
-
* content-directory seeder, no domain-service handler.
|
|
141
|
-
*/
|
|
142
|
-
WorkspaceSpecOverlay = 'workspace-spec-overlay',
|
|
143
|
-
/**
|
|
144
|
-
* RESERVED (category 3). No ingestion path today — no parser, no
|
|
145
|
-
* content-directory seeder, no domain-service handler.
|
|
146
|
-
*/
|
|
147
|
-
SystemOverlayContribution = 'system-overlay-contribution',
|
|
148
193
|
/** A concrete connector provider contributed by a biome inside an
|
|
149
194
|
* adapter kind. */
|
|
150
195
|
ConnectorAdapter = 'connector-adapter',
|
|
@@ -156,29 +201,88 @@ export enum ContributionKind {
|
|
|
156
201
|
DeliverableSpec = 'deliverable-spec',
|
|
157
202
|
/** A workspace manifest contributed by a biome. */
|
|
158
203
|
WorkspaceManifest = 'workspace-manifest',
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
204
|
+
// `WorkspaceManifestTemplate = 'workspace-manifest-template'` was DELETED
|
|
205
|
+
// here. It claimed delivery through a `workspace-manifest-templates/`
|
|
206
|
+
// convention directory that NOTHING reads — no seeder, no endpoint, and no
|
|
207
|
+
// `walkContent` call for that kind exists anywhere. The manifest templates a
|
|
208
|
+
// workspace actually renders (`spec.seedFiles[].template`) are resolved at
|
|
209
|
+
// compose time by `buildSkillBundleTemplateResolver` from SKILL BUNDLE
|
|
210
|
+
// resources at `assets/templates/<name>.hbs`, fetched from
|
|
211
|
+
// skill-registry-api — i.e. they ship as `AgentSkill`, which HAS a parser and
|
|
212
|
+
// a route. Re-adding a second delivery path for the same artifact would fork
|
|
213
|
+
// ownership away from skill-registry (bundle fetch, 5-tier scope ladder,
|
|
214
|
+
// shared Handlebars compile) for no gain.
|
|
165
215
|
/** A tool-profile contributed by a biome. */
|
|
166
216
|
ToolProfile = 'tool-profile',
|
|
167
|
-
/** An MCP catalog entry contributed by a biome
|
|
168
|
-
*
|
|
169
|
-
*
|
|
217
|
+
/** An MCP catalog entry contributed by a biome — a catalog manifest naming a
|
|
218
|
+
* connectable MCP service and the tools it exposes. This is the ONLY
|
|
219
|
+
* enum-dispatched MCP contribution path. */
|
|
170
220
|
McpCatalog = 'mcp-catalog',
|
|
171
221
|
/**
|
|
172
|
-
* RESERVED (category 3). No
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
222
|
+
* RESERVED (category 3). No parser, and one CANNOT be shipped without first
|
|
223
|
+
* closing the build-time gap tracked as H10 in
|
|
224
|
+
* `.claude/plans/canopy-universal-agent.md` §11.1, which carries the full,
|
|
225
|
+
* component-by-component write-down of what a runtime path would require;
|
|
226
|
+
* this note is the summary. Agent-runtime custom tools are EXECUTABLE
|
|
227
|
+
* `.ts`/`.js` files that reach an agent ONLY by being copied into the
|
|
228
|
+
* agent-runtime worker image at build time — every kernel biome that ships a
|
|
229
|
+
* tools directory has its own explicit `COPY` line in that image's
|
|
230
|
+
* Dockerfile, landing the files in the runtime's tool slot, which the runtime
|
|
231
|
+
* auto-discovers when its process starts. So a third-party biome tool costs a
|
|
232
|
+
* Dockerfile edit plus an image rebuild. The one runtime seam that exists is
|
|
233
|
+
* a WHOLE-DIRECTORY swap, not a merge: when the templates read-only volume is
|
|
234
|
+
* mounted, workspace-proxy deletes the image-baked tool directory and
|
|
235
|
+
* symlinks that volume's copy in its place — and the volume is populated
|
|
236
|
+
* entirely out of band.
|
|
237
|
+
*
|
|
238
|
+
* Do NOT ship a parser that fakes the missing half. A real runtime path needs
|
|
239
|
+
* FOUR things, none of which exists:
|
|
240
|
+
* 1. An owning registry that stores tool SOURCE and can be reconciled by
|
|
241
|
+
* the contribution outbox. It cannot be a plain descriptor table: the
|
|
242
|
+
* payload is code, so the store has to carry the bytes AND the trust
|
|
243
|
+
* decision (signature verification + static analysis — the pipeline
|
|
244
|
+
* `biome-fetcher-api` already runs for biome bundles), not just a row.
|
|
245
|
+
* 2. Materialization in the workspace composer, which starts with a new
|
|
246
|
+
* `WorkspaceMount` slot: that enum is CLOSED and has no tools entry, and
|
|
247
|
+
* workspace-proxy rejects a mount whose slot is not in it. The composer
|
|
248
|
+
* (`@xemahq/agent-session-runtime`) already writes the skill-bundle and
|
|
249
|
+
* slash-command slots the same way; a tools slot additionally needs
|
|
250
|
+
* biome-NAMESPACED filenames (two biomes must not collide on a tool
|
|
251
|
+
* name) that stay FLAT — the runtime's discovery glob is one level deep,
|
|
252
|
+
* so a nested directory is invisible — and it must compose with, not be
|
|
253
|
+
* clobbered by, the templates-volume symlink above.
|
|
254
|
+
* 3. A per-session tool allow/deny decision. Filtering is static today,
|
|
255
|
+
* baked into the image's runtime config, so a dynamically mounted tool
|
|
256
|
+
* has no visibility story.
|
|
257
|
+
* 4. Sandboxing/permission review, because a contributed tool executes in
|
|
258
|
+
* the agent's workspace with the agent's credentials.
|
|
259
|
+
*
|
|
260
|
+
* Timing is the one part already solved: discovery runs once per runtime
|
|
261
|
+
* PROCESS, and workspace-proxy already restarts that process after every
|
|
262
|
+
* session-bundle apply.
|
|
176
263
|
*/
|
|
177
264
|
OpenCodeTool = 'opencode-tool',
|
|
178
265
|
/**
|
|
179
|
-
* RESERVED (category 3). No parser
|
|
180
|
-
* does NOT route this kind
|
|
181
|
-
*
|
|
266
|
+
* RESERVED (category 3). No parser, and the contribution-registrar
|
|
267
|
+
* deliberately does NOT route this kind — its own unit test uses this member
|
|
268
|
+
* as the canonical "not routed" fixture. Plugins are baked harder than tools:
|
|
269
|
+
* a kernel biome's plugin directory is installed under a read-only `/opt`
|
|
270
|
+
* prefix during the image build, with its dependency tree resolved there, and
|
|
271
|
+
* the generated runtime config references it by ABSOLUTE IMAGE PATH. That
|
|
272
|
+
* `/opt` location is load-bearing — plugins once lived under the workspace
|
|
273
|
+
* root, where the per-session volume mount SHADOWED them and both shipped
|
|
274
|
+
* plugins silently failed to load.
|
|
275
|
+
*
|
|
276
|
+
* On top of the four prerequisites listed on
|
|
277
|
+
* {@link ContributionKind.OpenCodeTool}, a runtime plugin path additionally
|
|
278
|
+
* needs (a) the plugin list in the generated runtime config to become
|
|
279
|
+
* per-session paths instead of image constants, (b) a dependency story, since
|
|
280
|
+
* a plugin's dependencies are installed at image-build time and the runtime
|
|
281
|
+
* workspace has neither an install step nor network egress for one, and (c) a
|
|
282
|
+
* process restart, because the runtime resolves plugins once at process
|
|
283
|
+
* start. Until all of that lands, treat plugins as an image concern — tools
|
|
284
|
+
* are the cheaper half to make runtime-loadable first. See
|
|
285
|
+
* `.claude/plans/canopy-universal-agent.md` §11.1.
|
|
182
286
|
*/
|
|
183
287
|
OpenCodePlugin = 'opencode-plugin',
|
|
184
288
|
// -- Capability self-registration ------------------------------------------
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type BiomeOrigin,
|
|
10
10
|
type BiomeAudience,
|
|
11
11
|
} from '../../biome';
|
|
12
|
+
import { CapabilityDomainSchema } from '../../capability';
|
|
12
13
|
import {
|
|
13
14
|
PlatformServiceTierSchema,
|
|
14
15
|
type PlatformServiceTier,
|
|
@@ -164,10 +165,44 @@ export const LockedPlatformServiceSchema = z.object({
|
|
|
164
165
|
* kernel (kernel contracts hold no clock); both optional so the type is usable
|
|
165
166
|
* mid-resolution.
|
|
166
167
|
*/
|
|
168
|
+
/**
|
|
169
|
+
* One platform-reserved capability domain and the biome that owns it.
|
|
170
|
+
*
|
|
171
|
+
* Resolved from the FULL biome index the release pipeline discovered — NOT
|
|
172
|
+
* from `DistributionLock.biomes`. A reservation is a statement about who
|
|
173
|
+
* AUTHORED the domain, so it holds in every edition whether or not that
|
|
174
|
+
* edition preloads the owning biome. See `first-party-capability-domains.ts`
|
|
175
|
+
* for why per-edition ownership would be a fail-open hole.
|
|
176
|
+
*/
|
|
177
|
+
export interface CapabilityDomainOwner {
|
|
178
|
+
/** The `<domain>` segment of a `CapabilityRef`. */
|
|
179
|
+
domain: string;
|
|
180
|
+
/** `xema.id` of the biome whose manifest claims it. */
|
|
181
|
+
biomeId: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const CapabilityDomainOwnerSchema = z
|
|
185
|
+
.object({
|
|
186
|
+
domain: CapabilityDomainSchema,
|
|
187
|
+
biomeId: z.string().min(1),
|
|
188
|
+
})
|
|
189
|
+
.strict() as z.ZodType<CapabilityDomainOwner>;
|
|
190
|
+
|
|
167
191
|
export interface DistributionLock {
|
|
168
192
|
schemaVersion: 1;
|
|
169
193
|
distributionId: string;
|
|
170
194
|
biomes: readonly LockedBiome[];
|
|
195
|
+
/**
|
|
196
|
+
* Platform-reserved capability domains — the input `capability-registry-api`
|
|
197
|
+
* derives its org-provider-binding shadow refusal from, and the router its
|
|
198
|
+
* third-party authority-effect rule.
|
|
199
|
+
*
|
|
200
|
+
* REQUIRED, deliberately. An absent or empty table means "no domain is
|
|
201
|
+
* first-party", which is fail-OPEN: every platform ref would become
|
|
202
|
+
* org-bindable. A lock that predates this field must therefore fail to parse
|
|
203
|
+
* rather than silently disarm the guard.
|
|
204
|
+
*/
|
|
205
|
+
capabilityDomainOwners: readonly CapabilityDomainOwner[];
|
|
171
206
|
/** Non-biome substrate services that ship with this distribution (the deploy "init system"). */
|
|
172
207
|
platformServices: readonly LockedPlatformService[];
|
|
173
208
|
/** ISO-8601 timestamp the resolver stamped (tooling-supplied). */
|
|
@@ -180,6 +215,7 @@ export const DistributionLockSchema = z.object({
|
|
|
180
215
|
schemaVersion: z.literal(1),
|
|
181
216
|
distributionId: z.string().min(1),
|
|
182
217
|
biomes: z.array(LockedBiomeSchema).readonly(),
|
|
218
|
+
capabilityDomainOwners: z.array(CapabilityDomainOwnerSchema).readonly(),
|
|
183
219
|
platformServices: z.array(LockedPlatformServiceSchema).readonly(),
|
|
184
220
|
resolvedAt: z.string().min(1).optional(),
|
|
185
221
|
inputHash: z.string().min(1).optional(),
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
capabilityDomainOf,
|
|
3
|
+
isCapabilityDomain,
|
|
4
|
+
type CapabilityDomain,
|
|
5
|
+
} from '../../capability';
|
|
6
|
+
import type { DistributionLock } from './distribution-lock';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
* First-party capability domains — DERIVED from the distribution lock
|
|
11
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
*
|
|
13
|
+
* A capability ref is `<domain>:<resource>.<verb>@<major>`. A DOMAIN is
|
|
14
|
+
* "first-party" when an in-the-box platform biome owns every ref under it.
|
|
15
|
+
*
|
|
16
|
+
* WHY THIS MATTERS (the security job):
|
|
17
|
+
*
|
|
18
|
+
* `capability-registry-api` REFUSES an org `CapabilityProviderBinding` whose
|
|
19
|
+
* ref domain is first-party. Without that refusal an org admin could bind
|
|
20
|
+
* `kb:page.read@1` — or `web:page.fetch@1` — to a URL they control and
|
|
21
|
+
* silently shadow the platform data plane for every user, agent and app in
|
|
22
|
+
* the org. The router is data-driven off the invocation binding, so a
|
|
23
|
+
* shadowing binding is a live redirect, not a theoretical one.
|
|
24
|
+
*
|
|
25
|
+
* WHY THE DISTRIBUTION LOCK IS THE RIGHT SOURCE, AND THE OTHER TWO WERE NOT:
|
|
26
|
+
*
|
|
27
|
+
* • NOT `exposesCapabilities` on a live manifest — that is SELF-DECLARED at
|
|
28
|
+
* install time, so a third-party biome an org admin installs could mint
|
|
29
|
+
* itself first-party and then bind the domain it just claimed. Circular.
|
|
30
|
+
* • NOT capability-record provenance in the registry DB — that is written by
|
|
31
|
+
* an eventually-consistent contribution-sync retry, so a derivation from it
|
|
32
|
+
* would report "not first-party" for a genuinely first-party domain during
|
|
33
|
+
* the exact window (boot, resync) the refusal exists to cover. FAIL-OPEN.
|
|
34
|
+
* • The distribution lock is neither. It is RESOLVED by the release pipeline
|
|
35
|
+
* from a pinned checkout, COMMITTED, drift-checked in CI (`dist-check`),
|
|
36
|
+
* vendored into the deployment as an immutable ConfigMap, and read once at
|
|
37
|
+
* boot. Nothing an org admin can do at runtime — installing a biome from
|
|
38
|
+
* npm/OCI/git, editing a manifest, publishing a contribution — changes it.
|
|
39
|
+
* Membership is a PLATFORM-AUTHOR decision, exactly as it should be.
|
|
40
|
+
*
|
|
41
|
+
* ── OWNERSHIP IS RESERVATION-WIDE, NOT PER-EDITION ─────────────────────────
|
|
42
|
+
*
|
|
43
|
+
* `DistributionLock.capabilityDomainOwners` is resolved from the FULL biome
|
|
44
|
+
* index — every first-party biome the release pipeline discovered in its own
|
|
45
|
+
* checkout — NOT from the subset of biomes this edition happens to ship.
|
|
46
|
+
*
|
|
47
|
+
* That distinction is load-bearing. If ownership were per-edition, a trimmed
|
|
48
|
+
* edition that does not ship `knowledge-base` would leave `kb` unowned, so an
|
|
49
|
+
* org admin could install `knowledge-base` from the store and THEN bind
|
|
50
|
+
* `kb:page.read@1` to a URL they control — shadowing it for the whole org.
|
|
51
|
+
* Being authored by the platform is what makes a domain un-shadowable;
|
|
52
|
+
* whether a given edition preloads the owning biome is a packaging choice and
|
|
53
|
+
* must not change the security answer. The domains are RESERVED by the
|
|
54
|
+
* platform in every edition, shipped or not.
|
|
55
|
+
*
|
|
56
|
+
* This is also why the field is on the LOCK and not on `LockedBiome`: a
|
|
57
|
+
* reservation exists whether or not the owning biome appears in `biomes[]`.
|
|
58
|
+
*
|
|
59
|
+
* COLLISIONS FAIL FAST. Two biomes claiming the same domain is a packaging
|
|
60
|
+
* error that would make ownership ambiguous, so this class throws rather than
|
|
61
|
+
* picking a winner. The distribution resolver runs the same check before it
|
|
62
|
+
* writes, so the failure surfaces at build time; the boot-time throw is the
|
|
63
|
+
* backstop.
|
|
64
|
+
*
|
|
65
|
+
* UNDECIDABLE INPUT IS FIRST-PARTY, NOT THIRD-PARTY. `has()` on a string that
|
|
66
|
+
* is not a syntactically valid domain returns `true` (⇒ refuse the binding).
|
|
67
|
+
* A guard must never treat "I could not parse this" as "safe to shadow".
|
|
68
|
+
*/
|
|
69
|
+
export class FirstPartyCapabilityDomains {
|
|
70
|
+
private constructor(
|
|
71
|
+
private readonly owners: ReadonlyMap<CapabilityDomain, string>,
|
|
72
|
+
) {}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build the ownership index from a parsed distribution lock's
|
|
76
|
+
* `capabilityDomainOwners` reservation table.
|
|
77
|
+
*
|
|
78
|
+
* @throws when two biomes claim the same domain, or when a claimed value is
|
|
79
|
+
* not a syntactically valid capability domain. Both are packaging errors;
|
|
80
|
+
* neither may be tolerated silently in a security guard's input.
|
|
81
|
+
*/
|
|
82
|
+
static fromDistributionLock(
|
|
83
|
+
lock: DistributionLock,
|
|
84
|
+
): FirstPartyCapabilityDomains {
|
|
85
|
+
return FirstPartyCapabilityDomains.fromOwnerEntries(
|
|
86
|
+
lock.capabilityDomainOwners.map(
|
|
87
|
+
(owner) => [owner.domain, owner.biomeId] as const,
|
|
88
|
+
),
|
|
89
|
+
`Distribution "${lock.distributionId}"`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build the ownership index directly from `(domain → owning biome id)` pairs.
|
|
95
|
+
* For consumers that receive the ownership projection over the wire instead
|
|
96
|
+
* of reading the lock file themselves. Same validation, same collision rule.
|
|
97
|
+
*/
|
|
98
|
+
static fromOwnerEntries(
|
|
99
|
+
entries: Iterable<readonly [string, string]>,
|
|
100
|
+
sourceLabel: string,
|
|
101
|
+
): FirstPartyCapabilityDomains {
|
|
102
|
+
const owners = new Map<CapabilityDomain, string>();
|
|
103
|
+
for (const [claimed, biomeId] of entries) {
|
|
104
|
+
if (!isCapabilityDomain(claimed)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`${sourceLabel}: "${claimed}" is not a valid capability domain (^[a-z][a-z0-9-]*$).`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const incumbent = owners.get(claimed);
|
|
110
|
+
if (incumbent !== undefined && incumbent !== biomeId) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`${sourceLabel}: capability domain "${claimed}" is claimed by BOTH "${incumbent}" and "${biomeId}". ` +
|
|
113
|
+
'A domain has exactly one owner — ownership decides whether an org may bind refs in it, ' +
|
|
114
|
+
'so an ambiguous claim is refused rather than resolved to a winner.',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
owners.set(claimed, biomeId);
|
|
118
|
+
}
|
|
119
|
+
return new FirstPartyCapabilityDomains(owners);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `true` iff `domain` is owned by a biome in the active distribution — or if
|
|
124
|
+
* `domain` is not a parseable domain at all (undecidable ⇒ protected).
|
|
125
|
+
*/
|
|
126
|
+
has(domain: string): boolean {
|
|
127
|
+
return !isCapabilityDomain(domain) || this.owners.has(domain);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* `true` iff the DOMAIN OF `ref` is first-party. A ref whose domain cannot be
|
|
132
|
+
* extracted is undecidable and therefore protected.
|
|
133
|
+
*/
|
|
134
|
+
hasRefDomain(ref: string): boolean {
|
|
135
|
+
const domain = capabilityDomainOf(ref);
|
|
136
|
+
return domain === null || this.owners.has(domain);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The biome that owns `domain`, or `undefined` when nobody claims it. */
|
|
140
|
+
ownerOf(domain: string): string | undefined {
|
|
141
|
+
return this.owners.get(domain);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Claimed domains, sorted — for boot logs and diagnostics. */
|
|
145
|
+
get domains(): readonly CapabilityDomain[] {
|
|
146
|
+
return [...this.owners.keys()].sort();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
get size(): number {
|
|
150
|
+
return this.owners.size;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Convenience wrapper — `(domain → owning biome id)` for the whole lock, with
|
|
156
|
+
* the collision + grammar checks applied. The distribution resolver calls this
|
|
157
|
+
* at build time so a bad claim fails the release, not the deployment.
|
|
158
|
+
*/
|
|
159
|
+
export function resolveFirstPartyCapabilityDomains(
|
|
160
|
+
lock: DistributionLock,
|
|
161
|
+
): ReadonlyMap<CapabilityDomain, string> {
|
|
162
|
+
const resolved = FirstPartyCapabilityDomains.fromDistributionLock(lock);
|
|
163
|
+
return new Map(
|
|
164
|
+
resolved.domains.map((domain) => [domain, resolved.ownerOf(domain)!]),
|
|
165
|
+
);
|
|
166
|
+
}
|