@xemahq/kernel-contracts 0.7.0 → 0.9.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/agent-composition/index.d.ts +3 -3
- package/dist/agent-composition/index.d.ts.map +1 -1
- package/dist/agent-composition/index.js +3 -3
- package/dist/agent-composition/index.js.map +1 -1
- package/dist/agent-composition/lib/agent-limits-schema.d.ts +4 -0
- package/dist/agent-composition/lib/agent-limits-schema.d.ts.map +1 -0
- package/dist/agent-composition/lib/agent-limits-schema.js +13 -0
- package/dist/agent-composition/lib/agent-limits-schema.js.map +1 -0
- package/dist/agent-composition/lib/agent-workspace-config.d.ts +37 -0
- package/dist/agent-composition/lib/agent-workspace-config.d.ts.map +1 -0
- package/dist/agent-composition/lib/agent-workspace-config.js +9 -0
- package/dist/agent-composition/lib/agent-workspace-config.js.map +1 -0
- package/dist/agent-composition/lib/agent.d.ts +65 -0
- package/dist/agent-composition/lib/agent.d.ts.map +1 -0
- package/dist/agent-composition/lib/agent.js +18 -0
- package/dist/agent-composition/lib/agent.js.map +1 -0
- package/dist/agent-workspace/awp-spec.json +1 -1
- package/dist/biome/lib/biome-manifest.d.ts +880 -27
- package/dist/biome/lib/biome-manifest.d.ts.map +1 -1
- package/dist/biome/lib/biome-manifest.js +507 -20
- package/dist/biome/lib/biome-manifest.js.map +1 -1
- package/dist/distribution/lib/distribution-selector.d.ts +2 -0
- package/dist/distribution/lib/distribution-selector.d.ts.map +1 -1
- package/dist/distribution/lib/distribution-selector.js +6 -2
- package/dist/distribution/lib/distribution-selector.js.map +1 -1
- package/dist/resource/index.d.ts +7 -0
- package/dist/resource/index.d.ts.map +1 -0
- package/dist/resource/index.js +23 -0
- package/dist/resource/index.js.map +1 -0
- package/dist/resource/lib/plan.d.ts +31 -0
- package/dist/resource/lib/plan.d.ts.map +1 -0
- package/dist/resource/lib/plan.js +32 -0
- package/dist/resource/lib/plan.js.map +1 -0
- package/dist/resource/lib/resource-declaration.d.ts +15 -0
- package/dist/resource/lib/resource-declaration.d.ts.map +1 -0
- package/dist/resource/lib/resource-declaration.js +32 -0
- package/dist/resource/lib/resource-declaration.js.map +1 -0
- package/dist/resource/lib/resource-kind.d.ts +20 -0
- package/dist/resource/lib/resource-kind.d.ts.map +1 -0
- package/dist/resource/lib/resource-kind.js +24 -0
- package/dist/resource/lib/resource-kind.js.map +1 -0
- package/dist/resource/lib/resource-managed-by.d.ts +9 -0
- package/dist/resource/lib/resource-managed-by.d.ts.map +1 -0
- package/dist/resource/lib/resource-managed-by.js +13 -0
- package/dist/resource/lib/resource-managed-by.js.map +1 -0
- package/dist/resource/lib/stack.d.ts +22 -0
- package/dist/resource/lib/stack.d.ts.map +1 -0
- package/dist/resource/lib/stack.js +23 -0
- package/dist/resource/lib/stack.js.map +1 -0
- package/dist/resource/lib/xema-manifest.d.ts +11 -0
- package/dist/resource/lib/xema-manifest.d.ts.map +1 -0
- package/dist/resource/lib/xema-manifest.js +47 -0
- package/dist/resource/lib/xema-manifest.js.map +1 -0
- package/package.json +1 -1
- package/src/agent-composition/index.ts +3 -3
- package/src/agent-composition/lib/{composition-limits-schema.ts → agent-limits-schema.ts} +1 -1
- package/src/agent-composition/lib/{composition.ts → agent.ts} +1 -1
- package/src/biome/lib/biome-manifest.ts +1140 -66
- package/src/distribution/lib/distribution-selector.ts +16 -4
- package/src/invocation/lib/invoke-request.ts +1 -1
- package/src/invocation/lib/invoke-response.ts +1 -1
- package/src/resource/index.ts +22 -0
- package/src/resource/lib/plan.ts +83 -0
- package/src/resource/lib/resource-declaration.ts +67 -0
- package/src/resource/lib/resource-kind.ts +37 -0
- package/src/resource/lib/resource-managed-by.ts +29 -0
- package/src/resource/lib/stack.ts +61 -0
- package/src/resource/lib/xema-manifest.ts +86 -0
- /package/src/agent-composition/lib/{composition-workspace.ts → agent-workspace-config.ts} +0 -0
|
@@ -18,18 +18,30 @@ import {
|
|
|
18
18
|
* the ONLY form that can pull an `audience: operator` biome
|
|
19
19
|
* (the "force it in" mechanism for our internal distribution).
|
|
20
20
|
*
|
|
21
|
+
* **`mandatory`** (on the `{ tier }` and `{ biome }` forms; defaults `false`) is
|
|
22
|
+
* the DISTRIBUTION's force-on decision: a biome resolves to
|
|
23
|
+
* `LockedBiome.mandatory = true` (always-on, org cannot disable) iff ANY
|
|
24
|
+
* matching selector sets `mandatory: true`, OR it is kernel-tier (a platform
|
|
25
|
+
* invariant the resolver enforces regardless of selectors). Mandatory is NOT a
|
|
26
|
+
* property a biome self-declares in its manifest — the same biome can be
|
|
27
|
+
* force-on in one distribution and optional/installable in another.
|
|
28
|
+
*
|
|
21
29
|
* Selector matching is resolved against the global biome index; the resolver,
|
|
22
30
|
* not any consumer, applies these rules (the lockfile is the hard boundary).
|
|
23
31
|
*/
|
|
24
32
|
export type DistributionSelector =
|
|
25
|
-
| { tier: BiomeTier }
|
|
33
|
+
| { tier: BiomeTier; mandatory?: boolean }
|
|
26
34
|
| { origin: BiomeOrigin }
|
|
27
|
-
| { biome: string; version?: string };
|
|
35
|
+
| { biome: string; version?: string; mandatory?: boolean };
|
|
28
36
|
|
|
29
37
|
export const DistributionSelectorSchema = z.union([
|
|
30
|
-
z.object({ tier: BiomeTierSchema }).strict(),
|
|
38
|
+
z.object({ tier: BiomeTierSchema, mandatory: z.boolean().optional() }).strict(),
|
|
31
39
|
z.object({ origin: BiomeOriginSchema }).strict(),
|
|
32
40
|
z
|
|
33
|
-
.object({
|
|
41
|
+
.object({
|
|
42
|
+
biome: z.string().min(1),
|
|
43
|
+
version: z.string().min(1).optional(),
|
|
44
|
+
mandatory: z.boolean().optional(),
|
|
45
|
+
})
|
|
34
46
|
.strict(),
|
|
35
47
|
]) as z.ZodType<DistributionSelector>;
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from './execution-requirements';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* The wire body for `
|
|
8
|
+
* The wire body for `agent:invoke@1` — what a caller sends through the
|
|
9
9
|
* capability funnel to execute a published composition.
|
|
10
10
|
*
|
|
11
11
|
* `AgentInvokeRequestSchema` is the fail-fast validator the runtime
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from './invocation-decision';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* The response the runtime returns for a `
|
|
10
|
+
* The response the runtime returns for a `agent:invoke@1` call.
|
|
11
11
|
*
|
|
12
12
|
* In `Sync` mode `output` carries the composition root structured output. In
|
|
13
13
|
* `Event` mode `output` is omitted and the consumer subscribes to the result
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// @xemahq/kernel-contracts/resource — Xema-as-Code wire contracts.
|
|
3
|
+
//
|
|
4
|
+
// Pure types + Zod schemas for the declarative provisioning (IaC) surface of
|
|
5
|
+
// the tenant/runtime plane: the closed `XemaResourceKind` set, the
|
|
6
|
+
// `ResourceManagedBy` ownership marker, the `ResourceDeclaration` envelope and
|
|
7
|
+
// the `XemaManifest` (`xema.yaml`) document, plus the server-side `Stack` state
|
|
8
|
+
// and the `ResourcePlan` diff vocabulary.
|
|
9
|
+
//
|
|
10
|
+
// A DAG LEAF on purpose: per-kind `spec` bodies are validated by the owning
|
|
11
|
+
// domain service (which holds the authoritative DTO), so this surface drags no
|
|
12
|
+
// cross-subpath dependency into the kernel. The Terraform provider, the first-
|
|
13
|
+
// party `xema.yaml` apply, and the frontend all converge on these contracts.
|
|
14
|
+
// Zero runtime deps beyond `zod`.
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
|
|
17
|
+
export * from './lib/resource-kind';
|
|
18
|
+
export * from './lib/resource-managed-by';
|
|
19
|
+
export * from './lib/resource-declaration';
|
|
20
|
+
export * from './lib/xema-manifest';
|
|
21
|
+
export * from './lib/stack';
|
|
22
|
+
export * from './lib/plan';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { XemaResourceKind, XemaResourceKindSchema } from './resource-kind';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `ResourceChangeAction` — the closed set of actions a `plan` may attribute to
|
|
6
|
+
* a single resource, mirroring Terraform's plan vocabulary.
|
|
7
|
+
*
|
|
8
|
+
* - `Create` — declared, no physical resource yet.
|
|
9
|
+
* - `Update` — declared spec differs from the last-applied spec (in-place).
|
|
10
|
+
* - `Replace` — a change to an immutable field; the resource is destroyed and
|
|
11
|
+
* recreated (e.g. a slug/ref that the owning service treats as immutable).
|
|
12
|
+
* - `Delete` — present in the Stack mapping but absent from the new manifest.
|
|
13
|
+
* - `NoOp` — declared spec equals applied state; nothing to do.
|
|
14
|
+
*/
|
|
15
|
+
export enum ResourceChangeAction {
|
|
16
|
+
Create = 'create',
|
|
17
|
+
Update = 'update',
|
|
18
|
+
Replace = 'replace',
|
|
19
|
+
Delete = 'delete',
|
|
20
|
+
NoOp = 'noop',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ResourceChangeActionSchema = z.nativeEnum(ResourceChangeAction);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A per-field before/after, populated for `Update`/`Replace` so the plan is
|
|
27
|
+
* legible to a human reviewing `xema plan` (and to the FE drift view).
|
|
28
|
+
*/
|
|
29
|
+
export interface ResourceFieldDiff {
|
|
30
|
+
from: unknown;
|
|
31
|
+
to: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const ResourceFieldDiffSchema = z.object({
|
|
35
|
+
from: z.unknown(),
|
|
36
|
+
to: z.unknown(),
|
|
37
|
+
}) as z.ZodType<ResourceFieldDiff>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `ResourceChange` — one planned action against one resource.
|
|
41
|
+
*
|
|
42
|
+
* `driftFromActual` is true when the *current actual* state read back from the
|
|
43
|
+
* owning service differs from the Stack's last-applied state — i.e. someone
|
|
44
|
+
* changed it out-of-band (the frontend, a direct API call). This is what makes
|
|
45
|
+
* the behavior Terraform-exact: drift is surfaced at plan time, and `apply`
|
|
46
|
+
* reconciles the resource back to the declared spec.
|
|
47
|
+
*/
|
|
48
|
+
export interface ResourceChange {
|
|
49
|
+
kind: XemaResourceKind;
|
|
50
|
+
/** `"<kind>.<id>"` logical ref. */
|
|
51
|
+
logicalId: string;
|
|
52
|
+
action: ResourceChangeAction;
|
|
53
|
+
/** Present when the resource already has a physical identity. */
|
|
54
|
+
physicalId?: string;
|
|
55
|
+
driftFromActual?: boolean;
|
|
56
|
+
diff?: Record<string, ResourceFieldDiff>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const ResourceChangeSchema = z.object({
|
|
60
|
+
kind: XemaResourceKindSchema,
|
|
61
|
+
logicalId: z.string().min(1),
|
|
62
|
+
action: ResourceChangeActionSchema,
|
|
63
|
+
physicalId: z.string().min(1).optional(),
|
|
64
|
+
driftFromActual: z.boolean().optional(),
|
|
65
|
+
diff: z.record(z.string(), ResourceFieldDiffSchema).optional(),
|
|
66
|
+
}) as z.ZodType<ResourceChange>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `ResourcePlan` — the full diff produced by `POST /stacks/:name/plan`: the
|
|
70
|
+
* ordered set of changes plus whether any out-of-band drift was detected. An
|
|
71
|
+
* `apply` executes exactly this plan.
|
|
72
|
+
*/
|
|
73
|
+
export interface ResourcePlan {
|
|
74
|
+
stackName: string;
|
|
75
|
+
changes: readonly ResourceChange[];
|
|
76
|
+
driftDetected: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const ResourcePlanSchema = z.object({
|
|
80
|
+
stackName: z.string().min(1),
|
|
81
|
+
changes: z.array(ResourceChangeSchema).readonly(),
|
|
82
|
+
driftDetected: z.boolean(),
|
|
83
|
+
}) as z.ZodType<ResourcePlan>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { XemaResourceKind, XemaResourceKindSchema } from './resource-kind';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A logical reference to another declared resource within the same manifest,
|
|
6
|
+
* encoded as `"<kind>.<id>"` (e.g. `"project.web-app"`). Used by `dependsOn`
|
|
7
|
+
* to order an `apply` and to wire one resource's output into another's spec.
|
|
8
|
+
*/
|
|
9
|
+
export type ResourceLogicalRef = string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `ResourceDeclaration` — one resource entry in a `XemaManifest`. The `spec`
|
|
13
|
+
* is intentionally opaque at the kernel layer (`Record<string, unknown>`): the
|
|
14
|
+
* owning domain service holds the authoritative per-kind schema and validates
|
|
15
|
+
* it at the wire. This keeps the `resource` subpath a DAG leaf while still
|
|
16
|
+
* giving the control-plane a stable envelope to plan/diff against.
|
|
17
|
+
*
|
|
18
|
+
* - `id` — the author-chosen logical id, unique per `kind` within a
|
|
19
|
+
* manifest. Stable across applies; this is what the Stack maps to a physical
|
|
20
|
+
* id. Never the server-generated UUID.
|
|
21
|
+
* - `managedKey` — the natural key the owning service upserts on. Defaults to
|
|
22
|
+
* `id` when omitted. For resources whose natural key is already a slug/ref
|
|
23
|
+
* (space ref, skill slug, environment slug) the author may set it explicitly.
|
|
24
|
+
* - `dependsOn` — `"<kind>.<id>"` refs that must be applied first.
|
|
25
|
+
*/
|
|
26
|
+
export interface ResourceDeclaration {
|
|
27
|
+
kind: XemaResourceKind;
|
|
28
|
+
id: string;
|
|
29
|
+
managedKey?: string;
|
|
30
|
+
dependsOn?: readonly ResourceLogicalRef[];
|
|
31
|
+
spec: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const LOGICAL_REF_RE = /^[a-z0-9-]+\.[A-Za-z0-9._-]+$/;
|
|
35
|
+
|
|
36
|
+
export const ResourceLogicalRefSchema = z
|
|
37
|
+
.string()
|
|
38
|
+
.regex(LOGICAL_REF_RE, {
|
|
39
|
+
message:
|
|
40
|
+
'A resource ref must be "<kind>.<id>" (e.g. "project.web-app").',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const ResourceDeclarationSchema = z.object({
|
|
44
|
+
kind: XemaResourceKindSchema,
|
|
45
|
+
id: z
|
|
46
|
+
.string()
|
|
47
|
+
.min(1)
|
|
48
|
+
.regex(/^[A-Za-z0-9._-]+$/, {
|
|
49
|
+
message:
|
|
50
|
+
'ResourceDeclaration.id must be a stable slug ([A-Za-z0-9._-]+).',
|
|
51
|
+
}),
|
|
52
|
+
managedKey: z.string().min(1).optional(),
|
|
53
|
+
dependsOn: z.array(ResourceLogicalRefSchema).readonly().optional(),
|
|
54
|
+
spec: z.record(z.string(), z.unknown()),
|
|
55
|
+
}) as z.ZodType<ResourceDeclaration>;
|
|
56
|
+
|
|
57
|
+
/** The `"<kind>.<id>"` logical ref for a declaration. */
|
|
58
|
+
export function logicalRefOf(
|
|
59
|
+
decl: Pick<ResourceDeclaration, 'kind' | 'id'>,
|
|
60
|
+
): ResourceLogicalRef {
|
|
61
|
+
return `${decl.kind}.${decl.id}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The natural key a declaration upserts on (`managedKey` or `id`). */
|
|
65
|
+
export function managedKeyOf(decl: ResourceDeclaration): string {
|
|
66
|
+
return decl.managedKey ?? decl.id;
|
|
67
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `XemaResourceKind` — the closed set of platform resources that can be
|
|
5
|
+
* declared as code (Terraform / first-party `xema.yaml`) and reconciled by the
|
|
6
|
+
* control-plane. Each value is the canonical, wire-stable slug for the resource
|
|
7
|
+
* kind; the Terraform provider exposes it as `xema_<kind_with_underscores>`
|
|
8
|
+
* (e.g. `model-resolution-rule` → `xema_model_resolution_rule`).
|
|
9
|
+
*
|
|
10
|
+
* This is intentionally a LEAF surface: the kinds are plain slugs and the
|
|
11
|
+
* per-kind `spec` body is validated by the owning domain service (which holds
|
|
12
|
+
* the authoritative DTO), NOT here — so adding a resource never drags a
|
|
13
|
+
* cross-subpath dependency into the kernel. Extending the set is a one-line PR.
|
|
14
|
+
*
|
|
15
|
+
* The set mirrors the provisionable resource inventory of the tenant/runtime
|
|
16
|
+
* plane (see the Xema-as-Code plan). The operator/packaging plane
|
|
17
|
+
* (`Distribution`) is a SEPARATE surface and is not a member here.
|
|
18
|
+
*/
|
|
19
|
+
export enum XemaResourceKind {
|
|
20
|
+
Org = 'org',
|
|
21
|
+
Project = 'project',
|
|
22
|
+
Portal = 'portal',
|
|
23
|
+
BiomeInstall = 'biome-install',
|
|
24
|
+
Provider = 'provider',
|
|
25
|
+
Model = 'model',
|
|
26
|
+
ModelResolutionRule = 'model-resolution-rule',
|
|
27
|
+
Skill = 'skill',
|
|
28
|
+
Agent = 'agent',
|
|
29
|
+
Role = 'role',
|
|
30
|
+
Grant = 'grant',
|
|
31
|
+
Space = 'space',
|
|
32
|
+
Environment = 'environment',
|
|
33
|
+
Team = 'team',
|
|
34
|
+
DeliverableSpec = 'deliverable-spec',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const XemaResourceKindSchema = z.nativeEnum(XemaResourceKind);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `ResourceManagedBy` — the ownership marker stamped on every provisionable
|
|
5
|
+
* resource row. This single concept powers three behaviors with one switch:
|
|
6
|
+
*
|
|
7
|
+
* - `Seeder` — owned by a boot/event reconciler (e.g. portals derived from
|
|
8
|
+
* installed web biomes). The reconciler may create/update/retire these.
|
|
9
|
+
* Retire-removed deletes ONLY `Seeder`-owned rows whose `managedKey` no
|
|
10
|
+
* longer appears in the desired set.
|
|
11
|
+
* - `Iac` — owned by a declarative source (Terraform / `xema.yaml`).
|
|
12
|
+
* Declared-state-wins: `plan` surfaces drift, `apply` reconciles back. No
|
|
13
|
+
* reconciler touches a row it does not own.
|
|
14
|
+
* - `Ui` — created/edited through the frontend (or adopted away from a
|
|
15
|
+
* `Seeder`/`Iac` owner when a user customizes it). Never auto-reverted.
|
|
16
|
+
* - `System` — kernel/platform-shipped and immutable from any provisioning
|
|
17
|
+
* surface.
|
|
18
|
+
*
|
|
19
|
+
* Adoption (`Seeder`/`Iac` → `Ui`) is the explicit "I now own this by hand"
|
|
20
|
+
* action; it is the inverse of `import` (`Ui` → `Iac`).
|
|
21
|
+
*/
|
|
22
|
+
export enum ResourceManagedBy {
|
|
23
|
+
Ui = 'ui',
|
|
24
|
+
Iac = 'iac',
|
|
25
|
+
Seeder = 'seeder',
|
|
26
|
+
System = 'system',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const ResourceManagedBySchema = z.nativeEnum(ResourceManagedBy);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { XemaResourceKind, XemaResourceKindSchema } from './resource-kind';
|
|
3
|
+
import { XemaManifest, XemaManifestSchema } from './xema-manifest';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `ResourceMapping` — one row of a Stack's logical→physical binding, the
|
|
7
|
+
* server-side equivalent of a single entry in a Terraform/CloudFormation state
|
|
8
|
+
* file. It records which physical (server-generated) resource a declared
|
|
9
|
+
* logical id resolved to, plus the content hash of the spec that was last
|
|
10
|
+
* applied — the basis for drift detection on the native `xema.yaml` path.
|
|
11
|
+
*/
|
|
12
|
+
export interface ResourceMapping {
|
|
13
|
+
/** `"<kind>.<id>"` — the manifest logical ref. */
|
|
14
|
+
logicalId: string;
|
|
15
|
+
kind: XemaResourceKind;
|
|
16
|
+
/** The server-generated identity of the materialized resource. */
|
|
17
|
+
physicalId: string;
|
|
18
|
+
/** The natural key the owning service upserts on. */
|
|
19
|
+
managedKey: string;
|
|
20
|
+
/** sha256 of the spec last successfully applied for this resource. */
|
|
21
|
+
lastAppliedHash: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const ResourceMappingSchema = z.object({
|
|
25
|
+
logicalId: z.string().min(1),
|
|
26
|
+
kind: XemaResourceKindSchema,
|
|
27
|
+
physicalId: z.string().min(1),
|
|
28
|
+
managedKey: z.string().min(1),
|
|
29
|
+
lastAppliedHash: z.string().min(1),
|
|
30
|
+
}) as z.ZodType<ResourceMapping>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `Stack` — a named, org-scoped declarative state. It is the CloudFormation
|
|
34
|
+
* stack / Terraform state-file analogue that control-plane-api persists for the
|
|
35
|
+
* first-party `xema.yaml` path: the last-applied manifest plus the logical→
|
|
36
|
+
* physical mappings used to plan the next apply and detect out-of-band drift.
|
|
37
|
+
*
|
|
38
|
+
* The Terraform provider does NOT use a Stack — Terraform Core owns its own
|
|
39
|
+
* state. A Stack exists only for the native YAML / FE export-import surface.
|
|
40
|
+
*/
|
|
41
|
+
export interface Stack {
|
|
42
|
+
schemaVersion: 1;
|
|
43
|
+
stackId: string;
|
|
44
|
+
orgId: string;
|
|
45
|
+
name: string;
|
|
46
|
+
/** The manifest from the most recent successful apply (absent before first). */
|
|
47
|
+
lastAppliedManifest?: XemaManifest;
|
|
48
|
+
mappings: readonly ResourceMapping[];
|
|
49
|
+
/** ISO-8601 timestamp of the last apply. */
|
|
50
|
+
updatedAt?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const StackSchema = z.object({
|
|
54
|
+
schemaVersion: z.literal(1),
|
|
55
|
+
stackId: z.string().min(1),
|
|
56
|
+
orgId: z.string().min(1),
|
|
57
|
+
name: z.string().min(1),
|
|
58
|
+
lastAppliedManifest: XemaManifestSchema.optional(),
|
|
59
|
+
mappings: z.array(ResourceMappingSchema).readonly(),
|
|
60
|
+
updatedAt: z.string().min(1).optional(),
|
|
61
|
+
}) as z.ZodType<Stack>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
ResourceDeclaration,
|
|
4
|
+
ResourceDeclarationSchema,
|
|
5
|
+
logicalRefOf,
|
|
6
|
+
} from './resource-declaration';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema-versioning seed for the first-party `xema.yaml` document. Bumped via a
|
|
10
|
+
* coordinated PR on an incompatible shape change; consumers refuse versions
|
|
11
|
+
* they do not understand (fail-fast — never best-effort parse). Mirrors the
|
|
12
|
+
* `Distribution` / `AppLockfile` versioning discipline.
|
|
13
|
+
*/
|
|
14
|
+
export type XemaManifestApiVersion = 'xema/v1';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `XemaManifest` — the top-level shape of a first-party `xema.yaml`: a
|
|
18
|
+
* declarative description of a set of platform resources for one org, applied
|
|
19
|
+
* as a named Stack. This is the CloudFormation-equivalent document; the
|
|
20
|
+
* Terraform provider expresses the same resources as HCL and does not use this
|
|
21
|
+
* shape (Terraform Core owns its own state), but both converge on the same
|
|
22
|
+
* resource APIs and the same `ResourceDeclaration` envelope.
|
|
23
|
+
*
|
|
24
|
+
* `stack` is the logical state name. When omitted, the applying surface (the
|
|
25
|
+
* `xema` CLI or control-plane-api) supplies it (e.g. from `--stack`). The org
|
|
26
|
+
* is NEVER encoded in the document — it comes from the authenticated apply
|
|
27
|
+
* context, so the same manifest is portable across orgs.
|
|
28
|
+
*/
|
|
29
|
+
export interface XemaManifest {
|
|
30
|
+
apiVersion: XemaManifestApiVersion;
|
|
31
|
+
stack?: string;
|
|
32
|
+
resources: readonly ResourceDeclaration[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const XemaManifestSchema = z
|
|
36
|
+
.object({
|
|
37
|
+
apiVersion: z.literal('xema/v1'),
|
|
38
|
+
stack: z.string().min(1).optional(),
|
|
39
|
+
resources: z.array(ResourceDeclarationSchema).readonly(),
|
|
40
|
+
})
|
|
41
|
+
.superRefine((manifest, ctx) => {
|
|
42
|
+
// (kind, id) must be unique — it is the join key the Stack maps to a
|
|
43
|
+
// physical id. A duplicate would make the mapping ambiguous.
|
|
44
|
+
const seen = new Set<string>();
|
|
45
|
+
manifest.resources.forEach((decl, index) => {
|
|
46
|
+
const ref = logicalRefOf(decl);
|
|
47
|
+
if (seen.has(ref)) {
|
|
48
|
+
ctx.addIssue({
|
|
49
|
+
code: z.ZodIssueCode.custom,
|
|
50
|
+
path: ['resources', index],
|
|
51
|
+
message: `Duplicate resource ref "${ref}" — every (kind, id) must be unique within a manifest.`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
seen.add(ref);
|
|
55
|
+
});
|
|
56
|
+
// dependsOn refs must resolve within the manifest — fail-fast on a dangling
|
|
57
|
+
// dependency rather than silently dropping ordering.
|
|
58
|
+
manifest.resources.forEach((decl, index) => {
|
|
59
|
+
for (const dep of decl.dependsOn ?? []) {
|
|
60
|
+
if (!seen.has(dep)) {
|
|
61
|
+
ctx.addIssue({
|
|
62
|
+
code: z.ZodIssueCode.custom,
|
|
63
|
+
path: ['resources', index, 'dependsOn'],
|
|
64
|
+
message: `dependsOn "${dep}" does not match any resource declared in this manifest.`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}) as unknown as z.ZodType<XemaManifest>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse + validate a raw `xema.yaml` document. Fail-fast on any shape error,
|
|
73
|
+
* duplicate ref, or dangling dependency — never best-effort. Mirrors
|
|
74
|
+
* `parseDistribution`.
|
|
75
|
+
*/
|
|
76
|
+
export function parseXemaManifest(raw: unknown): XemaManifest {
|
|
77
|
+
const result = XemaManifestSchema.safeParse(raw);
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Invalid xema.yaml: ${result.error.issues
|
|
81
|
+
.map((i) => `[${i.path.join('.')}] ${i.message}`)
|
|
82
|
+
.join('; ')}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return result.data;
|
|
86
|
+
}
|
|
File without changes
|