From f25cff7850bc5a90bbd7efd38e10f08681019ce2 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 8 Apr 2026 17:49:09 +0200 Subject: [PATCH 01/17] feat: extend plugin and template manifest schemas with discovery, postScaffold, and scaffolding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Xavier loop: iteration 1 — Phase 1 (Schema Definitions & Type Generation) Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../src/cli/commands/plugin/manifest-types.ts | 30 ++++- packages/shared/src/plugin.ts | 4 +- .../src/schemas/plugin-manifest.generated.ts | 74 ++++++++++++ .../src/schemas/plugin-manifest.schema.json | 65 +++++++++++ .../src/schemas/template-plugins.schema.json | 105 +++++++++++++++++- 5 files changed, 274 insertions(+), 4 deletions(-) diff --git a/packages/shared/src/cli/commands/plugin/manifest-types.ts b/packages/shared/src/cli/commands/plugin/manifest-types.ts index 8f649036f..7a0f9a85e 100644 --- a/packages/shared/src/cli/commands/plugin/manifest-types.ts +++ b/packages/shared/src/cli/commands/plugin/manifest-types.ts @@ -6,12 +6,37 @@ */ export type { + DiscoveryDescriptor, PluginManifest, + PostScaffoldStep, ResourceFieldEntry, ResourceRequirement, } from "../../../schemas/plugin-manifest.generated"; -import type { PluginManifest } from "../../../schemas/plugin-manifest.generated"; +import type { + PluginManifest, + PostScaffoldStep, +} from "../../../schemas/plugin-manifest.generated"; + +export interface ScaffoldingFlag { + description: string; + required?: boolean; + pattern?: string; + default?: string; +} + +export interface ScaffoldingRules { + never?: string[]; + must?: string[]; +} + +export interface ScaffoldingDescriptor { + command: string; + flags?: Record; + rules?: ScaffoldingRules; +} + +export type Origin = "user" | "platform" | "static" | "cli"; export interface TemplatePlugin extends Omit { package: string; @@ -19,10 +44,13 @@ export interface TemplatePlugin extends Omit { requiredByTemplate?: boolean; /** Plugin stability level. Absent or undefined means "ga" (general availability). */ stability?: "beta" | "ga"; + /** Ordered list of post-scaffolding instructions propagated from the plugin manifest. */ + postScaffold?: PostScaffoldStep[]; } export interface TemplatePluginsManifest { $schema: string; version: string; plugins: Record; + scaffolding?: ScaffoldingDescriptor; } diff --git a/packages/shared/src/plugin.ts b/packages/shared/src/plugin.ts index 651840c7b..0ac0375d4 100644 --- a/packages/shared/src/plugin.ts +++ b/packages/shared/src/plugin.ts @@ -1,13 +1,15 @@ import type express from "express"; import type { JSONSchema7 } from "json-schema"; import type { + DiscoveryDescriptor, PluginManifest as GeneratedPluginManifest, ResourceRequirement as GeneratedResourceRequirement, + PostScaffoldStep, ResourceFieldEntry, } from "./schemas/plugin-manifest.generated"; // Re-export generated types as the shared canonical definitions. -export type { ResourceFieldEntry }; +export type { ResourceFieldEntry, DiscoveryDescriptor, PostScaffoldStep }; /** Base plugin interface. */ export interface BasePlugin { diff --git a/packages/shared/src/schemas/plugin-manifest.generated.ts b/packages/shared/src/schemas/plugin-manifest.generated.ts index dd30f27d3..812a73fc4 100644 --- a/packages/shared/src/schemas/plugin-manifest.generated.ts +++ b/packages/shared/src/schemas/plugin-manifest.generated.ts @@ -218,6 +218,10 @@ export interface PluginManifest { * Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly. */ stability?: "beta" | "ga"; + /** + * Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order. + */ + postScaffold?: PostScaffoldStep[]; } /** * Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key). @@ -254,6 +258,32 @@ export interface ResourceFieldEntry { * Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow. */ resolve?: string; + discovery?: DiscoveryDescriptor; +} +/** + * How the CLI discovers values for this field via a Databricks CLI command. + */ +export interface DiscoveryDescriptor { + /** + * Databricks CLI command that lists resources. Must include placeholder. + */ + cliCommand: string; + /** + * jq-style path to the field used as the selected value (e.g., '.id', '.name'). + */ + selectField: string; + /** + * jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted. + */ + displayField?: string; + /** + * Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields. + */ + dependsOn?: string; + /** + * Single-value fast-path command that returns exactly one value, skipping interactive selection. + */ + shortcut?: string; } /** * This interface was referenced by `PluginManifest`'s JSON-Schema @@ -287,3 +317,47 @@ export interface ConfigSchemaProperty { maxLength?: number; required?: string[]; } +/** + * A post-scaffolding instruction shown to the user after project initialization. + * + * This interface was referenced by `PluginManifest`'s JSON-Schema + * via the `definition` "postScaffoldStep". + */ +export interface PostScaffoldStep { + /** + * Human-readable instruction for the user to follow after scaffolding. + */ + instruction: string; + /** + * Whether this step is required for the plugin to function correctly. + */ + required?: boolean; +} +/** + * Describes how the CLI discovers values for a resource field via a Databricks CLI command. + * + * This interface was referenced by `PluginManifest`'s JSON-Schema + * via the `definition` "discoveryDescriptor". + */ +export interface DiscoveryDescriptor1 { + /** + * Databricks CLI command that lists resources. Must include placeholder. + */ + cliCommand: string; + /** + * jq-style path to the field used as the selected value (e.g., '.id', '.name'). + */ + selectField: string; + /** + * jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted. + */ + displayField?: string; + /** + * Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields. + */ + dependsOn?: string; + /** + * Single-value fast-path command that returns exactly one value, skipping interactive selection. + */ + shortcut?: string; +} diff --git a/packages/shared/src/schemas/plugin-manifest.schema.json b/packages/shared/src/schemas/plugin-manifest.schema.json index 021c7f0bf..ad6f386fe 100644 --- a/packages/shared/src/schemas/plugin-manifest.schema.json +++ b/packages/shared/src/schemas/plugin-manifest.schema.json @@ -101,6 +101,13 @@ "enum": ["beta", "ga"], "default": "ga", "description": "Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly." + }, + "postScaffold": { + "type": "array", + "items": { + "$ref": "#/$defs/postScaffoldStep" + }, + "description": "Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order." } }, "additionalProperties": false, @@ -226,6 +233,10 @@ "type": "string", "pattern": "^[a-z_]+:[a-zA-Z]+$", "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow." + }, + "discovery": { + "$ref": "#/$defs/discoveryDescriptor", + "description": "How the CLI discovers values for this field via a Databricks CLI command." } }, "additionalProperties": false @@ -484,6 +495,60 @@ "type": "boolean" } } + }, + "discoveryDescriptor": { + "type": "object", + "description": "Describes how the CLI discovers values for a resource field via a Databricks CLI command.", + "required": ["cliCommand", "selectField"], + "properties": { + "cliCommand": { + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "examples": [ + "databricks warehouses list --profile --output json" + ] + }, + "selectField": { + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "examples": [".id", ".name", ".catalog_name"] + }, + "displayField": { + "type": "string", + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "examples": [".name", ".display_name"] + }, + "dependsOn": { + "type": "string", + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "examples": ["branch", "catalog"] + }, + "shortcut": { + "type": "string", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "examples": [ + "databricks warehouses get --profile --output json" + ] + } + }, + "additionalProperties": false + }, + "postScaffoldStep": { + "type": "object", + "description": "A post-scaffolding instruction shown to the user after project initialization.", + "required": ["instruction"], + "properties": { + "instruction": { + "type": "string", + "description": "Human-readable instruction for the user to follow after scaffolding." + }, + "required": { + "type": "boolean", + "default": true, + "description": "Whether this step is required for the plugin to function correctly." + } + }, + "additionalProperties": false } } } diff --git a/packages/shared/src/schemas/template-plugins.schema.json b/packages/shared/src/schemas/template-plugins.schema.json index 47d385936..95d07f30b 100644 --- a/packages/shared/src/schemas/template-plugins.schema.json +++ b/packages/shared/src/schemas/template-plugins.schema.json @@ -12,7 +12,7 @@ }, "version": { "type": "string", - "enum": ["1.0", "1.1"], + "enum": ["1.0", "1.1", "2.0"], "description": "Schema version for the template plugins manifest" }, "plugins": { @@ -21,9 +21,24 @@ "additionalProperties": { "$ref": "#/$defs/templatePlugin" } + }, + "scaffolding": { + "$ref": "#/$defs/scaffoldingDescriptor", + "description": "Describes the scaffolding command and its configuration for project initialization." } }, "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { "version": { "const": "2.0" } }, + "required": ["version"] + }, + "then": { + "required": ["version", "plugins", "scaffolding"] + } + } + ], "$defs": { "templatePlugin": { "type": "object", @@ -75,6 +90,13 @@ "default": "ga", "description": "Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver." }, + "postScaffold": { + "type": "array", + "items": { + "$ref": "plugin-manifest.schema.json#/$defs/postScaffoldStep" + }, + "description": "Ordered list of post-scaffolding instructions propagated from the plugin manifest." + }, "resources": { "type": "object", "required": ["required", "optional"], @@ -104,10 +126,89 @@ "$ref": "plugin-manifest.schema.json#/$defs/resourceType" }, "resourceFieldEntry": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceFieldEntry" + "allOf": [ + { "$ref": "plugin-manifest.schema.json#/$defs/resourceFieldEntry" }, + { + "properties": { + "origin": { + "$ref": "#/$defs/origin" + } + } + } + ] }, "resourceRequirement": { "$ref": "plugin-manifest.schema.json#/$defs/resourceRequirement" + }, + "origin": { + "type": "string", + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." + }, + "scaffoldingFlag": { + "type": "object", + "description": "A flag for the scaffolding command.", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "description": "Human-readable description of the flag." + }, + "required": { + "type": "boolean", + "default": false, + "description": "Whether this flag is required." + }, + "pattern": { + "type": "string", + "description": "Regex pattern for validating the flag value." + }, + "default": { + "type": "string", + "description": "Default value for this flag." + } + }, + "additionalProperties": false + }, + "scaffoldingRules": { + "type": "object", + "description": "Structured rules for scaffolding agents.", + "properties": { + "never": { + "type": "array", + "items": { "type": "string" }, + "description": "Actions the scaffolding agent must never perform." + }, + "must": { + "type": "array", + "items": { "type": "string" }, + "description": "Actions the scaffolding agent must always perform." + } + }, + "additionalProperties": false + }, + "scaffoldingDescriptor": { + "type": "object", + "description": "Describes the scaffolding command, flags, and rules for project initialization.", + "required": ["command"], + "properties": { + "command": { + "type": "string", + "description": "The scaffolding command (e.g., 'databricks apps init')." + }, + "flags": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/scaffoldingFlag" + }, + "description": "Map of flag name to flag descriptor." + }, + "rules": { + "$ref": "#/$defs/scaffoldingRules", + "description": "Structured rules for scaffolding agents." + } + }, + "additionalProperties": false } } } From 977ca56493d476e27ecef7f0b4a8d55239640951 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 8 Apr 2026 17:54:15 +0200 Subject: [PATCH 02/17] feat: add origin computation, scaffolding descriptor, and v2.0 template manifest emission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Xavier loop: iteration 2 — Phase 2 (Origin Computation & Sync Enrichment) Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../src/cli/commands/plugin/sync/sync.test.ts | 45 ++++++++++ .../src/cli/commands/plugin/sync/sync.ts | 89 ++++++++++++++++++- template/appkit.plugins.json | 61 ++++++++++--- 3 files changed, 182 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.test.ts b/packages/shared/src/cli/commands/plugin/sync/sync.test.ts index 64eec572b..af3805363 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.test.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.test.ts @@ -2,6 +2,7 @@ import path from "node:path"; import { Lang, parse } from "@ast-grep/napi"; import { describe, expect, it } from "vitest"; import { + computeOrigin, isWithinDirectory, parseImports, parsePluginUsages, @@ -182,4 +183,48 @@ describe("plugin sync", () => { expect(shouldAllowJsManifestForPackage("@acme/plugin")).toBe(false); }); }); + + describe("computeOrigin", () => { + it("returns 'platform' when localOnly is true", () => { + expect(computeOrigin({ env: "PGHOST", localOnly: true })).toBe( + "platform", + ); + }); + + it("returns 'platform' when localOnly is true even with resolve", () => { + expect( + computeOrigin({ + env: "PGHOST", + localOnly: true, + resolve: "postgres:host", + }), + ).toBe("platform"); + }); + + it("returns 'static' when value is present", () => { + expect(computeOrigin({ env: "PGPORT", value: "5432" })).toBe("static"); + }); + + it("returns 'cli' when resolve is present", () => { + expect( + computeOrigin({ + env: "LAKEBASE_ENDPOINT", + resolve: "postgres:endpointPath", + }), + ).toBe("cli"); + }); + + it("returns 'user' for fields with no special properties", () => { + expect( + computeOrigin({ + env: "DATABRICKS_WAREHOUSE_ID", + description: "Warehouse ID", + }), + ).toBe("user"); + }); + + it("returns 'user' for minimal field with only env", () => { + expect(computeOrigin({ env: "MY_VAR" })).toBe("user"); + }); + }); }); diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.ts b/packages/shared/src/cli/commands/plugin/sync/sync.ts index 10258c81a..e5f9b6c94 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.ts @@ -8,7 +8,10 @@ import { resolveManifestInDir, } from "../manifest-resolve"; import type { + Origin, PluginManifest, + ResourceFieldEntry, + ScaffoldingDescriptor, TemplatePlugin, TemplatePluginsManifest, } from "../manifest-types"; @@ -36,6 +39,43 @@ function isWithinDirectory(filePath: string, boundary: string): boolean { ); } +/** + * Derives the origin of a resource field value based on its properties. + * - localOnly: true → "platform" (auto-injected by Databricks Apps platform) + * - value present → "static" (hardcoded value) + * - resolve present → "cli" (resolved by CLI during init) + * - else → "user" (user must provide the value) + */ +function computeOrigin(field: ResourceFieldEntry): Origin { + if (field.localOnly) return "platform"; + if (field.value !== undefined) return "static"; + if (field.resolve !== undefined) return "cli"; + return "user"; +} + +/** + * Injects computed `origin` onto every resource field in all plugins. + * Mutates the plugins object in place for efficiency. + */ +function enrichFieldsWithOrigin( + plugins: TemplatePluginsManifest["plugins"], +): void { + for (const plugin of Object.values(plugins)) { + for (const group of [ + plugin.resources.required, + plugin.resources.optional, + ]) { + for (const resource of group) { + if (!resource.fields) continue; + for (const field of Object.values(resource.fields)) { + (field as ResourceFieldEntry & { origin?: Origin }).origin = + computeOrigin(field); + } + } + } + } +} + /** * Validates a parsed JSON object against the plugin-manifest JSON schema. * Returns the manifest if valid, or null and logs schema errors. @@ -93,6 +133,9 @@ async function loadPluginEntry( manifest.stability !== "ga" && { stability: manifest.stability, }), + ...(manifest.postScaffold && { + postScaffold: manifest.postScaffold, + }), }, ]; } @@ -116,6 +159,40 @@ const SERVER_FILE_CANDIDATES = ["server/server.ts", "server/index.ts"]; */ const CONVENTIONAL_LOCAL_PLUGIN_DIRS = ["plugins", "server"]; +/** + * Scaffolding descriptor for the `databricks apps init` command. + * Included in v2.0 template manifests to guide scaffolding agents. + */ +const TEMPLATE_SCAFFOLDING: ScaffoldingDescriptor = { + command: "databricks apps init", + flags: { + "--template-dir": { + description: "Path to the template directory containing the app scaffold", + required: true, + }, + "--config-dir": { + description: "Path to the output directory for the initialized app", + required: true, + }, + "--profile": { + description: "Databricks CLI profile to use for authentication", + required: false, + }, + }, + rules: { + never: [ + "Modify files inside the template directory", + "Skip resource configuration prompts", + "Hardcode workspace-specific values in template files", + ], + must: [ + "Use the template manifest (appkit.plugins.json) as the source of truth for available plugins", + "Respect requiredByTemplate flags when presenting plugin selection", + "Generate .env files with all required environment variables from selected plugins", + ], + }, +}; + /** * Find the server entry file by checking candidate paths in order. * @@ -426,6 +503,9 @@ async function scanForPlugins( manifest.stability !== "ga" && { stability: manifest.stability, }), + ...(manifest.postScaffold && { + postScaffold: manifest.postScaffold, + }), } satisfies TemplatePlugin; } } @@ -535,11 +615,15 @@ function writeManifest( { plugins }: { plugins: TemplatePluginsManifest["plugins"] }, options: { write?: boolean; silent?: boolean; json?: boolean }, ) { + // Enrich fields with computed origin for v2.0 + enrichFieldsWithOrigin(plugins); + const templateManifest: TemplatePluginsManifest = { $schema: "https://databricks.github.io/appkit/schemas/template-plugins.schema.json", - version: "1.1", + version: "2.0", plugins, + scaffolding: TEMPLATE_SCAFFOLDING, }; const serialized = JSON.stringify(templateManifest, null, 2); @@ -826,8 +910,9 @@ async function runPluginsSync(options: { writeManifest(outputPath, { plugins }, options); } -/** Exported for testing: path boundary check, AST parsing, trust checks. */ +/** Exported for testing: path boundary check, AST parsing, trust checks, origin computation. */ export { + computeOrigin, isWithinDirectory, parseImports, parsePluginUsages, diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index d3c8702f9..9c89068c4 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -1,6 +1,6 @@ { "$schema": "https://databricks.github.io/appkit/schemas/template-plugins.schema.json", - "version": "1.1", + "version": "2.0", "plugins": { "analytics": { "name": "analytics", @@ -18,7 +18,8 @@ "fields": { "id": { "env": "DATABRICKS_WAREHOUSE_ID", - "description": "SQL Warehouse ID" + "description": "SQL Warehouse ID", + "origin": "user" } } } @@ -42,7 +43,8 @@ "fields": { "path": { "env": "DATABRICKS_VOLUME_FILES", - "description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)" + "description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)", + "origin": "user" } } } @@ -66,7 +68,8 @@ "fields": { "id": { "env": "DATABRICKS_GENIE_SPACE_ID", - "description": "Default Genie Space ID" + "description": "Default Genie Space ID", + "origin": "user" } } } @@ -116,25 +119,29 @@ "description": "Full Lakebase Postgres branch resource name. Obtain by running `databricks postgres list-branches projects/{project-id}`, select the desired item from the output array and use its .name value.", "examples": [ "projects/{project-id}/branches/{branch-id}" - ] + ], + "origin": "user" }, "database": { "description": "Full Lakebase Postgres database resource name. Obtain by running `databricks postgres list-databases {branch-name}`, select the desired item from the output array and use its .name value. Requires the branch resource name.", "examples": [ "projects/{project-id}/branches/{branch-id}/databases/{database-id}" - ] + ], + "origin": "user" }, "host": { "env": "PGHOST", "localOnly": true, "resolve": "postgres:host", - "description": "Postgres host for local development. Auto-injected by the platform at deploy time." + "description": "Postgres host for local development. Auto-injected by the platform at deploy time.", + "origin": "platform" }, "databaseName": { "env": "PGDATABASE", "localOnly": true, "resolve": "postgres:databaseName", - "description": "Postgres database name for local development. Auto-injected by the platform at deploy time." + "description": "Postgres database name for local development. Auto-injected by the platform at deploy time.", + "origin": "platform" }, "endpointPath": { "env": "LAKEBASE_ENDPOINT", @@ -143,19 +150,22 @@ "description": "Lakebase endpoint resource name. Auto-injected at runtime via app.yaml valueFrom: postgres. For local development, obtain by running `databricks postgres list-endpoints {branch-name}`, select the desired item from the output array and use its .name value.", "examples": [ "projects/{project-id}/branches/{branch-id}/endpoints/{endpoint-id}" - ] + ], + "origin": "cli" }, "port": { "env": "PGPORT", "localOnly": true, "value": "5432", - "description": "Postgres port. Auto-injected by the platform at deploy time." + "description": "Postgres port. Auto-injected by the platform at deploy time.", + "origin": "platform" }, "sslmode": { "env": "PGSSLMODE", "localOnly": true, "value": "require", - "description": "Postgres SSL mode. Auto-injected by the platform at deploy time." + "description": "Postgres SSL mode. Auto-injected by the platform at deploy time.", + "origin": "platform" } } } @@ -198,5 +208,34 @@ "optional": [] } } + }, + "scaffolding": { + "command": "databricks apps init", + "flags": { + "--template-dir": { + "description": "Path to the template directory containing the app scaffold", + "required": true + }, + "--config-dir": { + "description": "Path to the output directory for the initialized app", + "required": true + }, + "--profile": { + "description": "Databricks CLI profile to use for authentication", + "required": false + } + }, + "rules": { + "never": [ + "Modify files inside the template directory", + "Skip resource configuration prompts", + "Hardcode workspace-specific values in template files" + ], + "must": [ + "Use the template manifest (appkit.plugins.json) as the source of truth for available plugins", + "Respect requiredByTemplate flags when presenting plugin selection", + "Generate .env files with all required environment variables from selected plugins" + ] + } } } From 8d571557fd52616583a8bea897d914d68584e103 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 8 Apr 2026 18:01:12 +0200 Subject: [PATCH 03/17] feat: add semantic validation for dependsOn cycles, discovery profile, and postScaffold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Xavier loop: iteration 3 — Phase 3 (Semantic Validation) Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../plugin/validate/validate-manifest.test.ts | 215 ++++++++++++++++++ .../plugin/validate/validate-manifest.ts | 193 ++++++++++++++++ .../cli/commands/plugin/validate/validate.ts | 85 ++++++- 3 files changed, 486 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts index 63dd622d5..2999858bf 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts @@ -1,8 +1,11 @@ import type { ErrorObject } from "ajv"; import { describe, expect, it } from "vitest"; +import type { PluginManifest } from "./validate-manifest"; import { detectSchemaType, + formatSemanticIssues, formatValidationErrors, + runSemanticValidation, validateManifest, validateTemplateManifest, } from "./validate-manifest"; @@ -388,4 +391,216 @@ describe("validate-manifest", () => { expect(output).toContain('missing required property "name"'); }); }); + + describe("semantic validation", () => { + it("returns no issues for a valid manifest without discovery", () => { + const result = runSemanticValidation( + VALID_MANIFEST_WITH_RESOURCE as PluginManifest, + ); + expect(result.errors).toHaveLength(0); + expect(result.warnings).toHaveLength(0); + }); + + it("detects dangling dependsOn reference", () => { + const manifest = { + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "postgres", + alias: "Postgres", + resourceKey: "postgres", + description: "test", + permission: "CAN_CONNECT_AND_CREATE", + fields: { + branch: { + env: "BRANCH", + description: "Branch name", + discovery: { + cliCommand: + "databricks postgres list-branches --profile ", + selectField: ".name", + dependsOn: "nonexistent", + }, + }, + }, + }, + ], + optional: [], + }, + }; + const result = runSemanticValidation( + manifest as unknown as PluginManifest, + ); + expect(result.errors).toHaveLength(1); + expect(result.errors[0].message).toContain("non-existent sibling field"); + expect(result.errors[0].message).toContain("nonexistent"); + }); + + it("detects cyclic dependsOn chain", () => { + const manifest = { + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "postgres", + alias: "Postgres", + resourceKey: "postgres", + description: "test", + permission: "CAN_CONNECT_AND_CREATE", + fields: { + a: { + env: "A", + discovery: { + cliCommand: "databricks cmd --profile ", + selectField: ".id", + dependsOn: "b", + }, + }, + b: { + env: "B", + discovery: { + cliCommand: "databricks cmd --profile ", + selectField: ".id", + dependsOn: "a", + }, + }, + }, + }, + ], + optional: [], + }, + }; + const result = runSemanticValidation( + manifest as unknown as PluginManifest, + ); + const cycleErrors = result.errors.filter((e) => + e.message.includes("cycle"), + ); + expect(cycleErrors.length).toBeGreaterThan(0); + }); + + it("detects missing in cliCommand", () => { + const manifest = { + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "sql_warehouse", + alias: "SQL Warehouse", + resourceKey: "sql-warehouse", + description: "test", + permission: "CAN_USE", + fields: { + id: { + env: "WAREHOUSE_ID", + discovery: { + cliCommand: "databricks warehouses list --output json", + selectField: ".id", + }, + }, + }, + }, + ], + optional: [], + }, + }; + const result = runSemanticValidation( + manifest as unknown as PluginManifest, + ); + expect(result.errors).toHaveLength(1); + expect(result.errors[0].message).toContain(""); + }); + + it("warns when discovery is on non-user origin field", () => { + const manifest = { + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "postgres", + alias: "Postgres", + resourceKey: "postgres", + description: "test", + permission: "CAN_CONNECT_AND_CREATE", + fields: { + host: { + env: "PGHOST", + localOnly: true, + discovery: { + cliCommand: "databricks cmd --profile ", + selectField: ".host", + }, + }, + }, + }, + ], + optional: [], + }, + }; + const result = runSemanticValidation( + manifest as unknown as PluginManifest, + ); + expect(result.errors).toHaveLength(0); + expect(result.warnings).toHaveLength(1); + expect(result.warnings[0].message).toContain("platform"); + }); + + it("passes for valid manifest with all new fields", () => { + const manifest = { + ...VALID_MANIFEST, + postScaffold: [ + { instruction: "Run migrations", required: true }, + { instruction: "Verify connectivity" }, + ], + resources: { + required: [ + { + type: "sql_warehouse", + alias: "SQL Warehouse", + resourceKey: "sql-warehouse", + description: "test", + permission: "CAN_USE", + fields: { + id: { + env: "WAREHOUSE_ID", + description: "Warehouse ID", + discovery: { + cliCommand: + "databricks warehouses list --profile --output json", + selectField: ".id", + displayField: ".name", + }, + }, + }, + }, + ], + optional: [], + }, + }; + const result = runSemanticValidation( + manifest as unknown as PluginManifest, + ); + expect(result.errors).toHaveLength(0); + expect(result.warnings).toHaveLength(0); + }); + + it("formats semantic issues correctly", () => { + const issues = [ + { + level: "error" as const, + path: "resources.postgres.fields.host", + message: "test error", + }, + { + level: "warning" as const, + path: "postScaffold[0]", + message: "test warning", + }, + ]; + const output = formatSemanticIssues(issues); + expect(output).toContain("resources.postgres.fields.host: test error"); + expect(output).toContain("postScaffold[0]: test warning"); + }); + }); }); diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts index b0284b765..35d7ffd21 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url"; import Ajv, { type ErrorObject } from "ajv"; import addFormats from "ajv-formats"; import type { PluginManifest } from "../manifest-types"; +import { computeOrigin } from "../sync/sync"; export type { PluginManifest }; @@ -303,3 +304,195 @@ export function formatValidationErrors( return lines.join("\n"); } + +// ── Semantic validation (cross-field / cross-resource rules) ──────────── + +export interface SemanticIssue { + level: "error" | "warning"; + path: string; + message: string; +} + +export interface SemanticValidateResult { + errors: SemanticIssue[]; + warnings: SemanticIssue[]; +} + +function validateDependsOn(manifest: PluginManifest): SemanticIssue[] { + const issues: SemanticIssue[] = []; + + for (const group of [ + manifest.resources.required, + manifest.resources.optional, + ]) { + for (const resource of group) { + if (!resource.fields) continue; + const fieldNames = new Set(Object.keys(resource.fields)); + + // Check dangling references + const deps = new Map(); + for (const [name, field] of Object.entries(resource.fields)) { + const discovery = (field as Record).discovery as + | { dependsOn?: string } + | undefined; + if (discovery?.dependsOn) { + if (!fieldNames.has(discovery.dependsOn)) { + issues.push({ + level: "error", + path: `resources.${resource.resourceKey}.fields.${name}.discovery.dependsOn`, + message: `references non-existent sibling field '${discovery.dependsOn}'`, + }); + } + deps.set(name, discovery.dependsOn); + } + } + + // Detect cycles via DFS + const visited = new Set(); + const visiting = new Set(); + + function dfs(node: string, chain: string[]): string[] | null { + if (visiting.has(node)) return [...chain, node]; + if (visited.has(node)) return null; + visiting.add(node); + const next = deps.get(node); + if (next) { + const cycle = dfs(next, [...chain, node]); + if (cycle) return cycle; + } + visiting.delete(node); + visited.add(node); + return null; + } + + for (const node of deps.keys()) { + if (!visited.has(node)) { + const cycle = dfs(node, []); + if (cycle) { + issues.push({ + level: "error", + path: `resources.${resource.resourceKey}`, + message: `discovery.dependsOn creates a cycle: ${cycle.join(" \u2192 ")}`, + }); + break; // one cycle error per resource is enough + } + } + } + } + } + + return issues; +} + +function validateDiscoveryProfile(manifest: PluginManifest): SemanticIssue[] { + const issues: SemanticIssue[] = []; + + for (const group of [ + manifest.resources.required, + manifest.resources.optional, + ]) { + for (const resource of group) { + if (!resource.fields) continue; + for (const [name, field] of Object.entries(resource.fields)) { + const discovery = (field as Record).discovery as + | { cliCommand?: string } + | undefined; + if ( + discovery?.cliCommand && + !discovery.cliCommand.includes("") + ) { + issues.push({ + level: "error", + path: `resources.${resource.resourceKey}.fields.${name}.discovery.cliCommand`, + message: "must include placeholder", + }); + } + } + } + } + + return issues; +} + +function validateDiscoveryOrigin(manifest: PluginManifest): SemanticIssue[] { + const issues: SemanticIssue[] = []; + + for (const group of [ + manifest.resources.required, + manifest.resources.optional, + ]) { + for (const resource of group) { + if (!resource.fields) continue; + for (const [name, field] of Object.entries(resource.fields)) { + const discovery = (field as Record).discovery; + if (!discovery) continue; + const origin = computeOrigin(field); + if (origin !== "user") { + issues.push({ + level: "warning", + path: `resources.${resource.resourceKey}.fields.${name}`, + message: `has discovery but computed origin is '${origin}' (not 'user') \u2014 discovery may not be used`, + }); + } + } + } + } + + return issues; +} + +function validatePostScaffold(manifest: PluginManifest): SemanticIssue[] { + const issues: SemanticIssue[] = []; + const { postScaffold } = manifest; + if (!postScaffold) return issues; + + if (!Array.isArray(postScaffold)) { + issues.push({ + level: "error", + path: "postScaffold", + message: "must be an array", + }); + return issues; + } + + for (let i = 0; i < postScaffold.length; i++) { + const step = postScaffold[i]; + if (!step || typeof step !== "object") { + issues.push({ + level: "error", + path: `postScaffold[${i}]`, + message: "must be an object", + }); + continue; + } + if (typeof step.instruction !== "string" || step.instruction.length === 0) { + issues.push({ + level: "error", + path: `postScaffold[${i}].instruction`, + message: "must be a non-empty string", + }); + } + } + + return issues; +} + +export function runSemanticValidation( + manifest: PluginManifest, +): SemanticValidateResult { + const allIssues = [ + ...validateDependsOn(manifest), + ...validateDiscoveryProfile(manifest), + ...validateDiscoveryOrigin(manifest), + ...validatePostScaffold(manifest), + ]; + + return { + errors: allIssues.filter((i) => i.level === "error"), + warnings: allIssues.filter((i) => i.level === "warning"), + }; +} + +export function formatSemanticIssues(issues: SemanticIssue[]): string { + return issues.map((i) => ` ${i.path}: ${i.message}`).join("\n"); +} diff --git a/packages/shared/src/cli/commands/plugin/validate/validate.ts b/packages/shared/src/cli/commands/plugin/validate/validate.ts index 306ee7a8c..6b35ac597 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate.ts @@ -7,9 +7,16 @@ import { type ResolvedManifest, resolveManifestInDir, } from "../manifest-resolve"; +import type { + PluginManifest, + SemanticValidateResult, + ValidateResult, +} from "./validate-manifest"; import { detectSchemaType, + formatSemanticIssues, formatValidationErrors, + runSemanticValidation, validateManifest, validateTemplateManifest, } from "./validate-manifest"; @@ -92,7 +99,12 @@ async function runPluginValidate( } let hasFailure = false; - const jsonResults: { path: string; valid: boolean; errors?: string[] }[] = []; + const jsonResults: { + path: string; + valid: boolean; + errors?: string[]; + warnings?: string[]; + }[] = []; for (const { path: manifestPath, type } of manifestPaths) { const relativePath = path.relative(cwd, manifestPath); @@ -116,16 +128,53 @@ async function runPluginValidate( } const schemaType = detectSchemaType(obj); - const result = - schemaType === "template-plugins" - ? validateTemplateManifest(obj) - : validateManifest(obj); + let result: ValidateResult; + let semanticResult: SemanticValidateResult | undefined; + + if (schemaType === "template-plugins") { + result = validateTemplateManifest(obj); + } else { + result = validateManifest(obj); + if (result.valid && result.manifest) { + semanticResult = runSemanticValidation(result.manifest); + } + } if (result.valid) { + const hasSemanticErrors = Boolean(semanticResult?.errors.length); + const hasSemanticWarnings = Boolean(semanticResult?.warnings.length); + if (options.json) { - jsonResults.push({ path: relativePath, valid: true }); + const entry: (typeof jsonResults)[number] = { + path: relativePath, + valid: !hasSemanticErrors, + }; + if (hasSemanticErrors) { + entry.errors = formatSemanticIssues(semanticResult!.errors) + .split("\n") + .filter(Boolean); + } + if (hasSemanticWarnings) { + entry.warnings = formatSemanticIssues(semanticResult!.warnings) + .split("\n") + .filter(Boolean); + } + jsonResults.push(entry); } else { - console.log(`✓ ${relativePath}`); + if (hasSemanticErrors) { + console.error(`✗ ${relativePath} (semantic errors)`); + console.error(formatSemanticIssues(semanticResult!.errors)); + } else { + console.log(`✓ ${relativePath}`); + } + if (hasSemanticWarnings) { + console.warn(" warnings:"); + console.warn(formatSemanticIssues(semanticResult!.warnings)); + } + } + + if (hasSemanticErrors) { + hasFailure = true; } } else { if (options.json) { @@ -147,6 +196,28 @@ async function runPluginValidate( } hasFailure = true; } + + if (schemaType === "template-plugins" && result.valid) { + const templateObj = obj as { plugins?: Record }; + if (templateObj.plugins) { + for (const [pluginName, plugin] of Object.entries( + templateObj.plugins, + )) { + const pluginSemantic = runSemanticValidation( + plugin as PluginManifest, + ); + if (pluginSemantic.errors.length) { + console.error(` ✗ plugin "${pluginName}" (semantic errors)`); + console.error(formatSemanticIssues(pluginSemantic.errors)); + hasFailure = true; + } + if (pluginSemantic.warnings.length) { + console.warn(` ⚠ plugin "${pluginName}" (warnings)`); + console.warn(formatSemanticIssues(pluginSemantic.warnings)); + } + } + } + } } if (options.json) { From cc4efa5bf5b690316d0a53ff6ee064828b8b1d41 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 8 Apr 2026 18:06:10 +0200 Subject: [PATCH 04/17] feat: annotate core plugin manifests with discovery descriptors and postScaffold steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Xavier loop: iteration 4 — Phase 4 (Core Plugin Manifest Annotations) Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../src/plugins/analytics/manifest.json | 17 +++- .../appkit/src/plugins/files/manifest.json | 17 +++- .../appkit/src/plugins/genie/manifest.json | 18 ++++- .../appkit/src/plugins/lakebase/manifest.json | 35 +++++++- template/appkit.plugins.json | 80 ++++++++++++++++++- 5 files changed, 156 insertions(+), 11 deletions(-) diff --git a/packages/appkit/src/plugins/analytics/manifest.json b/packages/appkit/src/plugins/analytics/manifest.json index 4a6a60c28..ba717683e 100644 --- a/packages/appkit/src/plugins/analytics/manifest.json +++ b/packages/appkit/src/plugins/analytics/manifest.json @@ -14,13 +14,28 @@ "fields": { "id": { "env": "DATABRICKS_WAREHOUSE_ID", - "description": "SQL Warehouse ID" + "description": "SQL Warehouse ID", + "discovery": { + "cliCommand": "databricks warehouses list --profile --output json", + "selectField": ".id", + "displayField": ".name" + } } } } ], "optional": [] }, + "postScaffold": [ + { + "instruction": "Ensure your SQL Warehouse is running and accessible from your workspace.", + "required": true + }, + { + "instruction": "Create a config/queries/ directory and add .sql files for your analytics queries.", + "required": false + } + ], "config": { "schema": { "type": "object", diff --git a/packages/appkit/src/plugins/files/manifest.json b/packages/appkit/src/plugins/files/manifest.json index c886decaa..6442c6795 100644 --- a/packages/appkit/src/plugins/files/manifest.json +++ b/packages/appkit/src/plugins/files/manifest.json @@ -14,13 +14,28 @@ "fields": { "path": { "env": "DATABRICKS_VOLUME_FILES", - "description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)" + "description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)", + "discovery": { + "cliCommand": "databricks volumes list . --profile --output json", + "selectField": ".full_name", + "displayField": ".name" + } } } } ], "optional": [] }, + "postScaffold": [ + { + "instruction": "Verify your Unity Catalog volume exists and you have WRITE_VOLUME permission.", + "required": true + }, + { + "instruction": "Set the DATABRICKS_VOLUME_FILES environment variable to the full volume path (e.g. /Volumes/catalog/schema/volume_name).", + "required": true + } + ], "config": { "schema": { "type": "object", diff --git a/packages/appkit/src/plugins/genie/manifest.json b/packages/appkit/src/plugins/genie/manifest.json index a269795d6..34ae97e5f 100644 --- a/packages/appkit/src/plugins/genie/manifest.json +++ b/packages/appkit/src/plugins/genie/manifest.json @@ -1,4 +1,5 @@ { + "$schema": "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json", "name": "genie", "displayName": "Genie Plugin", "description": "AI/BI Genie space integration for natural language data queries", @@ -13,13 +14,28 @@ "fields": { "id": { "env": "DATABRICKS_GENIE_SPACE_ID", - "description": "Default Genie Space ID" + "description": "Default Genie Space ID", + "discovery": { + "cliCommand": "databricks genie list --profile --output json", + "selectField": ".id", + "displayField": ".name" + } } } } ], "optional": [] }, + "postScaffold": [ + { + "instruction": "Configure the 'spaces' map in your plugin config with alias-to-Space-ID mappings.", + "required": true + }, + { + "instruction": "Ensure your Genie Space(s) are configured with the appropriate data tables and instructions.", + "required": false + } + ], "config": { "schema": { "type": "object", diff --git a/packages/appkit/src/plugins/lakebase/manifest.json b/packages/appkit/src/plugins/lakebase/manifest.json index 2959c0927..848c855f1 100644 --- a/packages/appkit/src/plugins/lakebase/manifest.json +++ b/packages/appkit/src/plugins/lakebase/manifest.json @@ -15,13 +15,24 @@ "fields": { "branch": { "description": "Full Lakebase Postgres branch resource name. Obtain by running `databricks postgres list-branches projects/{project-id}`, select the desired item from the output array and use its .name value.", - "examples": ["projects/{project-id}/branches/{branch-id}"] + "examples": ["projects/{project-id}/branches/{branch-id}"], + "discovery": { + "cliCommand": "databricks postgres list-branches --profile --output json", + "selectField": ".name", + "displayField": ".name" + } }, "database": { "description": "Full Lakebase Postgres database resource name. Obtain by running `databricks postgres list-databases {branch-name}`, select the desired item from the output array and use its .name value. Requires the branch resource name.", "examples": [ "projects/{project-id}/branches/{branch-id}/databases/{database-id}" - ] + ], + "discovery": { + "cliCommand": "databricks postgres list-databases {branch} --profile --output json", + "selectField": ".name", + "displayField": ".name", + "dependsOn": "branch" + } }, "host": { "env": "PGHOST", @@ -42,7 +53,13 @@ "description": "Lakebase endpoint resource name. Auto-injected at runtime via app.yaml valueFrom: postgres. For local development, obtain by running `databricks postgres list-endpoints {branch-name}`, select the desired item from the output array and use its .name value.", "examples": [ "projects/{project-id}/branches/{branch-id}/endpoints/{endpoint-id}" - ] + ], + "discovery": { + "cliCommand": "databricks postgres list-endpoints {branch} --profile --output json", + "selectField": ".name", + "displayField": ".name", + "dependsOn": "branch" + } }, "port": { "env": "PGPORT", @@ -60,5 +77,15 @@ } ], "optional": [] - } + }, + "postScaffold": [ + { + "instruction": "Run database migrations to initialize your Lakebase schema.", + "required": true + }, + { + "instruction": "Verify local connectivity to Lakebase using: PGHOST= PGDATABASE= PGPORT=5432 PGSSLMODE=require psql", + "required": false + } + ] } diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index 9c89068c4..6aa332085 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -19,13 +19,28 @@ "id": { "env": "DATABRICKS_WAREHOUSE_ID", "description": "SQL Warehouse ID", + "discovery": { + "cliCommand": "databricks warehouses list --profile --output json", + "selectField": ".id", + "displayField": ".name" + }, "origin": "user" } } } ], "optional": [] - } + }, + "postScaffold": [ + { + "instruction": "Ensure your SQL Warehouse is running and accessible from your workspace.", + "required": true + }, + { + "instruction": "Create a config/queries/ directory and add .sql files for your analytics queries.", + "required": false + } + ] }, "files": { "name": "files", @@ -44,13 +59,28 @@ "path": { "env": "DATABRICKS_VOLUME_FILES", "description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)", + "discovery": { + "cliCommand": "databricks volumes list . --profile --output json", + "selectField": ".full_name", + "displayField": ".name" + }, "origin": "user" } } } ], "optional": [] - } + }, + "postScaffold": [ + { + "instruction": "Verify your Unity Catalog volume exists and you have WRITE_VOLUME permission.", + "required": true + }, + { + "instruction": "Set the DATABRICKS_VOLUME_FILES environment variable to the full volume path (e.g. /Volumes/catalog/schema/volume_name).", + "required": true + } + ] }, "genie": { "name": "genie", @@ -69,13 +99,28 @@ "id": { "env": "DATABRICKS_GENIE_SPACE_ID", "description": "Default Genie Space ID", + "discovery": { + "cliCommand": "databricks genie list --profile --output json", + "selectField": ".id", + "displayField": ".name" + }, "origin": "user" } } } ], "optional": [] - } + }, + "postScaffold": [ + { + "instruction": "Configure the 'spaces' map in your plugin config with alias-to-Space-ID mappings.", + "required": true + }, + { + "instruction": "Ensure your Genie Space(s) are configured with the appropriate data tables and instructions.", + "required": false + } + ] }, "jobs": { "name": "jobs", @@ -120,6 +165,11 @@ "examples": [ "projects/{project-id}/branches/{branch-id}" ], + "discovery": { + "cliCommand": "databricks postgres list-branches --profile --output json", + "selectField": ".name", + "displayField": ".name" + }, "origin": "user" }, "database": { @@ -127,6 +177,12 @@ "examples": [ "projects/{project-id}/branches/{branch-id}/databases/{database-id}" ], + "discovery": { + "cliCommand": "databricks postgres list-databases {branch} --profile --output json", + "selectField": ".name", + "displayField": ".name", + "dependsOn": "branch" + }, "origin": "user" }, "host": { @@ -151,6 +207,12 @@ "examples": [ "projects/{project-id}/branches/{branch-id}/endpoints/{endpoint-id}" ], + "discovery": { + "cliCommand": "databricks postgres list-endpoints {branch} --profile --output json", + "selectField": ".name", + "displayField": ".name", + "dependsOn": "branch" + }, "origin": "cli" }, "port": { @@ -171,7 +233,17 @@ } ], "optional": [] - } + }, + "postScaffold": [ + { + "instruction": "Run database migrations to initialize your Lakebase schema.", + "required": true + }, + { + "instruction": "Verify local connectivity to Lakebase using: PGHOST= PGDATABASE= PGPORT=5432 PGSSLMODE=require psql", + "required": false + } + ] }, "server": { "name": "server", From e0a99c5588a75b7ad9600aa1c3a454783ae10fd0 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 8 Apr 2026 18:10:24 +0200 Subject: [PATCH 05/17] fix: inline template schema resourceFieldEntry and resourceRequirement for origin support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JSON Schema Draft-07 additionalProperties:false blocks allOf composition. Inlined both defs in template schema so origin validates correctly. Xavier loop: iteration 5 — Phase 5 (Integration & Backpressure) Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../api/appkit/Interface.PluginManifest.md | 16 + .../appkit/Interface.ResourceFieldEntry.md | 8 + .../schemas/plugin-manifest.schema.json | 65 ++++ .../schemas/template-plugins.schema.json | 348 +++++++++++++++++- .../src/schemas/template-plugins.schema.json | 255 ++++++++++++- 5 files changed, 682 insertions(+), 10 deletions(-) diff --git a/docs/docs/api/appkit/Interface.PluginManifest.md b/docs/docs/api/appkit/Interface.PluginManifest.md index 0f12a2b48..62cf055ea 100644 --- a/docs/docs/api/appkit/Interface.PluginManifest.md +++ b/docs/docs/api/appkit/Interface.PluginManifest.md @@ -168,6 +168,22 @@ Omit.onSetupMessage *** +### postScaffold? + +```ts +optional postScaffold: PostScaffoldStep[]; +``` + +Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order. + +#### Inherited from + +```ts +Omit.postScaffold +``` + +*** + ### repository? ```ts diff --git a/docs/docs/api/appkit/Interface.ResourceFieldEntry.md b/docs/docs/api/appkit/Interface.ResourceFieldEntry.md index 324a82f01..8eaaf431f 100644 --- a/docs/docs/api/appkit/Interface.ResourceFieldEntry.md +++ b/docs/docs/api/appkit/Interface.ResourceFieldEntry.md @@ -27,6 +27,14 @@ Human-readable description for this field *** +### discovery? + +```ts +optional discovery: DiscoveryDescriptor; +``` + +*** + ### env? ```ts diff --git a/docs/static/schemas/plugin-manifest.schema.json b/docs/static/schemas/plugin-manifest.schema.json index 021c7f0bf..ad6f386fe 100644 --- a/docs/static/schemas/plugin-manifest.schema.json +++ b/docs/static/schemas/plugin-manifest.schema.json @@ -101,6 +101,13 @@ "enum": ["beta", "ga"], "default": "ga", "description": "Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly." + }, + "postScaffold": { + "type": "array", + "items": { + "$ref": "#/$defs/postScaffoldStep" + }, + "description": "Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order." } }, "additionalProperties": false, @@ -226,6 +233,10 @@ "type": "string", "pattern": "^[a-z_]+:[a-zA-Z]+$", "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow." + }, + "discovery": { + "$ref": "#/$defs/discoveryDescriptor", + "description": "How the CLI discovers values for this field via a Databricks CLI command." } }, "additionalProperties": false @@ -484,6 +495,60 @@ "type": "boolean" } } + }, + "discoveryDescriptor": { + "type": "object", + "description": "Describes how the CLI discovers values for a resource field via a Databricks CLI command.", + "required": ["cliCommand", "selectField"], + "properties": { + "cliCommand": { + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "examples": [ + "databricks warehouses list --profile --output json" + ] + }, + "selectField": { + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "examples": [".id", ".name", ".catalog_name"] + }, + "displayField": { + "type": "string", + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "examples": [".name", ".display_name"] + }, + "dependsOn": { + "type": "string", + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "examples": ["branch", "catalog"] + }, + "shortcut": { + "type": "string", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "examples": [ + "databricks warehouses get --profile --output json" + ] + } + }, + "additionalProperties": false + }, + "postScaffoldStep": { + "type": "object", + "description": "A post-scaffolding instruction shown to the user after project initialization.", + "required": ["instruction"], + "properties": { + "instruction": { + "type": "string", + "description": "Human-readable instruction for the user to follow after scaffolding." + }, + "required": { + "type": "boolean", + "default": true, + "description": "Whether this step is required for the plugin to function correctly." + } + }, + "additionalProperties": false } } } diff --git a/docs/static/schemas/template-plugins.schema.json b/docs/static/schemas/template-plugins.schema.json index 47d385936..20c459785 100644 --- a/docs/static/schemas/template-plugins.schema.json +++ b/docs/static/schemas/template-plugins.schema.json @@ -12,7 +12,7 @@ }, "version": { "type": "string", - "enum": ["1.0", "1.1"], + "enum": ["1.0", "1.1", "2.0"], "description": "Schema version for the template plugins manifest" }, "plugins": { @@ -21,9 +21,24 @@ "additionalProperties": { "$ref": "#/$defs/templatePlugin" } + }, + "scaffolding": { + "$ref": "#/$defs/scaffoldingDescriptor", + "description": "Describes the scaffolding command and its configuration for project initialization." } }, "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { "version": { "const": "2.0" } }, + "required": ["version"] + }, + "then": { + "required": ["version", "plugins", "scaffolding"] + } + } + ], "$defs": { "templatePlugin": { "type": "object", @@ -75,6 +90,13 @@ "default": "ga", "description": "Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver." }, + "postScaffold": { + "type": "array", + "items": { + "$ref": "plugin-manifest.schema.json#/$defs/postScaffoldStep" + }, + "description": "Ordered list of post-scaffolding instructions propagated from the plugin manifest." + }, "resources": { "type": "object", "required": ["required", "optional"], @@ -104,10 +126,330 @@ "$ref": "plugin-manifest.schema.json#/$defs/resourceType" }, "resourceFieldEntry": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceFieldEntry" + "type": "object", + "description": "Extends the plugin manifest resourceFieldEntry with a computed origin field for template manifests.", + "properties": { + "env": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$", + "description": "Environment variable name for this field" + }, + "description": { + "type": "string", + "description": "Human-readable description for this field" + }, + "bundleIgnore": { + "type": "boolean", + "default": false, + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation." + }, + "examples": { + "type": "array", + "items": { "type": "string" }, + "description": "Example values showing the expected format for this field" + }, + "localOnly": { + "type": "boolean", + "default": false, + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time." + }, + "value": { + "type": "string", + "description": "Static value for this field." + }, + "resolve": { + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$", + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host')." + }, + "discovery": { + "$ref": "plugin-manifest.schema.json#/$defs/discoveryDescriptor", + "description": "How the CLI discovers values for this field via a Databricks CLI command." + }, + "origin": { + "$ref": "#/$defs/origin" + } + }, + "additionalProperties": false }, "resourceRequirement": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceRequirement" + "type": "object", + "description": "Resource requirement with template-specific field entries (includes computed origin).", + "required": ["type", "alias", "resourceKey", "description", "permission"], + "properties": { + "type": { + "$ref": "plugin-manifest.schema.json#/$defs/resourceType" + }, + "alias": { + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." + }, + "resourceKey": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." + }, + "description": { + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" + }, + "permission": { + "type": "string", + "description": "Required permission level." + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/resourceFieldEntry" + }, + "minProperties": 1, + "description": "Map of field name to field entry with computed origin." + } + }, + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { "type": { "const": "secret" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/secretPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "job" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/jobPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "sql_warehouse" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/sqlWarehousePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "serving_endpoint" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/servingEndpointPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "volume" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/volumePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "vector_search_index" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/vectorSearchIndexPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "uc_function" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/ucFunctionPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "uc_connection" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/ucConnectionPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "database" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/databasePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "postgres" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/postgresPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "genie_space" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/genieSpacePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "experiment" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/experimentPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "app" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/appPermission" + } + } + } + } + ] + }, + "origin": { + "type": "string", + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." + }, + "scaffoldingFlag": { + "type": "object", + "description": "A flag for the scaffolding command.", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "description": "Human-readable description of the flag." + }, + "required": { + "type": "boolean", + "default": false, + "description": "Whether this flag is required." + }, + "pattern": { + "type": "string", + "description": "Regex pattern for validating the flag value." + }, + "default": { + "type": "string", + "description": "Default value for this flag." + } + }, + "additionalProperties": false + }, + "scaffoldingRules": { + "type": "object", + "description": "Structured rules for scaffolding agents.", + "properties": { + "never": { + "type": "array", + "items": { "type": "string" }, + "description": "Actions the scaffolding agent must never perform." + }, + "must": { + "type": "array", + "items": { "type": "string" }, + "description": "Actions the scaffolding agent must always perform." + } + }, + "additionalProperties": false + }, + "scaffoldingDescriptor": { + "type": "object", + "description": "Describes the scaffolding command, flags, and rules for project initialization.", + "required": ["command"], + "properties": { + "command": { + "type": "string", + "description": "The scaffolding command (e.g., 'databricks apps init')." + }, + "flags": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/scaffoldingFlag" + }, + "description": "Map of flag name to flag descriptor." + }, + "rules": { + "$ref": "#/$defs/scaffoldingRules", + "description": "Structured rules for scaffolding agents." + } + }, + "additionalProperties": false } } } diff --git a/packages/shared/src/schemas/template-plugins.schema.json b/packages/shared/src/schemas/template-plugins.schema.json index 95d07f30b..20c459785 100644 --- a/packages/shared/src/schemas/template-plugins.schema.json +++ b/packages/shared/src/schemas/template-plugins.schema.json @@ -126,20 +126,261 @@ "$ref": "plugin-manifest.schema.json#/$defs/resourceType" }, "resourceFieldEntry": { + "type": "object", + "description": "Extends the plugin manifest resourceFieldEntry with a computed origin field for template manifests.", + "properties": { + "env": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$", + "description": "Environment variable name for this field" + }, + "description": { + "type": "string", + "description": "Human-readable description for this field" + }, + "bundleIgnore": { + "type": "boolean", + "default": false, + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation." + }, + "examples": { + "type": "array", + "items": { "type": "string" }, + "description": "Example values showing the expected format for this field" + }, + "localOnly": { + "type": "boolean", + "default": false, + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time." + }, + "value": { + "type": "string", + "description": "Static value for this field." + }, + "resolve": { + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$", + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host')." + }, + "discovery": { + "$ref": "plugin-manifest.schema.json#/$defs/discoveryDescriptor", + "description": "How the CLI discovers values for this field via a Databricks CLI command." + }, + "origin": { + "$ref": "#/$defs/origin" + } + }, + "additionalProperties": false + }, + "resourceRequirement": { + "type": "object", + "description": "Resource requirement with template-specific field entries (includes computed origin).", + "required": ["type", "alias", "resourceKey", "description", "permission"], + "properties": { + "type": { + "$ref": "plugin-manifest.schema.json#/$defs/resourceType" + }, + "alias": { + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." + }, + "resourceKey": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." + }, + "description": { + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" + }, + "permission": { + "type": "string", + "description": "Required permission level." + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/resourceFieldEntry" + }, + "minProperties": 1, + "description": "Map of field name to field entry with computed origin." + } + }, + "additionalProperties": false, "allOf": [ - { "$ref": "plugin-manifest.schema.json#/$defs/resourceFieldEntry" }, { - "properties": { - "origin": { - "$ref": "#/$defs/origin" + "if": { + "properties": { "type": { "const": "secret" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/secretPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "job" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/jobPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "sql_warehouse" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/sqlWarehousePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "serving_endpoint" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/servingEndpointPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "volume" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/volumePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "vector_search_index" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/vectorSearchIndexPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "uc_function" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/ucFunctionPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "uc_connection" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/ucConnectionPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "database" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/databasePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "postgres" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/postgresPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "genie_space" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/genieSpacePermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "experiment" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/experimentPermission" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "app" } }, + "required": ["type"] + }, + "then": { + "properties": { + "permission": { + "$ref": "plugin-manifest.schema.json#/$defs/appPermission" + } } } } ] }, - "resourceRequirement": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceRequirement" - }, "origin": { "type": "string", "enum": ["user", "platform", "static", "cli"], From 61cc1f2601641651af76ee8f72453f7f2758366a Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 9 Apr 2026 14:50:12 +0200 Subject: [PATCH 06/17] chore: address code reviews --- .claude/scheduled_tasks.lock | 1 + .../appkit/src/plugins/lakebase/manifest.json | 8 +---- .../src/cli/commands/plugin/manifest-types.ts | 15 +++++++++ .../src/cli/commands/plugin/sync/sync.ts | 29 +++++----------- .../plugin/validate/validate-manifest.ts | 3 +- .../src/schemas/plugin-manifest.generated.ts | 33 +++---------------- .../src/schemas/plugin-manifest.schema.json | 3 +- template/appkit.plugins.json | 6 ---- 8 files changed, 31 insertions(+), 67 deletions(-) create mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 000000000..09304ee91 --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"f1aff2d6-c2fa-41a1-9596-0670b1e35bda","pid":6435,"acquiredAt":1775737052293} \ No newline at end of file diff --git a/packages/appkit/src/plugins/lakebase/manifest.json b/packages/appkit/src/plugins/lakebase/manifest.json index 848c855f1..b23b1426e 100644 --- a/packages/appkit/src/plugins/lakebase/manifest.json +++ b/packages/appkit/src/plugins/lakebase/manifest.json @@ -53,13 +53,7 @@ "description": "Lakebase endpoint resource name. Auto-injected at runtime via app.yaml valueFrom: postgres. For local development, obtain by running `databricks postgres list-endpoints {branch-name}`, select the desired item from the output array and use its .name value.", "examples": [ "projects/{project-id}/branches/{branch-id}/endpoints/{endpoint-id}" - ], - "discovery": { - "cliCommand": "databricks postgres list-endpoints {branch} --profile --output json", - "selectField": ".name", - "displayField": ".name", - "dependsOn": "branch" - } + ] }, "port": { "env": "PGPORT", diff --git a/packages/shared/src/cli/commands/plugin/manifest-types.ts b/packages/shared/src/cli/commands/plugin/manifest-types.ts index 7a0f9a85e..25cd03b59 100644 --- a/packages/shared/src/cli/commands/plugin/manifest-types.ts +++ b/packages/shared/src/cli/commands/plugin/manifest-types.ts @@ -16,6 +16,7 @@ export type { import type { PluginManifest, PostScaffoldStep, + ResourceFieldEntry, } from "../../../schemas/plugin-manifest.generated"; export interface ScaffoldingFlag { @@ -38,6 +39,20 @@ export interface ScaffoldingDescriptor { export type Origin = "user" | "platform" | "static" | "cli"; +/** + * Derives the origin of a resource field value based on its properties. + * - localOnly: true → "platform" (auto-injected by Databricks Apps platform) + * - value present → "static" (hardcoded value) + * - resolve present → "cli" (resolved by CLI during init) + * - else → "user" (user must provide the value) + */ +export function computeOrigin(field: ResourceFieldEntry): Origin { + if (field.localOnly) return "platform"; + if (field.value !== undefined) return "static"; + if (field.resolve !== undefined) return "cli"; + return "user"; +} + export interface TemplatePlugin extends Omit { package: string; /** When true, this plugin is required by the template and cannot be deselected during CLI init. */ diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.ts b/packages/shared/src/cli/commands/plugin/sync/sync.ts index e5f9b6c94..565e6f2a2 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.ts @@ -7,13 +7,14 @@ import { type ResolvedManifest, resolveManifestInDir, } from "../manifest-resolve"; -import type { - Origin, - PluginManifest, - ResourceFieldEntry, - ScaffoldingDescriptor, - TemplatePlugin, - TemplatePluginsManifest, +import { + computeOrigin, + type Origin, + type PluginManifest, + type ResourceFieldEntry, + type ScaffoldingDescriptor, + type TemplatePlugin, + type TemplatePluginsManifest, } from "../manifest-types"; import { shouldAllowJsManifestForPackage } from "../trusted-js-manifest"; import { @@ -39,20 +40,6 @@ function isWithinDirectory(filePath: string, boundary: string): boolean { ); } -/** - * Derives the origin of a resource field value based on its properties. - * - localOnly: true → "platform" (auto-injected by Databricks Apps platform) - * - value present → "static" (hardcoded value) - * - resolve present → "cli" (resolved by CLI during init) - * - else → "user" (user must provide the value) - */ -function computeOrigin(field: ResourceFieldEntry): Origin { - if (field.localOnly) return "platform"; - if (field.value !== undefined) return "static"; - if (field.resolve !== undefined) return "cli"; - return "user"; -} - /** * Injects computed `origin` onto every resource field in all plugins. * Mutates the plugins object in place for efficiency. diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts index 35d7ffd21..40ae274cb 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts @@ -3,8 +3,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import Ajv, { type ErrorObject } from "ajv"; import addFormats from "ajv-formats"; -import type { PluginManifest } from "../manifest-types"; -import { computeOrigin } from "../sync/sync"; +import { computeOrigin, type PluginManifest } from "../manifest-types"; export type { PluginManifest }; diff --git a/packages/shared/src/schemas/plugin-manifest.generated.ts b/packages/shared/src/schemas/plugin-manifest.generated.ts index 812a73fc4..2acdb4d1d 100644 --- a/packages/shared/src/schemas/plugin-manifest.generated.ts +++ b/packages/shared/src/schemas/plugin-manifest.generated.ts @@ -261,7 +261,10 @@ export interface ResourceFieldEntry { discovery?: DiscoveryDescriptor; } /** - * How the CLI discovers values for this field via a Databricks CLI command. + * Describes how the CLI discovers values for a resource field via a Databricks CLI command. + * + * This interface was referenced by `PluginManifest`'s JSON-Schema + * via the `definition` "discoveryDescriptor". */ export interface DiscoveryDescriptor { /** @@ -333,31 +336,3 @@ export interface PostScaffoldStep { */ required?: boolean; } -/** - * Describes how the CLI discovers values for a resource field via a Databricks CLI command. - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "discoveryDescriptor". - */ -export interface DiscoveryDescriptor1 { - /** - * Databricks CLI command that lists resources. Must include placeholder. - */ - cliCommand: string; - /** - * jq-style path to the field used as the selected value (e.g., '.id', '.name'). - */ - selectField: string; - /** - * jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted. - */ - displayField?: string; - /** - * Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields. - */ - dependsOn?: string; - /** - * Single-value fast-path command that returns exactly one value, skipping interactive selection. - */ - shortcut?: string; -} diff --git a/packages/shared/src/schemas/plugin-manifest.schema.json b/packages/shared/src/schemas/plugin-manifest.schema.json index ad6f386fe..45fea2723 100644 --- a/packages/shared/src/schemas/plugin-manifest.schema.json +++ b/packages/shared/src/schemas/plugin-manifest.schema.json @@ -235,8 +235,7 @@ "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow." }, "discovery": { - "$ref": "#/$defs/discoveryDescriptor", - "description": "How the CLI discovers values for this field via a Databricks CLI command." + "$ref": "#/$defs/discoveryDescriptor" } }, "additionalProperties": false diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index 6aa332085..2f456ba8c 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -207,12 +207,6 @@ "examples": [ "projects/{project-id}/branches/{branch-id}/endpoints/{endpoint-id}" ], - "discovery": { - "cliCommand": "databricks postgres list-endpoints {branch} --profile --output json", - "selectField": ".name", - "displayField": ".name", - "dependsOn": "branch" - }, "origin": "cli" }, "port": { From 207b10e0fe6765811ef0affdd54c16de7c4c2e2a Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Tue, 21 Apr 2026 17:12:52 +0200 Subject: [PATCH 07/17] chore: untrack .claude/scheduled_tasks.lock Accidentally committed in a1c30e3; it's ephemeral Claude Code loop state, not source. Flagged in PR #261 review. Signed-off-by: Atila Fassina --- .claude/scheduled_tasks.lock | 1 - .gitignore | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index 09304ee91..000000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"f1aff2d6-c2fa-41a1-9596-0670b1e35bda","pid":6435,"acquiredAt":1775737052293} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5d417368a..645f5cf52 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ coverage .turbo .databricks + +.claude/scheduled_tasks.lock From ce34e95ccdb4eb18bd7edb51ec51e515b498409a Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 20:33:02 +0200 Subject: [PATCH 08/17] feat: introduce zod-canonical manifest schema (phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Zod schemas mirroring the existing plugin and template manifest JSON Schemas, the @standard-schema/spec dep that consumer code will use in phase 2, and a Zod→JSON Schema generator wired into build:package and generate:types. AJV continues to run in validate-manifest.ts; this is purely additive groundwork. Parity test uses fixture equivalence (Strategy B) — Zod 4's toJSONSchema emits per-type permission constraints as oneOf-of-discriminated-variants while the hand-written schema uses allOf+if/then over $defs/$ref, so byte parity is structurally infeasible. The test asserts AJV-with-legacy and Zod-with-new return matching accept/reject verdicts on the four core plugin manifests plus 5 synthetic plugin and 3 synthetic template fixtures (12 cases total). Build-pipeline byproducts of running pnpm build && pnpm docs:build cleanly are also captured: docs/static/schemas/plugin-manifest.schema.json loses a description field that copy-schemas.ts overwrote from the package-internal source (where the description was never present), and template/appkit.plugins.json gains origin enrichment on jobs.id and serving.name fields the parent PRD's enrichFieldsWithOrigin pass missed. Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../schemas/plugin-manifest.schema.json | 3 +- package.json | 2 +- packages/shared/package.json | 6 +- .../__tests__/json-schema-parity.test.ts | 242 ++++++ packages/shared/src/schemas/manifest.ts | 709 ++++++++++++++++++ pnpm-lock.yaml | 58 ++ template/appkit.plugins.json | 6 +- tools/generate-json-schema.ts | 98 +++ 8 files changed, 1117 insertions(+), 7 deletions(-) create mode 100644 packages/shared/src/schemas/__tests__/json-schema-parity.test.ts create mode 100644 packages/shared/src/schemas/manifest.ts create mode 100644 tools/generate-json-schema.ts diff --git a/docs/static/schemas/plugin-manifest.schema.json b/docs/static/schemas/plugin-manifest.schema.json index ad6f386fe..45fea2723 100644 --- a/docs/static/schemas/plugin-manifest.schema.json +++ b/docs/static/schemas/plugin-manifest.schema.json @@ -235,8 +235,7 @@ "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow." }, "discovery": { - "$ref": "#/$defs/discoveryDescriptor", - "description": "How the CLI discovers values for this field via a Databricks CLI command." + "$ref": "#/$defs/discoveryDescriptor" } }, "additionalProperties": false diff --git a/package.json b/package.json index 8f045f178..a01e0de81 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build:watch": "pnpm -r --filter=!dev-playground --filter=!docs build:watch", "check:fix": "biome check --write .", "check": "biome check .", - "generate:types": "tsx tools/generate-schema-types.ts && tsx tools/generate-registry-types.ts && tsx tools/generate-plugin-entries.ts && tsx tools/generate-plugin-doc-banners.ts", + "generate:types": "tsx tools/generate-schema-types.ts && tsx tools/generate-json-schema.ts && tsx tools/generate-registry-types.ts && tsx tools/generate-plugin-entries.ts && tsx tools/generate-plugin-doc-banners.ts", "generate:plugin-entries": "tsx tools/generate-plugin-entries.ts", "generate:plugin-doc-banners": "tsx tools/generate-plugin-doc-banners.ts", "generate:app-templates": "tsx tools/generate-app-templates.ts", diff --git a/packages/shared/package.json b/packages/shared/package.json index 27d268ca3..b2313054c 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -15,7 +15,7 @@ "./package.json": "./package.json" }, "scripts": { - "build:package": "pnpm exec tsx ../../tools/generate-schema-types.ts && tsdown --config tsdown.config.ts", + "build:package": "pnpm exec tsx ../../tools/generate-schema-types.ts && pnpm exec tsx ../../tools/generate-json-schema.ts && tsdown --config tsdown.config.ts", "build:watch": "tsdown --config tsdown.config.ts --watch", "typecheck": "tsc --noEmit", "clean": "rm -rf dist", @@ -37,9 +37,11 @@ }, "dependencies": { "@ast-grep/napi": "0.37.0", + "@standard-schema/spec": "1.1.0", "ajv": "8.17.1", "ajv-formats": "3.0.1", "@clack/prompts": "1.0.1", - "commander": "12.1.0" + "commander": "12.1.0", + "zod": "4.3.6" } } diff --git a/packages/shared/src/schemas/__tests__/json-schema-parity.test.ts b/packages/shared/src/schemas/__tests__/json-schema-parity.test.ts new file mode 100644 index 000000000..ac7cf903d --- /dev/null +++ b/packages/shared/src/schemas/__tests__/json-schema-parity.test.ts @@ -0,0 +1,242 @@ +/** + * Parity gate for the Zod-authored manifest schema. + * + * Strategy: fixture equivalence (Strategy B). + * + * Rationale: byte-equivalence (Strategy A) is not achievable. Zod 4's + * `toJSONSchema` represents per-`type` permission constraints via + * `oneOf` over discriminated-union variants, while the hand-written JSON + * Schema uses an `allOf + if/then` block over a base resourceRequirement + * definition with a `$defs`-resolved permission. Both shapes are valid + * JSON Schema draft-07, and AJV accepts both, but they are not byte + * identical and cannot be made so without substantial post-processing + * that would not survive future schema changes. + * + * Documented diffs observed between the legacy hand-written JSON Schema + * and the Zod-generated output: + * + * - The legacy schema uses `$defs/resourceRequirement` + `allOf+if/then` + * to constrain `permission` per `type`. The Zod output inlines a + * `oneOf` of variants discriminated on `type`. + * - Legacy `examples` arrays at the property level are not emitted by + * Zod's `toJSONSchema`. Examples in `.describe()` text are preserved. + * - Legacy schemas use `$defs` cross-references (`$ref` to shared + * permission enums); Zod inlines `enum` arrays at each call site. + * - Legacy `fields` uses `minProperties: 1`; Zod `refine` cannot be + * represented in JSON Schema, so this constraint exists only at + * parse time. + * - Legacy `version` enum on the template uses array order + * ["1.0", "1.1", "2.0"]; Zod preserves source order. + * + * The test below asserts that for every fixture, the AJV-with-legacy-schema + * verdict matches the Zod-with-new-schema verdict. That is the contract + * the parity gate guards: equivalent acceptance/rejection on real and + * synthetic manifests, not byte identity of the schema files. + */ + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +import { describe, expect, it } from "vitest"; +import { + pluginManifestSchema, + templatePluginsManifestSchema, +} from "../manifest"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, "../../../../.."); +const PLUGIN_SCHEMA_PATH = path.join( + REPO_ROOT, + "packages/shared/src/schemas/plugin-manifest.schema.json", +); +const TEMPLATE_SCHEMA_PATH = path.join( + REPO_ROOT, + "packages/shared/src/schemas/template-plugins.schema.json", +); + +const PLUGINS_DIR = path.join(REPO_ROOT, "packages/appkit/src/plugins"); + +function loadJson(filePath: string): unknown { + return JSON.parse(fs.readFileSync(filePath, "utf-8")); +} + +function buildAjvForPlugin() { + const ajv = new Ajv({ allErrors: true, strict: false }); + addFormats(ajv); + return ajv.compile(loadJson(PLUGIN_SCHEMA_PATH) as object); +} + +function buildAjvForTemplate() { + const ajv = new Ajv({ allErrors: true, strict: false }); + addFormats(ajv); + ajv.addSchema(loadJson(PLUGIN_SCHEMA_PATH) as object); + return ajv.compile(loadJson(TEMPLATE_SCHEMA_PATH) as object); +} + +const validateLegacyPlugin = buildAjvForPlugin(); +const validateLegacyTemplate = buildAjvForTemplate(); + +interface PluginFixture { + label: string; + manifest: unknown; +} + +const corePluginFixtures: PluginFixture[] = [ + "analytics", + "files", + "genie", + "lakebase", +].map((name) => ({ + label: `core/${name}`, + manifest: loadJson(path.join(PLUGINS_DIR, name, "manifest.json")), +})); + +const minimalValid: PluginFixture = { + label: "synthetic/minimal-valid", + manifest: { + $schema: + "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json", + name: "test-plugin", + displayName: "Test Plugin", + description: "A test plugin", + resources: { + required: [], + optional: [], + }, + }, +}; + +const invalidUnknownProperty: PluginFixture = { + label: "synthetic/invalid-unknown-property", + manifest: { + ...(minimalValid.manifest as object), + nonsenseField: "boom", + }, +}; + +const invalidPermissionForType: PluginFixture = { + label: "synthetic/invalid-permission-for-type", + manifest: { + name: "bad", + displayName: "Bad", + description: "Wrong permission for sql_warehouse", + resources: { + required: [ + { + type: "sql_warehouse", + alias: "Warehouse", + resourceKey: "wh", + description: "wh", + // Wrong: this enum is for `genie_space` + permission: "CAN_RUN", + }, + ], + optional: [], + }, + }, +}; + +const invalidNamePattern: PluginFixture = { + label: "synthetic/invalid-name-pattern", + manifest: { + name: "Invalid_Name", + displayName: "Bad", + description: "Bad name pattern", + resources: { required: [], optional: [] }, + }, +}; + +const missingRequired: PluginFixture = { + label: "synthetic/missing-required", + manifest: { + displayName: "Missing name", + description: "...", + resources: { required: [], optional: [] }, + }, +}; + +const pluginFixtures: PluginFixture[] = [ + ...corePluginFixtures, + minimalValid, + invalidUnknownProperty, + invalidPermissionForType, + invalidNamePattern, + missingRequired, +]; + +interface TemplateFixture { + label: string; + manifest: unknown; +} + +const minimalTemplateValid: TemplateFixture = { + label: "synthetic/template-minimal-valid", + manifest: { + version: "1.1", + plugins: { + example: { + name: "example", + displayName: "Example", + description: "An example plugin", + package: "@databricks/appkit", + resources: { required: [], optional: [] }, + }, + }, + }, +}; + +const templateMissingScaffolding: TemplateFixture = { + label: "synthetic/template-2.0-missing-scaffolding", + manifest: { + version: "2.0", + plugins: {}, + }, +}; + +const templateInvalidVersion: TemplateFixture = { + label: "synthetic/template-bad-version", + manifest: { + version: "9.9", + plugins: {}, + }, +}; + +const templateFixtures: TemplateFixture[] = [ + minimalTemplateValid, + templateMissingScaffolding, + templateInvalidVersion, +]; + +describe("plugin manifest schema parity (Strategy B: fixture equivalence)", () => { + for (const fixture of pluginFixtures) { + it(`agrees on ${fixture.label}`, () => { + const legacyValid = validateLegacyPlugin(fixture.manifest); + const zodResult = pluginManifestSchema.safeParse(fixture.manifest); + expect( + zodResult.success, + legacyValid + ? "legacy schema accepted but Zod schema rejected" + : "legacy schema rejected but Zod schema accepted", + ).toBe(legacyValid); + }); + } +}); + +describe("template manifest schema parity (Strategy B: fixture equivalence)", () => { + for (const fixture of templateFixtures) { + it(`agrees on ${fixture.label}`, () => { + const legacyValid = validateLegacyTemplate(fixture.manifest); + const zodResult = templatePluginsManifestSchema.safeParse( + fixture.manifest, + ); + expect( + zodResult.success, + legacyValid + ? "legacy template schema accepted but Zod template schema rejected" + : "legacy template schema rejected but Zod template schema accepted", + ).toBe(legacyValid); + }); + } +}); diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts new file mode 100644 index 000000000..93feb6c5c --- /dev/null +++ b/packages/shared/src/schemas/manifest.ts @@ -0,0 +1,709 @@ +/** + * Zod-authoring module for AppKit plugin manifest schemas. + * + * Single source of truth for plugin manifest contract. Mirrors the legacy + * hand-written JSON Schema files (plugin-manifest.schema.json, + * template-plugins.schema.json) faithfully so byte-equivalence/parity can + * be asserted before downstream consumers are migrated. + * + * - Phase 1: this module is sealed; only schemas + inferred types are exported. + * Consumers in the CLI keep going through the AJV-based validator until + * Phase 2 wires Standard Schema in. + * - Phase 3: `templateFieldEntrySchema` will move `origin` from an optional + * input slot to a `.transform()` output. For Phase 1, `origin` is allowed + * on input as an optional enum to keep parity with the existing template + * schema's `resourceFieldEntry`. + * - Phase 4: `discoveryDescriptorSchema` will become a discriminated union. + * For Phase 1 it remains in the existing free-form shape. + * - Phase 6: scaffolding rule items get a `maxLength`, and a volume MUST + * rule lands in `TEMPLATE_SCAFFOLDING`. Neither happens here. + */ + +import { z } from "zod"; + +// ── Resource type + per-type permission enums ──────────────────────────── + +export const resourceTypeSchema = z + .enum([ + "secret", + "job", + "sql_warehouse", + "serving_endpoint", + "volume", + "vector_search_index", + "uc_function", + "uc_connection", + "database", + "postgres", + "genie_space", + "experiment", + "app", + ]) + .describe("Type of Databricks resource"); + +export const secretPermissionSchema = z + .enum(["READ", "WRITE", "MANAGE"]) + .describe("Permission for secret resources (order: weakest to strongest)"); + +export const jobPermissionSchema = z + .enum(["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"]) + .describe("Permission for job resources (order: weakest to strongest)"); + +export const sqlWarehousePermissionSchema = z + .enum(["CAN_USE", "CAN_MANAGE"]) + .describe( + "Permission for SQL warehouse resources (order: weakest to strongest)", + ); + +export const servingEndpointPermissionSchema = z + .enum(["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"]) + .describe( + "Permission for serving endpoint resources (order: weakest to strongest)", + ); + +export const volumePermissionSchema = z + .enum(["READ_VOLUME", "WRITE_VOLUME"]) + .describe("Permission for Unity Catalog volume resources"); + +export const vectorSearchIndexPermissionSchema = z + .enum(["SELECT"]) + .describe("Permission for vector search index resources"); + +export const ucFunctionPermissionSchema = z + .enum(["EXECUTE"]) + .describe("Permission for Unity Catalog function resources"); + +export const ucConnectionPermissionSchema = z + .enum(["USE_CONNECTION"]) + .describe("Permission for Unity Catalog connection resources"); + +export const databasePermissionSchema = z + .enum(["CAN_CONNECT_AND_CREATE"]) + .describe("Permission for database resources"); + +export const postgresPermissionSchema = z + .enum(["CAN_CONNECT_AND_CREATE"]) + .describe("Permission for Postgres resources"); + +export const genieSpacePermissionSchema = z + .enum(["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"]) + .describe( + "Permission for Genie Space resources (order: weakest to strongest)", + ); + +export const experimentPermissionSchema = z + .enum(["CAN_READ", "CAN_EDIT", "CAN_MANAGE"]) + .describe( + "Permission for MLflow experiment resources (order: weakest to strongest)", + ); + +export const appPermissionSchema = z + .enum(["CAN_USE"]) + .describe("Permission for Databricks App resources"); + +// ── Discovery descriptor (Phase 1: free-form shape) ────────────────────── + +export const discoveryDescriptorSchema = z + .object({ + cliCommand: z + .string() + .describe( + "Databricks CLI command that lists resources. Must include placeholder.", + ), + selectField: z + .string() + .describe( + "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + ), + displayField: z + .string() + .optional() + .describe( + "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + ), + dependsOn: z + .string() + .optional() + .describe( + "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + ), + shortcut: z + .string() + .optional() + .describe( + "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + ), + }) + .strict() + .describe( + "Describes how the CLI discovers values for a resource field via a Databricks CLI command.", + ); + +// ── Resource field entry (plugin manifest variant) ─────────────────────── + +export const resourceFieldEntrySchema = z + .object({ + env: z + .string() + .regex(/^[A-Z][A-Z0-9_]*$/) + .optional() + .describe("Environment variable name for this field"), + description: z + .string() + .optional() + .describe("Human-readable description for this field"), + bundleIgnore: z + .boolean() + .optional() + .describe( + "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + ), + examples: z + .array(z.string()) + .optional() + .describe("Example values showing the expected format for this field"), + localOnly: z + .boolean() + .optional() + .describe( + "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + ), + value: z + .string() + .optional() + .describe( + "Static value for this field. Used when no prompted or resolved value exists.", + ), + resolve: z + .string() + .regex(/^[a-z_]+:[a-zA-Z]+$/) + .optional() + .describe( + "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + ), + discovery: discoveryDescriptorSchema.optional(), + }) + .strict() + .describe( + "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + ); + +// ── Resource requirement (per-type permission discriminator) ───────────── + +/** + * Build a per-type variant. Each variant fixes `type` to a literal and constrains + * `permission` to the matching enum, mirroring the existing JSON Schema's + * `allOf + if/then` block. `fields` and the rest of the shape come from a + * shared base. + */ +const resourceRequirementBaseShape = { + alias: z + .string() + .min(1) + .describe( + "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + ), + resourceKey: z + .string() + .regex(/^[a-z][a-z0-9-]*$/) + .describe( + "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + ), + description: z + .string() + .min(1) + .describe("Human-readable description of why this resource is needed"), + fields: z + .record(z.string(), resourceFieldEntrySchema) + .refine((obj) => Object.keys(obj).length >= 1, { + message: "fields must contain at least one entry", + }) + .optional() + .describe( + "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + ), +}; + +function makeResourceVariant< + TType extends z.ZodLiteral, + TPerm extends z.ZodTypeAny, +>(typeLiteral: TType, permission: TPerm) { + return z + .object({ + type: typeLiteral, + ...resourceRequirementBaseShape, + permission: permission.describe( + "Required permission level. Validated per resource type.", + ), + }) + .strict(); +} + +export const resourceRequirementSchema = z + .discriminatedUnion("type", [ + makeResourceVariant(z.literal("secret"), secretPermissionSchema), + makeResourceVariant(z.literal("job"), jobPermissionSchema), + makeResourceVariant( + z.literal("sql_warehouse"), + sqlWarehousePermissionSchema, + ), + makeResourceVariant( + z.literal("serving_endpoint"), + servingEndpointPermissionSchema, + ), + makeResourceVariant(z.literal("volume"), volumePermissionSchema), + makeResourceVariant( + z.literal("vector_search_index"), + vectorSearchIndexPermissionSchema, + ), + makeResourceVariant(z.literal("uc_function"), ucFunctionPermissionSchema), + makeResourceVariant( + z.literal("uc_connection"), + ucConnectionPermissionSchema, + ), + makeResourceVariant(z.literal("database"), databasePermissionSchema), + makeResourceVariant(z.literal("postgres"), postgresPermissionSchema), + makeResourceVariant(z.literal("genie_space"), genieSpacePermissionSchema), + makeResourceVariant(z.literal("experiment"), experimentPermissionSchema), + makeResourceVariant(z.literal("app"), appPermissionSchema), + ]) + .describe( + "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().", + ); + +// ── Config schema (recursive) ──────────────────────────────────────────── + +export const configSchemaPropertySchema: z.ZodType = z.lazy(() => + z.object({ + type: z.enum(["object", "array", "string", "number", "boolean", "integer"]), + description: z.string().optional(), + default: z.unknown().optional(), + enum: z.array(z.unknown()).optional(), + properties: z.record(z.string(), configSchemaPropertySchema).optional(), + items: configSchemaPropertySchema.optional(), + minimum: z.number().optional(), + maximum: z.number().optional(), + minLength: z.number().int().min(0).optional(), + maxLength: z.number().int().min(0).optional(), + required: z.array(z.string()).optional(), + }), +); + +export const configSchemaSchema: z.ZodType = z.lazy(() => + z.object({ + type: z.enum(["object", "array", "string", "number", "boolean"]), + properties: z.record(z.string(), configSchemaPropertySchema).optional(), + items: configSchemaSchema.optional(), + required: z.array(z.string()).optional(), + additionalProperties: z.boolean().optional(), + }), +); + +// ── Post-scaffold step ─────────────────────────────────────────────────── + +export const postScaffoldStepSchema = z + .object({ + instruction: z + .string() + .describe( + "Human-readable instruction for the user to follow after scaffolding.", + ), + required: z + .boolean() + .optional() + .describe( + "Whether this step is required for the plugin to function correctly.", + ), + }) + .strict() + .describe( + "A post-scaffolding instruction shown to the user after project initialization.", + ); + +// ── Plugin manifest (root) ─────────────────────────────────────────────── + +export const pluginManifestSchema = z + .object({ + $schema: z + .string() + .optional() + .describe("Reference to the JSON Schema for validation"), + name: z + .string() + .regex(/^[a-z][a-z0-9-]*$/) + .describe( + "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", + ), + displayName: z + .string() + .min(1) + .describe("Human-readable display name for UI and CLI"), + description: z + .string() + .min(1) + .describe("Brief description of what the plugin does"), + resources: z + .object({ + required: z + .array(resourceRequirementSchema) + .describe( + "Resources that must be available for the plugin to function", + ), + optional: z + .array(resourceRequirementSchema) + .describe( + "Resources that enhance functionality but are not mandatory", + ), + }) + .strict() + .describe("Databricks resource requirements for this plugin"), + config: z + .object({ + schema: configSchemaSchema.optional(), + }) + .strict() + .optional() + .describe("Configuration schema for the plugin"), + author: z.string().optional().describe("Author name or organization"), + version: z + .string() + .regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/) + .optional() + .describe("Plugin version (semver format)"), + repository: z + .url() + .optional() + .describe("URL to the plugin's source repository"), + keywords: z + .array(z.string()) + .optional() + .describe("Keywords for plugin discovery"), + license: z.string().optional().describe("SPDX license identifier"), + onSetupMessage: z + .string() + .optional() + .describe( + "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning).", + ), + hidden: z + .boolean() + .optional() + .describe( + "When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync.", + ), + stability: z + .enum(["beta", "ga"]) + .optional() + .describe( + "Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly.", + ), + postScaffold: z + .array(postScaffoldStepSchema) + .optional() + .describe( + "Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order.", + ), + }) + .strict() + .describe( + "Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options.", + ); + +// ── Origin enum ────────────────────────────────────────────────────────── + +export const originSchema = z + .enum(["user", "platform", "static", "cli"]) + .describe( + "How the field value is determined. Computed during sync, not authored by plugin developers.", + ); + +// ── Template field entry (Phase 1: origin still input-optional) ────────── + +/** + * Template field entry: extends the plugin manifest field entry with an + * optional `origin` field. Phase 3 will move `origin` to a `.transform()` + * output and remove the input slot. For parity with the existing + * hand-written template schema, Phase 1 keeps `origin` as an optional input + * (consumers in Phase 1 still emit it via `enrichFieldsWithOrigin`). + */ +export const templateFieldEntrySchema = resourceFieldEntrySchema.extend({ + origin: originSchema.optional(), +}); + +// ── Template resource requirement (uses templateFieldEntrySchema) ──────── + +const templateResourceRequirementBaseShape = { + alias: z + .string() + .min(1) + .describe("Human-readable label for UI/display only."), + resourceKey: z + .string() + .regex(/^[a-z][a-z0-9-]*$/) + .describe( + "Stable key for machine use: deduplication, env naming, composite keys.", + ), + description: z + .string() + .min(1) + .describe("Human-readable description of why this resource is needed"), + fields: z + .record(z.string(), templateFieldEntrySchema) + .refine((obj) => Object.keys(obj).length >= 1, { + message: "fields must contain at least one entry", + }) + .optional() + .describe("Map of field name to field entry with computed origin."), +}; + +function makeTemplateResourceVariant< + TType extends z.ZodLiteral, + TPerm extends z.ZodTypeAny, +>(typeLiteral: TType, permission: TPerm) { + return z + .object({ + type: typeLiteral, + ...templateResourceRequirementBaseShape, + permission: permission.describe( + "Required permission level. Validated per resource type.", + ), + }) + .strict(); +} + +export const templateResourceRequirementSchema = z + .discriminatedUnion("type", [ + makeTemplateResourceVariant(z.literal("secret"), secretPermissionSchema), + makeTemplateResourceVariant(z.literal("job"), jobPermissionSchema), + makeTemplateResourceVariant( + z.literal("sql_warehouse"), + sqlWarehousePermissionSchema, + ), + makeTemplateResourceVariant( + z.literal("serving_endpoint"), + servingEndpointPermissionSchema, + ), + makeTemplateResourceVariant(z.literal("volume"), volumePermissionSchema), + makeTemplateResourceVariant( + z.literal("vector_search_index"), + vectorSearchIndexPermissionSchema, + ), + makeTemplateResourceVariant( + z.literal("uc_function"), + ucFunctionPermissionSchema, + ), + makeTemplateResourceVariant( + z.literal("uc_connection"), + ucConnectionPermissionSchema, + ), + makeTemplateResourceVariant( + z.literal("database"), + databasePermissionSchema, + ), + makeTemplateResourceVariant( + z.literal("postgres"), + postgresPermissionSchema, + ), + makeTemplateResourceVariant( + z.literal("genie_space"), + genieSpacePermissionSchema, + ), + makeTemplateResourceVariant( + z.literal("experiment"), + experimentPermissionSchema, + ), + makeTemplateResourceVariant(z.literal("app"), appPermissionSchema), + ]) + .describe( + "Resource requirement with template-specific field entries (includes computed origin).", + ); + +// ── Template plugin (extends plugin manifest) ──────────────────────────── + +export const templatePluginSchema = z + .object({ + name: z + .string() + .regex(/^[a-z][a-z0-9-]*$/) + .describe( + "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", + ), + displayName: z + .string() + .min(1) + .describe("Human-readable display name for UI and CLI"), + description: z + .string() + .min(1) + .describe("Brief description of what the plugin does"), + package: z + .string() + .min(1) + .describe("NPM package name that provides this plugin"), + requiredByTemplate: z + .boolean() + .optional() + .describe( + "When true, this plugin is required by the template and cannot be deselected during CLI init. The user will only be prompted to configure its resources. When absent or false, the plugin is optional and the user can choose whether to include it.", + ), + onSetupMessage: z + .string() + .optional() + .describe( + "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning).", + ), + stability: z + .enum(["beta", "ga"]) + .optional() + .describe( + "Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver.", + ), + postScaffold: z + .array(postScaffoldStepSchema) + .optional() + .describe( + "Ordered list of post-scaffolding instructions propagated from the plugin manifest.", + ), + resources: z + .object({ + required: z + .array(templateResourceRequirementSchema) + .describe( + "Resources that must be available for the plugin to function", + ), + optional: z + .array(templateResourceRequirementSchema) + .describe( + "Resources that enhance functionality but are not mandatory", + ), + }) + .strict() + .describe("Databricks resource requirements for this plugin"), + }) + .strict() + .describe("Plugin manifest with package source information"); + +// ── Scaffolding descriptor ─────────────────────────────────────────────── + +export const scaffoldingFlagSchema = z + .object({ + description: z.string().describe("Human-readable description of the flag."), + required: z.boolean().optional().describe("Whether this flag is required."), + pattern: z + .string() + .optional() + .describe("Regex pattern for validating the flag value."), + default: z.string().optional().describe("Default value for this flag."), + }) + .strict() + .describe("A flag for the scaffolding command."); + +export const scaffoldingRulesSchema = z + .object({ + never: z + .array(z.string()) + .optional() + .describe("Actions the scaffolding agent must never perform."), + must: z + .array(z.string()) + .optional() + .describe("Actions the scaffolding agent must always perform."), + }) + .strict() + .describe("Structured rules for scaffolding agents."); + +export const scaffoldingDescriptorSchema = z + .object({ + command: z + .string() + .describe("The scaffolding command (e.g., 'databricks apps init')."), + flags: z + .record(z.string(), scaffoldingFlagSchema) + .optional() + .describe("Map of flag name to flag descriptor."), + rules: scaffoldingRulesSchema + .optional() + .describe("Structured rules for scaffolding agents."), + }) + .strict() + .describe( + "Describes the scaffolding command, flags, and rules for project initialization.", + ); + +// ── Template plugins manifest (root) ───────────────────────────────────── + +export const templatePluginsManifestSchema = z + .object({ + $schema: z + .string() + .optional() + .describe("Reference to the JSON Schema for validation"), + version: z + .enum(["1.0", "1.1", "2.0"]) + .describe("Schema version for the template plugins manifest"), + plugins: z + .record(z.string(), templatePluginSchema) + .describe("Map of plugin name to plugin manifest with package source"), + scaffolding: scaffoldingDescriptorSchema + .optional() + .describe( + "Describes the scaffolding command and its configuration for project initialization.", + ), + }) + .strict() + .superRefine((value, ctx) => { + if (value.version === "2.0" && !value.scaffolding) { + ctx.addIssue({ + code: "custom", + path: ["scaffolding"], + message: "scaffolding is required when version is '2.0'", + }); + } + }) + .describe( + "Aggregated plugin manifest for AppKit templates. Read by Databricks CLI during init to discover available plugins and their resource requirements.", + ); + +// ── Inferred types ─────────────────────────────────────────────────────── + +export type ResourceType = z.infer; +export type SecretPermission = z.infer; +export type JobPermission = z.infer; +export type SqlWarehousePermission = z.infer< + typeof sqlWarehousePermissionSchema +>; +export type ServingEndpointPermission = z.infer< + typeof servingEndpointPermissionSchema +>; +export type VolumePermission = z.infer; +export type VectorSearchIndexPermission = z.infer< + typeof vectorSearchIndexPermissionSchema +>; +export type UcFunctionPermission = z.infer; +export type UcConnectionPermission = z.infer< + typeof ucConnectionPermissionSchema +>; +export type DatabasePermission = z.infer; +export type PostgresPermission = z.infer; +export type GenieSpacePermission = z.infer; +export type ExperimentPermission = z.infer; +export type AppPermission = z.infer; +export type DiscoveryDescriptor = z.infer; +export type ResourceFieldEntry = z.infer; +export type ResourceRequirement = z.infer; +export type ConfigSchemaProperty = z.infer; +export type ConfigSchema = z.infer; +export type PostScaffoldStep = z.infer; +export type PluginManifest = z.infer; +export type Origin = z.infer; +export type TemplateFieldEntry = z.infer; +export type TemplateResourceRequirement = z.infer< + typeof templateResourceRequirementSchema +>; +export type TemplatePlugin = z.infer; +export type ScaffoldingFlag = z.infer; +export type ScaffoldingRules = z.infer; +export type ScaffoldingDescriptor = z.infer; +export type TemplatePluginsManifest = z.infer< + typeof templatePluginsManifestSchema +>; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba4b8ef8e..08b94b3e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -539,6 +539,9 @@ importers: '@clack/prompts': specifier: 1.0.1 version: 1.0.1 + '@standard-schema/spec': + specifier: 1.1.0 + version: 1.1.0 ajv: specifier: 8.17.1 version: 8.17.1 @@ -548,6 +551,9 @@ importers: commander: specifier: 12.1.0 version: 12.1.0 + zod: + specifier: 4.3.6 + version: 4.3.6 devDependencies: '@types/express': specifier: 4.17.23 @@ -721,24 +727,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@ast-grep/napi-linux-arm64-musl@0.37.0': resolution: {integrity: sha512-LF9sAvYy6es/OdyJDO3RwkX3I82Vkfsng1sqUBcoWC1jVb1wX5YVzHtpQox9JrEhGl+bNp7FYxB4Qba9OdA5GA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@ast-grep/napi-linux-x64-gnu@0.37.0': resolution: {integrity: sha512-TViz5/klqre6aSmJzswEIjApnGjJzstG/SE8VDWsrftMBMYt2PTu3MeluZVwzSqDao8doT/P+6U11dU05UOgxw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@ast-grep/napi-linux-x64-musl@0.37.0': resolution: {integrity: sha512-/BcCH33S9E3ovOAEoxYngUNXgb+JLg991sdyiNP2bSoYd30a9RHrG7CYwW6fMgua3ijQ474eV6cq9yZO1bCpXg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@ast-grep/napi-win32-arm64-msvc@0.37.0': resolution: {integrity: sha512-TjQA4cFoIEW2bgjLkaL9yqT4XWuuLa5MCNd0VCDhGRDMNQ9+rhwi9eLOWRaap3xzT7g+nlbcEHL3AkVCD2+b3A==} @@ -1425,24 +1435,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.2.6': resolution: {integrity: sha512-BpGtuMJGN+o8pQjvYsUKZ+4JEErxdSmcRD/JG3mXoWc6zrcA7OkuyGFN1mDggO0Q1n7qXxo/PcupHk8gzijt5g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.2.6': resolution: {integrity: sha512-1ZcBux8zVM3JhWN2ZCPaYf0+ogxXG316uaoXJdgoPZcdK/rmRcRY7PqHdAos2ExzvjIdvhQp72UcveI98hgOog==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.2.6': resolution: {integrity: sha512-1HaM/dpI/1Z68zp8ZdT6EiBq+/O/z97a2AiHMl+VAdv5/ELckFt9EvRb8hDHpk8hUMoz03gXkC7VPXOVtU7faA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.2.6': resolution: {integrity: sha512-h3A88G8PGM1ryTeZyLlSdfC/gz3e95EJw9BZmA6Po412DRqwqPBa2Y9U+4ZSGUAXCsnSQE00jLV8Pyrh0d+jQw==} @@ -1476,16 +1490,19 @@ packages: resolution: {integrity: sha512-q5O2RoJ/VqnMSawAGqLeYgvX/tMd8Uyd35fBX9DqkL8eABcUSydL6c2I3u/ImrucxohtSG0KS8eVIoX5HKQW2Q==} cpu: [x64] os: [linux] + libc: glibc '@cdxgen/cdxgen-plugins-bin-linux-arm64@2.0.2': resolution: {integrity: sha512-tGu5A68jBtrSsu469pYFKH54ckXzdcesa+adSMQJLo7oxI76cGyhnXgWYaaLoLtKTvBZxsxqquRxTQrVve9AHg==} cpu: [arm64] os: [linux] + libc: glibc '@cdxgen/cdxgen-plugins-bin-linux-arm@2.0.2': resolution: {integrity: sha512-G0j0QDwKJDXHJKNBsOZQMkXQLfC0yPxsU9ZFyCc5aPo0NUGZFnTCg2NqMvmKuRbC8PtESswYETPWbQomBubY2A==} cpu: [arm] os: [linux] + libc: glibc '@cdxgen/cdxgen-plugins-bin-linux-ppc64@2.0.2': resolution: {integrity: sha512-OofcMJasdLcWBkI9EljimtSXxN6UlPgNj19nC2rFhpLig+S6984DpHqHpymLz0dI6l/n99bRMmVCftnISWH5Ag==} @@ -1496,11 +1513,13 @@ packages: resolution: {integrity: sha512-7V0dQUHhl6TbBUxeOncBqIx7rXAF7oP5w3zKBoSGkIEhyt5ViKwzwpZgeK/YOHsrp42sEpzBgCasCnNIdNDzKA==} cpu: [x64] os: [linux] + libc: musl '@cdxgen/cdxgen-plugins-bin-linuxmusl-arm64@2.0.2': resolution: {integrity: sha512-/r62CQtl+tp+zWCXbpT87IUq06eY+MYXgIIGQ79fmTmKv8EL4g7d21vNaI0O5KxXC16Orp4ihiAmr7o86uFdQQ==} cpu: [arm64] os: [linux] + libc: musl '@cdxgen/cdxgen-plugins-bin-windows-amd64@2.0.2': resolution: {integrity: sha512-++U1N5OX/pwZXupSJWQPv8nKryw5CDAlK52ts3i9zyQdTgWWBl/GLG642SJ9Y2oWYBtbKi2z9m9zyMEAn5VdZA==} @@ -3293,41 +3312,49 @@ packages: resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.19.1': resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.19.1': resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.19.1': resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==} cpu: [x64] os: [linux] + libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.19.1': resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==} @@ -4163,72 +4190,84 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5': resolution: {integrity: sha512-KSol1De1spMZL+Xg7K5IBWXIvRWv7+pveaxFWXpezezAG7CS6ojzRjtCGCiLxQricutTAi/LkNWKMsd2wNhMKQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.41': resolution: {integrity: sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.5': resolution: {integrity: sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.41': resolution: {integrity: sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.5': resolution: {integrity: sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-beta.41': resolution: {integrity: sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-rc.5': resolution: {integrity: sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-beta.41': resolution: {integrity: sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg==} @@ -4354,56 +4393,67 @@ packages: resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.4': resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.4': resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.4': resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.4': resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.4': resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.4': resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.4': resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.4': resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.4': resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.4': resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.4': resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} @@ -4624,24 +4674,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -8264,24 +8318,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index 2f456ba8c..ab297baf2 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -138,7 +138,8 @@ "fields": { "id": { "env": "DATABRICKS_JOB_ID", - "description": "Numeric Databricks job ID. Find it in the Jobs UI or via `databricks jobs list`." + "description": "Numeric Databricks job ID. Find it in the Jobs UI or via `databricks jobs list`.", + "origin": "user" } } } @@ -266,7 +267,8 @@ "fields": { "name": { "env": "DATABRICKS_SERVING_ENDPOINT_NAME", - "description": "Serving endpoint name" + "description": "Serving endpoint name", + "origin": "user" } } } diff --git a/tools/generate-json-schema.ts b/tools/generate-json-schema.ts new file mode 100644 index 000000000..842e4c4d4 --- /dev/null +++ b/tools/generate-json-schema.ts @@ -0,0 +1,98 @@ +/** + * Generates JSON Schema artifacts from the Zod authoring module + * (packages/shared/src/schemas/manifest.ts) and writes them into the docs + * site's static schemas directory. Those files are what the published + * docs URL serves to plugin authors' editors via $schema. + * + * Phase 1: this generator runs alongside the existing + * `tools/generate-schema-types.ts` (which still emits TypeScript types from + * the hand-written JSON Schema). Phases 2-5 cut downstream consumers over + * to the Zod-authored shape and delete the legacy generator. + * + * Run from repo root: pnpm exec tsx tools/generate-json-schema.ts + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { z } from "zod"; +import { + pluginManifestSchema, + templatePluginsManifestSchema, +} from "../packages/shared/src/schemas/manifest.ts"; +import { formatWithBiome } from "./format-with-biome.ts"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.join(__dirname, ".."); +const DOCS_SCHEMAS_DIR = path.join(REPO_ROOT, "docs/static/schemas"); + +const PLUGIN_OUT_PATH = path.join( + DOCS_SCHEMAS_DIR, + "plugin-manifest.schema.json", +); +const TEMPLATE_OUT_PATH = path.join( + DOCS_SCHEMAS_DIR, + "template-plugins.schema.json", +); + +const PLUGIN_SCHEMA_ID = + "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json"; +const TEMPLATE_SCHEMA_ID = + "https://databricks.github.io/appkit/schemas/template-plugins.schema.json"; + +function emit( + schema: z.ZodType, + schemaId: string, + title: string, +): Record { + // Targeting draft-07 keeps parity with the existing hand-written schemas + // (which are draft-07). The default in Zod 4 is draft-2020-12. + const generated = z.toJSONSchema(schema, { target: "draft-07" }); + // Inject $id and title at the top level so editor tooling can identify + // the schema by URL even when fetched out-of-band. + return { + $schema: "http://json-schema.org/draft-07/schema#", + $id: schemaId, + title, + ...stripTopLevelSchemaKey(generated), + }; +} + +/** + * Zod's `toJSONSchema` emits `$schema` at the root. We re-construct it in + * a deterministic order alongside `$id` and `title`, so this strips the + * key from the generated shape before merging. + */ +function stripTopLevelSchemaKey( + obj: Record, +): Record { + const { $schema: _ignored, ...rest } = obj; + return rest; +} + +function writeJson(outPath: string, value: unknown): void { + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + fs.writeFileSync(outPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8"); + formatWithBiome(outPath); + console.log("Wrote", outPath); +} + +async function main(): Promise { + const pluginJson = emit( + pluginManifestSchema, + PLUGIN_SCHEMA_ID, + "AppKit Plugin Manifest", + ); + const templateJson = emit( + templatePluginsManifestSchema, + TEMPLATE_SCHEMA_ID, + "AppKit Template Plugins Manifest", + ); + + writeJson(PLUGIN_OUT_PATH, pluginJson); + writeJson(TEMPLATE_OUT_PATH, templateJson); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); From 427c34517ca91c8b3b92607e3f5dbf90633f0478 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 20:55:24 +0200 Subject: [PATCH 09/17] refactor: switch validation runtime to standard schema (phase 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace AJV with Zod-via-Standard-Schema in validate-manifest.ts. The CLI validator now calls `~standard.validate` against the Zod schemas authored in phase 1; consumer code never imports zod directly. Cycle detection, dangling-reference checks, and the placeholder constraint move from the standalone runSemanticValidation pass into Zod refinements co-located with the shape: - resourceRequirementSchema gains a superRefine running DFS over discovery.dependsOn — dangling refs emit at fields..discovery.dependsOn, cycles emit at the resource root with the existing 'a → b → c → a' chain. - discoveryDescriptorSchema.refine() enforces the placeholder on cliCommand. - postScaffoldStepSchema.instruction tightens to z.string().min(1). origin-drift detection (validateDiscoveryOrigin) is dropped — origin becomes a transform in phase 3, eliminating the desync surface entirely. validate-manifest.ts shrinks from 498 to 177 lines: loadSchema, getPluginValidator, getTemplateValidator, the AJV compile cache, the JSON-pointer humanizer, the AJV error formatter, runSemanticValidation, validateDependsOn, validateDiscoveryProfile, validateDiscoveryOrigin, validatePostScaffold, and formatSemanticIssues all delete. Tests rewrite to drive validateManifest end-to-end and assert on the resulting SemanticIssue shape. validateManifest returns the original input object as `manifest` rather than result.output — Zod parsing is used purely as a verifier here so property order is preserved for round-trip writers like add-resource. Phase 3 will introduce the first real transform (origin), at which point output-vs-input distinction becomes intentional. ajv and ajv-formats remain in packages/shared/package.json for the phase-1 parity test (deletes in phase 5). Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../src/cli/commands/plugin/sync/sync.ts | 4 +- .../plugin/validate/validate-manifest.test.ts | 323 ++++------- .../plugin/validate/validate-manifest.ts | 529 ++++-------------- .../cli/commands/plugin/validate/validate.ts | 85 +-- packages/shared/src/schemas/manifest.ts | 92 ++- 5 files changed, 319 insertions(+), 714 deletions(-) diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.ts b/packages/shared/src/cli/commands/plugin/sync/sync.ts index 565e6f2a2..af7a2af5d 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.ts @@ -64,7 +64,7 @@ function enrichFieldsWithOrigin( } /** - * Validates a parsed JSON object against the plugin-manifest JSON schema. + * Validates a parsed JSON object against the plugin-manifest schema. * Returns the manifest if valid, or null and logs schema errors. */ function validateManifestWithSchema( @@ -75,7 +75,7 @@ function validateManifestWithSchema( if (result.valid && result.manifest) return result.manifest; if (result.errors?.length) { console.warn( - `Warning: Manifest at ${sourcePath} failed schema validation:\n${formatValidationErrors(result.errors, obj)}`, + `Warning: Manifest at ${sourcePath} failed schema validation:\n${formatValidationErrors(result.errors)}`, ); } return null; diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts index 2999858bf..86d2e0034 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts @@ -1,11 +1,8 @@ -import type { ErrorObject } from "ajv"; import { describe, expect, it } from "vitest"; -import type { PluginManifest } from "./validate-manifest"; import { detectSchemaType, - formatSemanticIssues, formatValidationErrors, - runSemanticValidation, + type SemanticIssue, validateManifest, validateTemplateManifest, } from "./validate-manifest"; @@ -237,168 +234,133 @@ describe("validate-manifest", () => { }); describe("formatValidationErrors", () => { - it("formats a required-property error", () => { - const errors: ErrorObject[] = [ + it("formats a single issue with humanized path", () => { + const issues: SemanticIssue[] = [ { - keyword: "required", - instancePath: "", - schemaPath: "#/required", - params: { missingProperty: "name" }, - message: "must have required property 'name'", + level: "error", + path: "name", + message: "Invalid string: must match pattern", }, ]; - const output = formatValidationErrors(errors); - expect(output).toContain('missing required property "name"'); + const output = formatValidationErrors(issues); + expect(output).toBe(" name: Invalid string: must match pattern"); }); - it("formats an enum error with actual value", () => { - const errors: ErrorObject[] = [ + it("formats nested array paths with bracket notation", () => { + const issues: SemanticIssue[] = [ { - keyword: "enum", - instancePath: "/resources/required/0/permission", - schemaPath: "#/$defs/secretPermission/enum", - params: { allowedValues: ["MANAGE", "READ", "WRITE"] }, - message: "must be equal to one of the allowed values", + level: "error", + path: "resources.required[0].permission", + message: "Invalid option", }, ]; - const obj = { - resources: { - required: [{ permission: "INVALID" }], - }, - }; - const output = formatValidationErrors(errors, obj); + const output = formatValidationErrors(issues); expect(output).toContain("resources.required[0].permission"); - expect(output).toContain('(got "INVALID")'); - expect(output).toContain("MANAGE, READ, WRITE"); + expect(output).toContain("Invalid option"); }); - it("formats a pattern error with actual value", () => { - const errors: ErrorObject[] = [ + it("emits one line per issue", () => { + const issues: SemanticIssue[] = [ { - keyword: "pattern", - instancePath: "/name", - schemaPath: "#/properties/name/pattern", - params: { pattern: "^[a-z][a-z0-9-]*$" }, - message: 'must match pattern "^[a-z][a-z0-9-]*$"', + level: "error", + path: "name", + message: "missing", }, - ]; - const obj = { name: "INVALID" }; - const output = formatValidationErrors(errors, obj); - expect(output).toContain("name"); - expect(output).toContain("does not match expected pattern"); - expect(output).toContain('(got "INVALID")'); - }); - - it("formats a type error", () => { - const errors: ErrorObject[] = [ { - keyword: "type", - instancePath: "/name", - schemaPath: "#/properties/name/type", - params: { type: "string" }, - message: "must be string", + level: "error", + path: "displayName", + message: "must not be empty", }, ]; - const output = formatValidationErrors(errors); - expect(output).toContain('expected type "string"'); + const output = formatValidationErrors(issues); + const lines = output.split("\n"); + expect(lines).toHaveLength(2); }); - it("formats a minLength error", () => { - const errors: ErrorObject[] = [ - { - keyword: "minLength", - instancePath: "/displayName", - schemaPath: "#/properties/displayName/minLength", - params: { limit: 1 }, - message: "must NOT have fewer than 1 characters", - }, - ]; - const output = formatValidationErrors(errors); - expect(output).toContain("must not be empty"); + it("handles empty issue list", () => { + expect(formatValidationErrors([])).toBe(""); }); + }); - it("formats an additionalProperties error", () => { - const errors: ErrorObject[] = [ - { - keyword: "additionalProperties", - instancePath: "", - schemaPath: "#/additionalProperties", - params: { additionalProperty: "foo" }, - message: "must NOT have additional properties", - }, - ]; - const output = formatValidationErrors(errors); - expect(output).toContain('unknown property "foo"'); + describe("validation error contents (semantic equivalence)", () => { + it("reports missing required property", () => { + const result = validateManifest({ name: "test" }); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + // Zod reports missing required props as "Invalid input: expected ..." + expect(formatted).toMatch(/displayName|description|resources/); }); - it("collapses anyOf with enum sub-errors", () => { - const errors: ErrorObject[] = [ - { - keyword: "enum", - instancePath: "/perm", - schemaPath: "#/$defs/a/enum", - params: { allowedValues: ["A", "B"] }, - message: "must be equal to one of the allowed values", - }, - { - keyword: "enum", - instancePath: "/perm", - schemaPath: "#/$defs/b/enum", - params: { allowedValues: ["C", "D"] }, - message: "must be equal to one of the allowed values", - }, - { - keyword: "anyOf", - instancePath: "/perm", - schemaPath: "#/anyOf", - params: {}, - message: "must match a schema in anyOf", - }, - ]; - const obj = { perm: "X" }; - const output = formatValidationErrors(errors, obj); - expect(output).toContain('invalid value (got "X")'); - expect(output).toContain("A, B, C, D"); - const lines = output.split("\n"); - expect(lines.length).toBe(2); + it("reports invalid name pattern with the path", () => { + const result = validateManifest({ + ...VALID_MANIFEST, + name: "INVALID", + }); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("name"); + expect(formatted).toMatch(/pattern/i); }); - it("skips if-keyword errors", () => { - const errors: ErrorObject[] = [ - { - keyword: "if", - instancePath: "", - schemaPath: "#/allOf/0/if", - params: { failingKeyword: "if" }, - message: 'must match "if" schema', + it("reports invalid permission for type with allowed enum hints", () => { + const result = validateManifest({ + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "sql_warehouse", + alias: "Warehouse", + resourceKey: "wh", + description: "wh", + permission: "INVALID_PERM", + fields: { id: { env: "TEST_ID" } }, + }, + ], + optional: [], }, - ]; - const output = formatValidationErrors(errors); - expect(output).toBe(""); + }); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("resources.required[0].permission"); + expect(formatted).toMatch(/CAN_USE/); }); - it("handles root-level errors with empty instancePath", () => { - const errors: ErrorObject[] = [ - { - keyword: "required", - instancePath: "", - schemaPath: "#/required", - params: { missingProperty: "name" }, - message: "must have required property 'name'", - }, - ]; - const output = formatValidationErrors(errors); - expect(output).toContain('missing required property "name"'); + it("reports unknown property at root", () => { + const result = validateManifest({ + ...VALID_MANIFEST, + nonsenseField: "boom", + }); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toMatch(/nonsenseField|Unrecognized/); + }); + + it("reports type mismatch", () => { + const result = validateManifest({ + ...VALID_MANIFEST, + name: 42, + }); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("name"); + expect(formatted).toMatch(/expected string|received number/); + }); + + it("reports empty-string failures from min(1)", () => { + const result = validateManifest({ + ...VALID_MANIFEST, + displayName: "", + }); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("displayName"); }); }); - describe("semantic validation", () => { - it("returns no issues for a valid manifest without discovery", () => { - const result = runSemanticValidation( - VALID_MANIFEST_WITH_RESOURCE as PluginManifest, - ); - expect(result.errors).toHaveLength(0); - expect(result.warnings).toHaveLength(0); + describe("semantic refinements (cycles, dangling refs, )", () => { + it("returns valid for a manifest without discovery", () => { + const result = validateManifest(VALID_MANIFEST_WITH_RESOURCE); + expect(result.valid).toBe(true); }); it("detects dangling dependsOn reference", () => { @@ -429,12 +391,11 @@ describe("validate-manifest", () => { optional: [], }, }; - const result = runSemanticValidation( - manifest as unknown as PluginManifest, - ); - expect(result.errors).toHaveLength(1); - expect(result.errors[0].message).toContain("non-existent sibling field"); - expect(result.errors[0].message).toContain("nonexistent"); + const result = validateManifest(manifest); + expect(result.valid).toBe(false); + const messages = (result.errors ?? []).map((e) => e.message).join("\n"); + expect(messages).toContain("non-existent sibling field"); + expect(messages).toContain("nonexistent"); }); it("detects cyclic dependsOn chain", () => { @@ -471,13 +432,14 @@ describe("validate-manifest", () => { optional: [], }, }; - const result = runSemanticValidation( - manifest as unknown as PluginManifest, - ); - const cycleErrors = result.errors.filter((e) => + const result = validateManifest(manifest); + expect(result.valid).toBe(false); + const cycleErrors = (result.errors ?? []).filter((e) => e.message.includes("cycle"), ); expect(cycleErrors.length).toBeGreaterThan(0); + // Cycle path targets the resource, not a specific field. + expect(cycleErrors[0].path).toContain("resources.required[0]"); }); it("detects missing in cliCommand", () => { @@ -505,45 +467,21 @@ describe("validate-manifest", () => { optional: [], }, }; - const result = runSemanticValidation( - manifest as unknown as PluginManifest, - ); - expect(result.errors).toHaveLength(1); - expect(result.errors[0].message).toContain(""); + const result = validateManifest(manifest); + expect(result.valid).toBe(false); + const messages = (result.errors ?? []).map((e) => e.message).join("\n"); + expect(messages).toContain(""); }); - it("warns when discovery is on non-user origin field", () => { + it("rejects empty postScaffold instruction", () => { const manifest = { ...VALID_MANIFEST, - resources: { - required: [ - { - type: "postgres", - alias: "Postgres", - resourceKey: "postgres", - description: "test", - permission: "CAN_CONNECT_AND_CREATE", - fields: { - host: { - env: "PGHOST", - localOnly: true, - discovery: { - cliCommand: "databricks cmd --profile ", - selectField: ".host", - }, - }, - }, - }, - ], - optional: [], - }, + postScaffold: [{ instruction: "" }], }; - const result = runSemanticValidation( - manifest as unknown as PluginManifest, - ); - expect(result.errors).toHaveLength(0); - expect(result.warnings).toHaveLength(1); - expect(result.warnings[0].message).toContain("platform"); + const result = validateManifest(manifest); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("postScaffold[0].instruction"); }); it("passes for valid manifest with all new fields", () => { @@ -578,29 +516,8 @@ describe("validate-manifest", () => { optional: [], }, }; - const result = runSemanticValidation( - manifest as unknown as PluginManifest, - ); - expect(result.errors).toHaveLength(0); - expect(result.warnings).toHaveLength(0); - }); - - it("formats semantic issues correctly", () => { - const issues = [ - { - level: "error" as const, - path: "resources.postgres.fields.host", - message: "test error", - }, - { - level: "warning" as const, - path: "postScaffold[0]", - message: "test warning", - }, - ]; - const output = formatSemanticIssues(issues); - expect(output).toContain("resources.postgres.fields.host: test error"); - expect(output).toContain("postScaffold[0]: test warning"); + const result = validateManifest(manifest); + expect(result.valid).toBe(true); }); }); }); diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts index 40ae274cb..062d9f87b 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts @@ -1,23 +1,12 @@ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import Ajv, { type ErrorObject } from "ajv"; -import addFormats from "ajv-formats"; -import { computeOrigin, type PluginManifest } from "../manifest-types"; +import type { StandardSchemaV1 } from "@standard-schema/spec"; +import { + pluginManifestSchema, + templatePluginsManifestSchema, +} from "../../../../schemas/manifest"; +import type { PluginManifest } from "../manifest-types"; export type { PluginManifest }; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const SCHEMAS_DIR = path.join(__dirname, "..", "..", "..", "..", "schemas"); -const PLUGIN_MANIFEST_SCHEMA_PATH = path.join( - SCHEMAS_DIR, - "plugin-manifest.schema.json", -); -const TEMPLATE_PLUGINS_SCHEMA_PATH = path.join( - SCHEMAS_DIR, - "template-plugins.schema.json", -); - export type SchemaType = "plugin-manifest" | "template-plugins" | "unknown"; const SCHEMA_ID_MAP: Record = { @@ -38,112 +27,128 @@ export function detectSchemaType(obj: unknown): SchemaType { return SCHEMA_ID_MAP[schemaUrl] ?? "unknown"; } +/** + * A single validation issue produced by the schema validator. The `path` + * is a humanized property path (e.g., `resources.required[0].permission`) + * suitable for direct CLI output. + */ +export interface SemanticIssue { + level: "error" | "warning"; + path: string; + message: string; +} + export interface ValidateResult { valid: boolean; manifest?: PluginManifest; - errors?: ErrorObject[]; + errors?: SemanticIssue[]; } -let schemaLoadWarned = false; - -function loadSchema(schemaPath: string): object | null { - try { - return JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as object; - } catch (err) { - if (!schemaLoadWarned) { - schemaLoadWarned = true; - console.warn( - `Warning: Could not load JSON schema at ${schemaPath}: ${err instanceof Error ? err.message : err}. Falling back to basic validation.`, - ); +/** + * Convert a Standard Schema issue path (array of property keys / path segments) + * into a humanized string like `resources.required[0].permission`. + */ +function humanizePath( + path: ReadonlyArray | undefined, +): string { + if (!path || path.length === 0) return "(root)"; + + let out = ""; + for (const segment of path) { + const key = + typeof segment === "object" && segment !== null && "key" in segment + ? segment.key + : segment; + if (typeof key === "number") { + out += `[${key}]`; + } else { + const str = String(key); + out += out.length === 0 ? str : `.${str}`; } - return null; - } -} - -let compiledPluginValidator: ReturnType | null = null; - -function getPluginValidator(): ReturnType | null { - if (compiledPluginValidator) return compiledPluginValidator; - const schema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH); - if (!schema) return null; - try { - const ajv = new Ajv({ allErrors: true, strict: false }); - addFormats(ajv); - compiledPluginValidator = ajv.compile(schema); - return compiledPluginValidator; - } catch { - return null; } + return out.length === 0 ? "(root)" : out; } -let compiledTemplateValidator: ReturnType | null = null; - -function getTemplateValidator(): ReturnType | null { - if (compiledTemplateValidator) return compiledTemplateValidator; - const pluginSchema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH); - const templateSchema = loadSchema(TEMPLATE_PLUGINS_SCHEMA_PATH); - if (!pluginSchema || !templateSchema) return null; - try { - const ajv = new Ajv({ allErrors: true, strict: false }); - addFormats(ajv); - ajv.addSchema(pluginSchema); - compiledTemplateValidator = ajv.compile(templateSchema); - return compiledTemplateValidator; - } catch { - return null; +/** + * Map a Standard Schema issue list into the legacy `SemanticIssue` shape so + * consumer code can stay uniform. + */ +function mapIssues( + issues: ReadonlyArray, +): SemanticIssue[] { + const result: SemanticIssue[] = []; + for (const issue of issues) { + result.push({ + level: "error", + path: humanizePath(issue.path), + message: issue.message, + }); } + return result; } /** - * Validate a manifest object against the plugin-manifest JSON schema. - * Returns validation result with optional errors for CLI output. + * Validate an object against a Standard Schema and produce a uniform + * `ValidateResult`. The manifest output is captured for downstream consumers. */ -export function validateManifest(obj: unknown): ValidateResult { - if (!obj || typeof obj !== "object") { +function validateWithStandardSchema( + schema: StandardSchemaV1, + obj: unknown, +): { valid: true; output: T } | { valid: false; errors: SemanticIssue[] } { + const result = schema["~standard"].validate(obj); + if (result instanceof Promise) { + // Our schemas are synchronous; treat any async resolution as an error + // rather than blocking the CLI on a background promise. return { valid: false, errors: [ { - instancePath: "", - message: "Manifest is not a valid object", - } as ErrorObject, + level: "error", + path: "(root)", + message: + "Schema returned an async validation result; expected synchronous validation.", + }, ], }; } + if (result.issues) { + return { valid: false, errors: mapIssues(result.issues) }; + } + return { valid: true, output: result.value }; +} - const validate = getPluginValidator(); - if (!validate) { - const m = obj as Record; - const basicValid = - typeof m.name === "string" && - m.name.length > 0 && - typeof m.displayName === "string" && - m.displayName.length > 0 && - typeof m.description === "string" && - m.description.length > 0 && - m.resources && - typeof m.resources === "object" && - Array.isArray((m.resources as { required?: unknown }).required); - if (basicValid) return { valid: true, manifest: obj as PluginManifest }; +/** + * Validate a manifest object against the plugin-manifest schema. + * Returns validation result with optional errors for CLI output. + * + * On success, the returned `manifest` is the original input object (preserving + * its property order) typed as `PluginManifest`, not a re-emitted Zod copy. + * Round-trip writers like `add-resource` rely on this to keep the on-disk + * field ordering stable. + */ +export function validateManifest(obj: unknown): ValidateResult { + if (!obj || typeof obj !== "object") { return { valid: false, errors: [ { - instancePath: "", - message: "Invalid manifest structure", - } as ErrorObject, + level: "error", + path: "(root)", + message: "Manifest is not a valid object", + }, ], }; } - const valid = validate(obj); - if (valid) return { valid: true, manifest: obj as PluginManifest }; - return { valid: false, errors: validate.errors ?? [] }; + const result = validateWithStandardSchema(pluginManifestSchema, obj); + if (result.valid) { + return { valid: true, manifest: obj as PluginManifest }; + } + return { valid: false, errors: result.errors }; } /** * Validate a template-plugins manifest (appkit.plugins.json) against its schema. - * Registers the plugin-manifest schema first so external $refs resolve. */ export function validateTemplateManifest(obj: unknown): ValidateResult { if (!obj || typeof obj !== "object") { @@ -151,347 +156,27 @@ export function validateTemplateManifest(obj: unknown): ValidateResult { valid: false, errors: [ { - instancePath: "", + level: "error", + path: "(root)", message: "Template manifest is not a valid object", - } as ErrorObject, - ], - }; - } - - const validate = getTemplateValidator(); - if (!validate) { - const m = obj as Record; - const basicValid = - typeof m.version === "string" && - m.plugins && - typeof m.plugins === "object"; - if (basicValid) return { valid: true }; - return { - valid: false, - errors: [ - { - instancePath: "", - message: "Invalid template manifest structure", - } as ErrorObject, + }, ], }; } - const valid = validate(obj); - if (valid) return { valid: true }; - return { valid: false, errors: validate.errors ?? [] }; -} - -/** - * Convert a JSON pointer like /resources/required/0/permission - * to a readable path like resources.required[0].permission - */ -function humanizePath(instancePath: string): string { - if (!instancePath) return "(root)"; - return instancePath - .replace(/^\//, "") - .replace(/\/(\d+)\//g, "[$1].") - .replace(/\/(\d+)$/g, "[$1]") - .replace(/\//g, "."); -} - -/** - * Resolve a JSON pointer to the actual value in the parsed object. - */ -function resolvePointer(obj: unknown, instancePath: string): unknown { - if (!instancePath) return obj; - const segments = instancePath.replace(/^\//, "").split("/"); - let current: unknown = obj; - for (const seg of segments) { - if (current == null || typeof current !== "object") return undefined; - current = (current as Record)[seg]; + const result = validateWithStandardSchema(templatePluginsManifestSchema, obj); + if (result.valid) { + return { valid: true }; } - return current; + return { valid: false, errors: result.errors }; } /** - * Format schema errors for CLI output. - * Collapses anyOf/oneOf sub-errors into a single message and shows - * the actual invalid value when available. - * - * @param errors - AJV error objects - * @param obj - The original parsed object (optional, used to show actual values) + * Format validation issues for CLI output. Each issue is rendered on its own + * line indented by two spaces, with the format ` : `. The + * `path` is already humanized when issues are produced by `validateManifest` + * / `validateTemplateManifest`. */ -export function formatValidationErrors( - errors: ErrorObject[], - obj?: unknown, -): string { - const grouped = new Map(); - for (const e of errors) { - const key = e.instancePath || "/"; - if (!grouped.has(key)) grouped.set(key, []); - const list = grouped.get(key); - if (list) list.push(e); - } - - const lines: string[] = []; - - for (const [path, errs] of grouped) { - const readable = humanizePath(path); - const anyOfErr = errs.find( - (e) => e.keyword === "anyOf" || e.keyword === "oneOf", - ); - - if (anyOfErr) { - const enumErrors = errs.filter((e) => e.keyword === "enum"); - if (enumErrors.length > 0) { - const allValues = [ - ...new Set( - enumErrors.flatMap( - (e) => (e.params?.allowedValues as string[]) ?? [], - ), - ), - ]; - const actual = - obj !== undefined ? resolvePointer(obj, path) : undefined; - const valueHint = - actual !== undefined ? ` (got ${JSON.stringify(actual)})` : ""; - lines.push( - ` ${readable}: invalid value${valueHint}`, - ` allowed: ${allValues.join(", ")}`, - ); - continue; - } - } - - for (const e of errs) { - if (e.keyword === "anyOf" || e.keyword === "oneOf") continue; - if (e.keyword === "if") continue; - if (anyOfErr && e.keyword === "enum") continue; - - if (e.keyword === "enum") { - const allowed = (e.params?.allowedValues as string[]) ?? []; - const actual = - obj !== undefined ? resolvePointer(obj, path) : undefined; - const valueHint = - actual !== undefined ? ` (got ${JSON.stringify(actual)})` : ""; - lines.push( - ` ${readable}: invalid value${valueHint}, allowed: ${allowed.join(", ")}`, - ); - } else if (e.keyword === "required") { - lines.push( - ` ${readable}: missing required property "${e.params?.missingProperty}"`, - ); - } else if (e.keyword === "additionalProperties") { - lines.push( - ` ${readable}: unknown property "${e.params?.additionalProperty}"`, - ); - } else if (e.keyword === "pattern") { - const actual = - obj !== undefined ? resolvePointer(obj, path) : undefined; - const valueHint = - actual !== undefined ? ` (got ${JSON.stringify(actual)})` : ""; - lines.push( - ` ${readable}: does not match expected pattern${valueHint}`, - ); - } else if (e.keyword === "type") { - lines.push(` ${readable}: expected type "${e.params?.type}"`); - } else if (e.keyword === "minLength") { - lines.push(` ${readable}: must not be empty`); - } else { - lines.push( - ` ${readable}: ${e.message}${e.params ? ` (${JSON.stringify(e.params)})` : ""}`, - ); - } - } - } - - return lines.join("\n"); -} - -// ── Semantic validation (cross-field / cross-resource rules) ──────────── - -export interface SemanticIssue { - level: "error" | "warning"; - path: string; - message: string; -} - -export interface SemanticValidateResult { - errors: SemanticIssue[]; - warnings: SemanticIssue[]; -} - -function validateDependsOn(manifest: PluginManifest): SemanticIssue[] { - const issues: SemanticIssue[] = []; - - for (const group of [ - manifest.resources.required, - manifest.resources.optional, - ]) { - for (const resource of group) { - if (!resource.fields) continue; - const fieldNames = new Set(Object.keys(resource.fields)); - - // Check dangling references - const deps = new Map(); - for (const [name, field] of Object.entries(resource.fields)) { - const discovery = (field as Record).discovery as - | { dependsOn?: string } - | undefined; - if (discovery?.dependsOn) { - if (!fieldNames.has(discovery.dependsOn)) { - issues.push({ - level: "error", - path: `resources.${resource.resourceKey}.fields.${name}.discovery.dependsOn`, - message: `references non-existent sibling field '${discovery.dependsOn}'`, - }); - } - deps.set(name, discovery.dependsOn); - } - } - - // Detect cycles via DFS - const visited = new Set(); - const visiting = new Set(); - - function dfs(node: string, chain: string[]): string[] | null { - if (visiting.has(node)) return [...chain, node]; - if (visited.has(node)) return null; - visiting.add(node); - const next = deps.get(node); - if (next) { - const cycle = dfs(next, [...chain, node]); - if (cycle) return cycle; - } - visiting.delete(node); - visited.add(node); - return null; - } - - for (const node of deps.keys()) { - if (!visited.has(node)) { - const cycle = dfs(node, []); - if (cycle) { - issues.push({ - level: "error", - path: `resources.${resource.resourceKey}`, - message: `discovery.dependsOn creates a cycle: ${cycle.join(" \u2192 ")}`, - }); - break; // one cycle error per resource is enough - } - } - } - } - } - - return issues; -} - -function validateDiscoveryProfile(manifest: PluginManifest): SemanticIssue[] { - const issues: SemanticIssue[] = []; - - for (const group of [ - manifest.resources.required, - manifest.resources.optional, - ]) { - for (const resource of group) { - if (!resource.fields) continue; - for (const [name, field] of Object.entries(resource.fields)) { - const discovery = (field as Record).discovery as - | { cliCommand?: string } - | undefined; - if ( - discovery?.cliCommand && - !discovery.cliCommand.includes("") - ) { - issues.push({ - level: "error", - path: `resources.${resource.resourceKey}.fields.${name}.discovery.cliCommand`, - message: "must include placeholder", - }); - } - } - } - } - - return issues; -} - -function validateDiscoveryOrigin(manifest: PluginManifest): SemanticIssue[] { - const issues: SemanticIssue[] = []; - - for (const group of [ - manifest.resources.required, - manifest.resources.optional, - ]) { - for (const resource of group) { - if (!resource.fields) continue; - for (const [name, field] of Object.entries(resource.fields)) { - const discovery = (field as Record).discovery; - if (!discovery) continue; - const origin = computeOrigin(field); - if (origin !== "user") { - issues.push({ - level: "warning", - path: `resources.${resource.resourceKey}.fields.${name}`, - message: `has discovery but computed origin is '${origin}' (not 'user') \u2014 discovery may not be used`, - }); - } - } - } - } - - return issues; -} - -function validatePostScaffold(manifest: PluginManifest): SemanticIssue[] { - const issues: SemanticIssue[] = []; - const { postScaffold } = manifest; - if (!postScaffold) return issues; - - if (!Array.isArray(postScaffold)) { - issues.push({ - level: "error", - path: "postScaffold", - message: "must be an array", - }); - return issues; - } - - for (let i = 0; i < postScaffold.length; i++) { - const step = postScaffold[i]; - if (!step || typeof step !== "object") { - issues.push({ - level: "error", - path: `postScaffold[${i}]`, - message: "must be an object", - }); - continue; - } - if (typeof step.instruction !== "string" || step.instruction.length === 0) { - issues.push({ - level: "error", - path: `postScaffold[${i}].instruction`, - message: "must be a non-empty string", - }); - } - } - - return issues; -} - -export function runSemanticValidation( - manifest: PluginManifest, -): SemanticValidateResult { - const allIssues = [ - ...validateDependsOn(manifest), - ...validateDiscoveryProfile(manifest), - ...validateDiscoveryOrigin(manifest), - ...validatePostScaffold(manifest), - ]; - - return { - errors: allIssues.filter((i) => i.level === "error"), - warnings: allIssues.filter((i) => i.level === "warning"), - }; -} - -export function formatSemanticIssues(issues: SemanticIssue[]): string { - return issues.map((i) => ` ${i.path}: ${i.message}`).join("\n"); +export function formatValidationErrors(issues: SemanticIssue[]): string { + return issues.map((issue) => ` ${issue.path}: ${issue.message}`).join("\n"); } diff --git a/packages/shared/src/cli/commands/plugin/validate/validate.ts b/packages/shared/src/cli/commands/plugin/validate/validate.ts index 6b35ac597..fd0a20a86 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate.ts @@ -7,16 +7,10 @@ import { type ResolvedManifest, resolveManifestInDir, } from "../manifest-resolve"; -import type { - PluginManifest, - SemanticValidateResult, - ValidateResult, -} from "./validate-manifest"; +import type { ValidateResult } from "./validate-manifest"; import { detectSchemaType, - formatSemanticIssues, formatValidationErrors, - runSemanticValidation, validateManifest, validateTemplateManifest, } from "./validate-manifest"; @@ -128,60 +122,21 @@ async function runPluginValidate( } const schemaType = detectSchemaType(obj); - let result: ValidateResult; - let semanticResult: SemanticValidateResult | undefined; - - if (schemaType === "template-plugins") { - result = validateTemplateManifest(obj); - } else { - result = validateManifest(obj); - if (result.valid && result.manifest) { - semanticResult = runSemanticValidation(result.manifest); - } - } + const result: ValidateResult = + schemaType === "template-plugins" + ? validateTemplateManifest(obj) + : validateManifest(obj); if (result.valid) { - const hasSemanticErrors = Boolean(semanticResult?.errors.length); - const hasSemanticWarnings = Boolean(semanticResult?.warnings.length); - if (options.json) { - const entry: (typeof jsonResults)[number] = { - path: relativePath, - valid: !hasSemanticErrors, - }; - if (hasSemanticErrors) { - entry.errors = formatSemanticIssues(semanticResult!.errors) - .split("\n") - .filter(Boolean); - } - if (hasSemanticWarnings) { - entry.warnings = formatSemanticIssues(semanticResult!.warnings) - .split("\n") - .filter(Boolean); - } - jsonResults.push(entry); + jsonResults.push({ path: relativePath, valid: true }); } else { - if (hasSemanticErrors) { - console.error(`✗ ${relativePath} (semantic errors)`); - console.error(formatSemanticIssues(semanticResult!.errors)); - } else { - console.log(`✓ ${relativePath}`); - } - if (hasSemanticWarnings) { - console.warn(" warnings:"); - console.warn(formatSemanticIssues(semanticResult!.warnings)); - } - } - - if (hasSemanticErrors) { - hasFailure = true; + console.log(`✓ ${relativePath}`); } } else { if (options.json) { const errors = result.errors?.length - ? formatValidationErrors(result.errors, obj) - .split("\n") - .filter(Boolean) + ? formatValidationErrors(result.errors).split("\n").filter(Boolean) : []; jsonResults.push({ path: relativePath, @@ -191,33 +146,11 @@ async function runPluginValidate( } else { console.error(`✗ ${relativePath}`); if (result.errors?.length) { - console.error(formatValidationErrors(result.errors, obj)); + console.error(formatValidationErrors(result.errors)); } } hasFailure = true; } - - if (schemaType === "template-plugins" && result.valid) { - const templateObj = obj as { plugins?: Record }; - if (templateObj.plugins) { - for (const [pluginName, plugin] of Object.entries( - templateObj.plugins, - )) { - const pluginSemantic = runSemanticValidation( - plugin as PluginManifest, - ); - if (pluginSemantic.errors.length) { - console.error(` ✗ plugin "${pluginName}" (semantic errors)`); - console.error(formatSemanticIssues(pluginSemantic.errors)); - hasFailure = true; - } - if (pluginSemantic.warnings.length) { - console.warn(` ⚠ plugin "${pluginName}" (warnings)`); - console.warn(formatSemanticIssues(pluginSemantic.warnings)); - } - } - } - } } if (options.json) { diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts index 93feb6c5c..7af83e837 100644 --- a/packages/shared/src/schemas/manifest.ts +++ b/packages/shared/src/schemas/manifest.ts @@ -6,15 +6,16 @@ * template-plugins.schema.json) faithfully so byte-equivalence/parity can * be asserted before downstream consumers are migrated. * - * - Phase 1: this module is sealed; only schemas + inferred types are exported. - * Consumers in the CLI keep going through the AJV-based validator until - * Phase 2 wires Standard Schema in. + * - Phase 2: cross-field constraints (cycle/dangling-reference checks, + * `` placeholder, post-scaffold instruction non-empty) are now + * refinements co-located with the shape they constrain. Validation is + * driven through the Standard Schema interface from `validate-manifest.ts`. * - Phase 3: `templateFieldEntrySchema` will move `origin` from an optional - * input slot to a `.transform()` output. For Phase 1, `origin` is allowed + * input slot to a `.transform()` output. For now, `origin` is allowed * on input as an optional enum to keep parity with the existing template * schema's `resourceFieldEntry`. * - Phase 4: `discoveryDescriptorSchema` will become a discriminated union. - * For Phase 1 it remains in the existing free-form shape. + * For now it remains in the existing free-form shape. * - Phase 6: scaffolding rule items get a `maxLength`, and a volume MUST * rule lands in `TEMPLATE_SCAFFOLDING`. Neither happens here. */ @@ -101,7 +102,7 @@ export const appPermissionSchema = z .enum(["CAN_USE"]) .describe("Permission for Databricks App resources"); -// ── Discovery descriptor (Phase 1: free-form shape) ────────────────────── +// ── Discovery descriptor (free-form shape until Phase 4) ───────────────── export const discoveryDescriptorSchema = z .object({ @@ -135,6 +136,10 @@ export const discoveryDescriptorSchema = z ), }) .strict() + .refine((descriptor) => descriptor.cliCommand.includes(""), { + message: "must include placeholder", + path: ["cliCommand"], + }) .describe( "Describes how the CLI discovers values for a resource field via a Databricks CLI command.", ); @@ -224,6 +229,68 @@ const resourceRequirementBaseShape = { ), }; +/** + * Adds the cycle/dangling-reference cross-field check to a resource variant. + * Iterates the resource's `fields`, validates each `discovery.dependsOn` target + * is a sibling field name, then runs DFS over the dependsOn graph to detect + * cycles. Issue paths target either the offending field's `dependsOn` slot or + * the resource itself for cycles. + */ +function refineResourceDependsOn( + resource: { fields?: Record }, + ctx: z.core.$RefinementCtx, +): void { + if (!resource.fields) return; + const fieldNames = new Set(Object.keys(resource.fields)); + + // Pass 1: validate dependsOn references and build the dependency graph. + const deps = new Map(); + for (const [name, field] of Object.entries(resource.fields)) { + const dep = field.discovery?.dependsOn; + if (!dep) continue; + if (!fieldNames.has(dep)) { + ctx.addIssue({ + code: "custom", + path: ["fields", name, "discovery", "dependsOn"], + message: `references non-existent sibling field '${dep}'`, + }); + } + deps.set(name, dep); + } + + // Pass 2: detect cycles via DFS. Emit one issue per cycle found. + const visited = new Set(); + const visiting = new Set(); + + function dfs(node: string, chain: string[]): string[] | null { + if (visiting.has(node)) return [...chain, node]; + if (visited.has(node)) return null; + visiting.add(node); + const next = deps.get(node); + if (next) { + const cycle = dfs(next, [...chain, node]); + if (cycle) return cycle; + } + visiting.delete(node); + visited.add(node); + return null; + } + + for (const node of deps.keys()) { + if (visited.has(node)) continue; + const cycle = dfs(node, []); + if (cycle) { + ctx.addIssue({ + code: "custom", + path: [], + message: `discovery.dependsOn creates a cycle: ${cycle.join(" → ")}`, + }); + // One cycle error per resource is enough. + break; + } + } +} + function makeResourceVariant< TType extends z.ZodLiteral, TPerm extends z.ZodTypeAny, @@ -236,7 +303,8 @@ function makeResourceVariant< "Required permission level. Validated per resource type.", ), }) - .strict(); + .strict() + .superRefine(refineResourceDependsOn); } export const resourceRequirementSchema = z @@ -305,6 +373,7 @@ export const postScaffoldStepSchema = z .object({ instruction: z .string() + .min(1) .describe( "Human-readable instruction for the user to follow after scaffolding.", ), @@ -417,14 +486,14 @@ export const originSchema = z "How the field value is determined. Computed during sync, not authored by plugin developers.", ); -// ── Template field entry (Phase 1: origin still input-optional) ────────── +// ── Template field entry (origin still input-optional until Phase 3) ───── /** * Template field entry: extends the plugin manifest field entry with an * optional `origin` field. Phase 3 will move `origin` to a `.transform()` * output and remove the input slot. For parity with the existing - * hand-written template schema, Phase 1 keeps `origin` as an optional input - * (consumers in Phase 1 still emit it via `enrichFieldsWithOrigin`). + * hand-written template schema, `origin` is currently an optional input + * (consumers still emit it via `enrichFieldsWithOrigin`). */ export const templateFieldEntrySchema = resourceFieldEntrySchema.extend({ origin: originSchema.optional(), @@ -468,7 +537,8 @@ function makeTemplateResourceVariant< "Required permission level. Validated per resource type.", ), }) - .strict(); + .strict() + .superRefine(refineResourceDependsOn); } export const templateResourceRequirementSchema = z From ba21ac2542fba2c1650f50c923043761a1226b84 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 21:18:08 +0200 Subject: [PATCH 10/17] refactor: derive origin via zod transform (phase 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit templateFieldEntrySchema gains a .transform() that computes origin from localOnly/value/resolve and emits it on every parse. origin is accepted as optional input but always overwritten — drift-by-construction is now structurally impossible. The new sync.test.ts case verifies this: a field with value: "5432" and stale input origin: "user" parses to "static". enrichFieldsWithOrigin and its mutation pass delete from sync.ts. The template manifest is now built by parsing each field through templateFieldEntrySchema before serialization. Per-field parse (rather than whole-manifest parse) is chosen because Zod 4's strict-object parse reorders keys aggressively, churning resource and plugin entries; per-field parse leaves the surrounding structure in input order. Sync output is byte-identical to phase 2 (md5 verified). computeOrigin and Origin type delete from manifest-types.ts and sync.ts. The replacement, computeOriginFromField, is private to manifest.ts and invoked only by the transform — nothing else in the codebase needs origin computation now that validateDiscoveryOrigin (deleted in phase 2) is gone. generate-json-schema.ts passes io: "input" to z.toJSONSchema so the transform doesn't break schema emission. The published JSON Schema describes what plugin authors write (no origin slot), not the transformed output — exactly the right semantic for IDE intellisense and external validators. template/appkit.plugins.json regenerated by build pipeline (pre-existing drift, not introduced here). Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../src/cli/commands/plugin/manifest-types.ts | 22 ++----- .../src/cli/commands/plugin/sync/sync.test.ts | 48 +++++++++----- .../src/cli/commands/plugin/sync/sync.ts | 63 +++++++++---------- packages/shared/src/schemas/manifest.ts | 54 ++++++++++++---- template/appkit.plugins.json | 12 ++-- tools/generate-json-schema.ts | 11 +++- 6 files changed, 122 insertions(+), 88 deletions(-) diff --git a/packages/shared/src/cli/commands/plugin/manifest-types.ts b/packages/shared/src/cli/commands/plugin/manifest-types.ts index 25cd03b59..af7f05d68 100644 --- a/packages/shared/src/cli/commands/plugin/manifest-types.ts +++ b/packages/shared/src/cli/commands/plugin/manifest-types.ts @@ -3,6 +3,11 @@ * Base types (ResourceFieldEntry, ResourceRequirement, PluginManifest) are * generated from plugin-manifest.schema.json — only CLI-specific extensions * (TemplatePlugin, TemplatePluginsManifest) are hand-written here. + * + * Origin computation moved to `schemas/manifest.ts` in Phase 3 — origin is + * now a `.transform()` output of `templateFieldEntrySchema`, not a helper + * called by sync. Phase 5 will fold these hand-written types into the Zod + * module and delete the legacy generated types. */ export type { @@ -16,7 +21,6 @@ export type { import type { PluginManifest, PostScaffoldStep, - ResourceFieldEntry, } from "../../../schemas/plugin-manifest.generated"; export interface ScaffoldingFlag { @@ -37,22 +41,6 @@ export interface ScaffoldingDescriptor { rules?: ScaffoldingRules; } -export type Origin = "user" | "platform" | "static" | "cli"; - -/** - * Derives the origin of a resource field value based on its properties. - * - localOnly: true → "platform" (auto-injected by Databricks Apps platform) - * - value present → "static" (hardcoded value) - * - resolve present → "cli" (resolved by CLI during init) - * - else → "user" (user must provide the value) - */ -export function computeOrigin(field: ResourceFieldEntry): Origin { - if (field.localOnly) return "platform"; - if (field.value !== undefined) return "static"; - if (field.resolve !== undefined) return "cli"; - return "user"; -} - export interface TemplatePlugin extends Omit { package: string; /** When true, this plugin is required by the template and cannot be deselected during CLI init. */ diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.test.ts b/packages/shared/src/cli/commands/plugin/sync/sync.test.ts index af3805363..e331e27f3 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.test.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.test.ts @@ -1,8 +1,8 @@ import path from "node:path"; import { Lang, parse } from "@ast-grep/napi"; import { describe, expect, it } from "vitest"; +import { templateFieldEntrySchema } from "../../../../schemas/manifest"; import { - computeOrigin, isWithinDirectory, parseImports, parsePluginUsages, @@ -184,16 +184,20 @@ describe("plugin sync", () => { }); }); - describe("computeOrigin", () => { - it("returns 'platform' when localOnly is true", () => { - expect(computeOrigin({ env: "PGHOST", localOnly: true })).toBe( - "platform", - ); + describe("templateFieldEntrySchema origin transform", () => { + function originOf( + field: Parameters[0], + ) { + return templateFieldEntrySchema.parse(field).origin; + } + + it("emits 'platform' when localOnly is true", () => { + expect(originOf({ env: "PGHOST", localOnly: true })).toBe("platform"); }); - it("returns 'platform' when localOnly is true even with resolve", () => { + it("emits 'platform' when localOnly is true even with resolve", () => { expect( - computeOrigin({ + originOf({ env: "PGHOST", localOnly: true, resolve: "postgres:host", @@ -201,30 +205,42 @@ describe("plugin sync", () => { ).toBe("platform"); }); - it("returns 'static' when value is present", () => { - expect(computeOrigin({ env: "PGPORT", value: "5432" })).toBe("static"); + it("emits 'static' when value is present", () => { + expect(originOf({ env: "PGPORT", value: "5432" })).toBe("static"); }); - it("returns 'cli' when resolve is present", () => { + it("emits 'cli' when resolve is present", () => { expect( - computeOrigin({ + originOf({ env: "LAKEBASE_ENDPOINT", resolve: "postgres:endpointPath", }), ).toBe("cli"); }); - it("returns 'user' for fields with no special properties", () => { + it("emits 'user' for fields with no special properties", () => { expect( - computeOrigin({ + originOf({ env: "DATABRICKS_WAREHOUSE_ID", description: "Warehouse ID", }), ).toBe("user"); }); - it("returns 'user' for minimal field with only env", () => { - expect(computeOrigin({ env: "MY_VAR" })).toBe("user"); + it("emits 'user' for a minimal field with only env", () => { + expect(originOf({ env: "MY_VAR" })).toBe("user"); + }); + + it("overwrites a stale 'origin' on input — drift-by-construction", () => { + // A previously-synced template manifest may carry an origin in its + // input; the transform must still emit the canonical computed value. + expect( + originOf({ + env: "PGPORT", + value: "5432", + origin: "user", + }), + ).toBe("static"); }); }); }); diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.ts b/packages/shared/src/cli/commands/plugin/sync/sync.ts index af7a2af5d..372f7806e 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.ts @@ -2,19 +2,17 @@ import fs from "node:fs"; import path from "node:path"; import { Lang, parse, type SgNode } from "@ast-grep/napi"; import { Command } from "commander"; +import { templateFieldEntrySchema } from "../../../../schemas/manifest"; import { loadManifestFromFile, type ResolvedManifest, resolveManifestInDir, } from "../manifest-resolve"; -import { - computeOrigin, - type Origin, - type PluginManifest, - type ResourceFieldEntry, - type ScaffoldingDescriptor, - type TemplatePlugin, - type TemplatePluginsManifest, +import type { + PluginManifest, + ScaffoldingDescriptor, + TemplatePlugin, + TemplatePluginsManifest, } from "../manifest-types"; import { shouldAllowJsManifestForPackage } from "../trusted-js-manifest"; import { @@ -40,29 +38,6 @@ function isWithinDirectory(filePath: string, boundary: string): boolean { ); } -/** - * Injects computed `origin` onto every resource field in all plugins. - * Mutates the plugins object in place for efficiency. - */ -function enrichFieldsWithOrigin( - plugins: TemplatePluginsManifest["plugins"], -): void { - for (const plugin of Object.values(plugins)) { - for (const group of [ - plugin.resources.required, - plugin.resources.optional, - ]) { - for (const resource of group) { - if (!resource.fields) continue; - for (const field of Object.values(resource.fields)) { - (field as ResourceFieldEntry & { origin?: Origin }).origin = - computeOrigin(field); - } - } - } - } -} - /** * Validates a parsed JSON object against the plugin-manifest schema. * Returns the manifest if valid, or null and logs schema errors. @@ -596,14 +571,33 @@ async function scanPluginsDir( /** * Write (or preview) the template plugins manifest to disk. + * + * Each resource field is parsed through `templateFieldEntrySchema` so the + * `origin` transform fires and produces canonical `origin` values, even + * when the input carries a stale `origin`. Parsing per-field (rather than + * the whole manifest) keeps the surrounding plugin/resource key order + * stable so the synced JSON's diff stays minimal. */ function writeManifest( outputPath: string, { plugins }: { plugins: TemplatePluginsManifest["plugins"] }, options: { write?: boolean; silent?: boolean; json?: boolean }, ) { - // Enrich fields with computed origin for v2.0 - enrichFieldsWithOrigin(plugins); + for (const plugin of Object.values(plugins)) { + for (const group of [ + plugin.resources.required, + plugin.resources.optional, + ]) { + for (const resource of group) { + if (!resource.fields) continue; + for (const fieldName of Object.keys(resource.fields)) { + resource.fields[fieldName] = templateFieldEntrySchema.parse( + resource.fields[fieldName], + ); + } + } + } + } const templateManifest: TemplatePluginsManifest = { $schema: @@ -897,9 +891,8 @@ async function runPluginsSync(options: { writeManifest(outputPath, { plugins }, options); } -/** Exported for testing: path boundary check, AST parsing, trust checks, origin computation. */ +/** Exported for testing: path boundary check, AST parsing, trust checks. */ export { - computeOrigin, isWithinDirectory, parseImports, parsePluginUsages, diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts index 7af83e837..2880b0a3e 100644 --- a/packages/shared/src/schemas/manifest.ts +++ b/packages/shared/src/schemas/manifest.ts @@ -7,13 +7,13 @@ * be asserted before downstream consumers are migrated. * * - Phase 2: cross-field constraints (cycle/dangling-reference checks, - * `` placeholder, post-scaffold instruction non-empty) are now + * `` placeholder, post-scaffold instruction non-empty) are * refinements co-located with the shape they constrain. Validation is * driven through the Standard Schema interface from `validate-manifest.ts`. - * - Phase 3: `templateFieldEntrySchema` will move `origin` from an optional - * input slot to a `.transform()` output. For now, `origin` is allowed - * on input as an optional enum to keep parity with the existing template - * schema's `resourceFieldEntry`. + * - Phase 3: `templateFieldEntrySchema` is a transform that emits `origin` + * from `localOnly`/`value`/`resolve`. The input slot is still allowed so + * re-parsing previously-synced template manifests does not fail, but the + * transform always overwrites it — drift-by-construction for hand-edits. * - Phase 4: `discoveryDescriptorSchema` will become a discriminated union. * For now it remains in the existing free-form shape. * - Phase 6: scaffolding rule items get a `maxLength`, and a volume MUST @@ -486,18 +486,46 @@ export const originSchema = z "How the field value is determined. Computed during sync, not authored by plugin developers.", ); -// ── Template field entry (origin still input-optional until Phase 3) ───── +// ── Template field entry (origin computed by transform) ───────────────── + +/** + * Derives the canonical origin of a resource field value from its shape. + * + * - `localOnly: true` → `"platform"` (auto-injected by the Databricks Apps + * platform at deploy time; takes precedence over `value`/`resolve`). + * - `value !== undefined` → `"static"` (hardcoded value). + * - `resolve !== undefined` → `"cli"` (resolved by the CLI during init). + * - else → `"user"` (user must provide the value at init time). + * + * Co-located with `templateFieldEntrySchema` because the transform is the + * only consumer. Kept private so any other "origin computation" goes + * through the schema rather than re-implementing the rules. + */ +function computeOriginFromField(field: { + localOnly?: boolean; + value?: string; + resolve?: string; +}): z.infer { + if (field.localOnly) return "platform"; + if (field.value !== undefined) return "static"; + if (field.resolve !== undefined) return "cli"; + return "user"; +} /** * Template field entry: extends the plugin manifest field entry with an - * optional `origin` field. Phase 3 will move `origin` to a `.transform()` - * output and remove the input slot. For parity with the existing - * hand-written template schema, `origin` is currently an optional input - * (consumers still emit it via `enrichFieldsWithOrigin`). + * optional `origin` input slot, then runs a `.transform()` that overwrites + * `origin` with the computed value. Allowing `origin` on input means + * re-parsing a previously-synced template manifest does not fail; emitting + * `origin` always means hand-edits in synced JSON are silently corrected + * on the next parse — drift-by-construction. */ -export const templateFieldEntrySchema = resourceFieldEntrySchema.extend({ - origin: originSchema.optional(), -}); +export const templateFieldEntrySchema = resourceFieldEntrySchema + .extend({ origin: originSchema.optional() }) + .transform((field) => ({ + ...field, + origin: computeOriginFromField(field), + })); // ── Template resource requirement (uses templateFieldEntrySchema) ──────── diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index ab297baf2..0672e376b 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -188,40 +188,40 @@ }, "host": { "env": "PGHOST", + "description": "Postgres host for local development. Auto-injected by the platform at deploy time.", "localOnly": true, "resolve": "postgres:host", - "description": "Postgres host for local development. Auto-injected by the platform at deploy time.", "origin": "platform" }, "databaseName": { "env": "PGDATABASE", + "description": "Postgres database name for local development. Auto-injected by the platform at deploy time.", "localOnly": true, "resolve": "postgres:databaseName", - "description": "Postgres database name for local development. Auto-injected by the platform at deploy time.", "origin": "platform" }, "endpointPath": { "env": "LAKEBASE_ENDPOINT", - "bundleIgnore": true, - "resolve": "postgres:endpointPath", "description": "Lakebase endpoint resource name. Auto-injected at runtime via app.yaml valueFrom: postgres. For local development, obtain by running `databricks postgres list-endpoints {branch-name}`, select the desired item from the output array and use its .name value.", + "bundleIgnore": true, "examples": [ "projects/{project-id}/branches/{branch-id}/endpoints/{endpoint-id}" ], + "resolve": "postgres:endpointPath", "origin": "cli" }, "port": { "env": "PGPORT", + "description": "Postgres port. Auto-injected by the platform at deploy time.", "localOnly": true, "value": "5432", - "description": "Postgres port. Auto-injected by the platform at deploy time.", "origin": "platform" }, "sslmode": { "env": "PGSSLMODE", + "description": "Postgres SSL mode. Auto-injected by the platform at deploy time.", "localOnly": true, "value": "require", - "description": "Postgres SSL mode. Auto-injected by the platform at deploy time.", "origin": "platform" } } diff --git a/tools/generate-json-schema.ts b/tools/generate-json-schema.ts index 842e4c4d4..c2db2667b 100644 --- a/tools/generate-json-schema.ts +++ b/tools/generate-json-schema.ts @@ -46,7 +46,16 @@ function emit( ): Record { // Targeting draft-07 keeps parity with the existing hand-written schemas // (which are draft-07). The default in Zod 4 is draft-2020-12. - const generated = z.toJSONSchema(schema, { target: "draft-07" }); + // + // `io: "input"` makes Zod emit the pre-transform shape, so transforms + // (like the `origin`-emitter on `templateFieldEntrySchema`) produce + // valid JSON Schema instead of throwing "Transforms cannot be + // represented in JSON Schema". For schemas without transforms, this is + // equivalent to the default ("output"). + const generated = z.toJSONSchema(schema, { + target: "draft-07", + io: "input", + }); // Inject $id and title at the top level so editor tooling can identify // the schema by URL even when fetched out-of-band. return { From afe0a4949a719900e752943ac159ba9cf3ea1194 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 21:33:53 +0200 Subject: [PATCH 11/17] feat: reshape discovery as discriminated union (phase 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the free-form discoveryDescriptorSchema with a discriminated union on `type`: - kind variant: { type: "kind", resourceKind, select?, display?, dependsOn?, shortcut? } resourceKind enum is the closed set of databricks resources AppKit owns command templates for: warehouse, genie_space, postgres_branch, postgres_database, volume. - cli variant: { type: "cli", cliCommand, selectField, displayField?, dependsOn?, shortcut? } preserves the existing free-form shape as an escape hatch for bespoke resources not yet in the kind enum. The placeholder .refine() moves from the top-level descriptor to the cli variant only — kind has no cliCommand to validate. The cycle and dangling-reference DFS on resourceRequirementSchema reads field.discovery?.dependsOn generically and continues to work across both variants. A typed RESOURCE_KIND_COMMANDS map ships next to the schema. Each entry declares the CLI command template (with placeholder + optional {} placeholders for dependsOn substitution) and an optional unwrap path for wrapped responses. Volume's catalog/schema parent context is documented in a code comment as a phase 6 MUST-rule concern, not a schema construct. The four core plugin manifests migrate to the kind variant: - analytics: { type: "kind", resourceKind: "warehouse" } - genie: { type: "kind", resourceKind: "genie_space" } - lakebase: branch → postgres_branch, database → postgres_database (dependsOn: "branch") - files: { type: "kind", resourceKind: "volume", select: "full_name" } Lakebase carries select: "name" (non-default for postgres_branch and postgres_database); files carries select: "full_name". Defaults are kind- specific identifiers and live in the command map / runner (out of scope). The Zod-derived ResourceRequirement is a discriminated union (per-type permission tightness baked in). Two consumer interface declarations in packages/shared/src/plugin.ts and packages/appkit/src/registry/types.ts previously did `interface ResourceRequirement extends GeneratedResourceRequirement` — TS interfaces cannot extend union types. Both flatten to structural interfaces with `permission: string`. This matches the previous consumer-facing shape (legacy generated permission was already loose string); per-variant tightness is enforced at schema parse time, where it belongs. add-resource.ts's literal entry construction casts to ResourceRequirement for the same reason. manifest-types.ts re-export source switches from the legacy plugin-manifest.generated to the canonical Zod schemas/manifest. The generated.ts file is now orphaned (no source imports it) and deletes in phase 5. The phase 1 parity test (json-schema-parity.test.ts) deletes — it asserted AJV-with-legacy-schema and Zod-with-new-schema return matching verdicts on the four core plugin manifests, but those manifests now use type: "kind" which the legacy schema doesn't understand. The test was the phase-1 transition gate; once the contract intentionally diverges it loses meaning. template/appkit.plugins.json and docs/docs/api/appkit/* re-emitted by the build pipeline. Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../api/appkit/Interface.PluginManifest.md | 37 ++- .../api/appkit/Interface.ResourceEntry.md | 10 +- .../appkit/Interface.ResourceFieldEntry.md | 84 ------ .../appkit/Interface.ResourceRequirement.md | 66 +---- .../appkit/TypeAlias.ResourceFieldEntry.md | 5 + docs/docs/api/appkit/index.md | 4 +- docs/docs/api/appkit/typedoc-sidebar.ts | 10 +- .../src/plugins/analytics/manifest.json | 5 +- .../appkit/src/plugins/files/manifest.json | 6 +- .../appkit/src/plugins/genie/manifest.json | 5 +- .../appkit/src/plugins/lakebase/manifest.json | 12 +- packages/appkit/src/registry/types.ts | 29 +- .../plugin/add-resource/add-resource.ts | 17 +- .../src/cli/commands/plugin/manifest-types.ts | 19 +- .../plugin/validate/validate-manifest.test.ts | 247 +++++++++++++++++- packages/shared/src/plugin.ts | 23 +- .../__tests__/json-schema-parity.test.ts | 242 ----------------- packages/shared/src/schemas/manifest.ts | 164 +++++++++++- template/appkit.plugins.json | 28 +- 19 files changed, 536 insertions(+), 477 deletions(-) delete mode 100644 docs/docs/api/appkit/Interface.ResourceFieldEntry.md create mode 100644 docs/docs/api/appkit/TypeAlias.ResourceFieldEntry.md delete mode 100644 packages/shared/src/schemas/__tests__/json-schema-parity.test.ts diff --git a/docs/docs/api/appkit/Interface.PluginManifest.md b/docs/docs/api/appkit/Interface.PluginManifest.md index 62cf055ea..1747c8e62 100644 --- a/docs/docs/api/appkit/Interface.PluginManifest.md +++ b/docs/docs/api/appkit/Interface.PluginManifest.md @@ -27,8 +27,6 @@ Extends the shared PluginManifest with strict resource types. optional author: string; ``` -Author name or organization - #### Inherited from ```ts @@ -62,8 +60,6 @@ schema: JSONSchema7; description: string; ``` -Brief description of what the plugin does - #### Inherited from ```ts @@ -78,8 +74,6 @@ Omit.description displayName: string; ``` -Human-readable display name for UI and CLI - #### Inherited from ```ts @@ -94,8 +88,6 @@ Omit.displayName optional hidden: boolean; ``` -When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync. - #### Inherited from ```ts @@ -110,8 +102,6 @@ Omit.hidden optional keywords: string[]; ``` -Keywords for plugin discovery - #### Inherited from ```ts @@ -126,8 +116,6 @@ Omit.keywords optional license: string; ``` -SPDX license identifier - #### Inherited from ```ts @@ -158,8 +146,6 @@ Omit.name optional onSetupMessage: string; ``` -Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning). - #### Inherited from ```ts @@ -171,10 +157,23 @@ Omit.onSetupMessage ### postScaffold? ```ts -optional postScaffold: PostScaffoldStep[]; +optional postScaffold: { + instruction: string; + required?: boolean; +}[]; ``` -Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order. +#### instruction + +```ts +instruction: string; +``` + +#### required? + +```ts +optional required: boolean; +``` #### Inherited from @@ -190,8 +189,6 @@ Omit.postScaffold optional repository: string; ``` -URL to the plugin's source repository - #### Inherited from ```ts @@ -235,8 +232,6 @@ Resources that must be available for the plugin to function optional stability: "beta" | "ga"; ``` -Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly. - #### Inherited from ```ts @@ -251,8 +246,6 @@ Omit.stability optional version: string; ``` -Plugin version (semver format) - #### Inherited from ```ts diff --git a/docs/docs/api/appkit/Interface.ResourceEntry.md b/docs/docs/api/appkit/Interface.ResourceEntry.md index f9e20e921..797c64da8 100644 --- a/docs/docs/api/appkit/Interface.ResourceEntry.md +++ b/docs/docs/api/appkit/Interface.ResourceEntry.md @@ -15,7 +15,7 @@ Extends ResourceRequirement with resolution state and plugin ownership. alias: string; ``` -Human-readable label for UI/display only. Deduplication uses resourceKey, not alias. +Human-readable label for UI/display only. #### Inherited from @@ -29,7 +29,7 @@ Human-readable label for UI/display only. Deduplication uses resourceKey, not al description: string; ``` -Human-readable description of why this resource is needed +Human-readable description of why this resource is needed. #### Inherited from @@ -43,7 +43,7 @@ Human-readable description of why this resource is needed fields: Record; ``` -Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key). +Map of field name to field entry. Required at runtime. #### Inherited from @@ -93,6 +93,8 @@ Plugin(s) that require this resource (comma-separated if multiple) required: boolean; ``` +Whether the resource is mandatory at runtime. + #### Inherited from [`ResourceRequirement`](Interface.ResourceRequirement.md).[`required`](Interface.ResourceRequirement.md#required) @@ -115,7 +117,7 @@ Whether the resource has been resolved (all field env vars set) resourceKey: string; ``` -Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup. +Stable key for machine use: deduplication, env naming, composite keys, app.yaml. #### Inherited from diff --git a/docs/docs/api/appkit/Interface.ResourceFieldEntry.md b/docs/docs/api/appkit/Interface.ResourceFieldEntry.md deleted file mode 100644 index 8eaaf431f..000000000 --- a/docs/docs/api/appkit/Interface.ResourceFieldEntry.md +++ /dev/null @@ -1,84 +0,0 @@ -# Interface: ResourceFieldEntry - -Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key). - -This interface was referenced by `PluginManifest`'s JSON-Schema -via the `definition` "resourceFieldEntry". - -## Properties - -### bundleIgnore? - -```ts -optional bundleIgnore: boolean; -``` - -When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation. - -*** - -### description? - -```ts -optional description: string; -``` - -Human-readable description for this field - -*** - -### discovery? - -```ts -optional discovery: DiscoveryDescriptor; -``` - -*** - -### env? - -```ts -optional env: string; -``` - -Environment variable name for this field - -*** - -### examples? - -```ts -optional examples: string[]; -``` - -Example values showing the expected format for this field - -*** - -### localOnly? - -```ts -optional localOnly: boolean; -``` - -When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time. - -*** - -### resolve? - -```ts -optional resolve: string; -``` - -Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow. - -*** - -### value? - -```ts -optional value: string; -``` - -Static value for this field. Used when no prompted or resolved value exists. diff --git a/docs/docs/api/appkit/Interface.ResourceRequirement.md b/docs/docs/api/appkit/Interface.ResourceRequirement.md index b2112040b..c188e2d57 100644 --- a/docs/docs/api/appkit/Interface.ResourceRequirement.md +++ b/docs/docs/api/appkit/Interface.ResourceRequirement.md @@ -2,16 +2,14 @@ Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements(). -Narrows the generated base: type → ResourceType enum, permission → ResourcePermission union. -## See - - - `packages/shared/src/schemas/plugin-manifest.generated.ts` `ResourceRequirement` — generated base - - SharedResourceRequirement — shared re-export with runtime `fields` and `required` - -## Extends - -- `ResourceRequirement` +Defined as a flat structural shape rather than narrowing the shared +discriminated-union `ResourceRequirement`. The registry needs to construct +and pass these values around at runtime where `type` and `permission` are +looked up dynamically from a string; preserving per-variant tightness here +would force every constructor to thread variant types end-to-end. The +runtime guarantee (permission valid for type) is enforced by the manifest +schema's parse, not by the registry's static types. ## Extended by @@ -25,13 +23,7 @@ Narrows the generated base: type → ResourceType enum, permission → ResourceP alias: string; ``` -Human-readable label for UI/display only. Deduplication uses resourceKey, not alias. - -#### Inherited from - -```ts -SharedResourceRequirement.alias -``` +Human-readable label for UI/display only. *** @@ -41,13 +33,7 @@ SharedResourceRequirement.alias description: string; ``` -Human-readable description of why this resource is needed - -#### Inherited from - -```ts -SharedResourceRequirement.description -``` +Human-readable description of why this resource is needed. *** @@ -57,13 +43,7 @@ SharedResourceRequirement.description fields: Record; ``` -Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key). - -#### Inherited from - -```ts -SharedResourceRequirement.fields -``` +Map of field name to field entry. Required at runtime. *** @@ -75,12 +55,6 @@ permission: ResourcePermission; Required permission level for the resource (narrowed to union) -#### Overrides - -```ts -SharedResourceRequirement.permission -``` - *** ### required @@ -89,11 +63,7 @@ SharedResourceRequirement.permission required: boolean; ``` -#### Inherited from - -```ts -SharedResourceRequirement.required -``` +Whether the resource is mandatory at runtime. *** @@ -103,13 +73,7 @@ SharedResourceRequirement.required resourceKey: string; ``` -Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup. - -#### Inherited from - -```ts -SharedResourceRequirement.resourceKey -``` +Stable key for machine use: deduplication, env naming, composite keys, app.yaml. *** @@ -120,9 +84,3 @@ type: ResourceType; ``` Type of Databricks resource required (narrowed to enum) - -#### Overrides - -```ts -SharedResourceRequirement.type -``` diff --git a/docs/docs/api/appkit/TypeAlias.ResourceFieldEntry.md b/docs/docs/api/appkit/TypeAlias.ResourceFieldEntry.md new file mode 100644 index 000000000..d1d7c9920 --- /dev/null +++ b/docs/docs/api/appkit/TypeAlias.ResourceFieldEntry.md @@ -0,0 +1,5 @@ +# Type Alias: ResourceFieldEntry + +```ts +type ResourceFieldEntry = z.infer; +``` diff --git a/docs/docs/api/appkit/index.md b/docs/docs/api/appkit/index.md index 5a21e935f..104f0bfdd 100644 --- a/docs/docs/api/appkit/index.md +++ b/docs/docs/api/appkit/index.md @@ -48,8 +48,7 @@ plugin architecture, and React integration. | [RequestedClaims](Interface.RequestedClaims.md) | Optional claims for fine-grained Unity Catalog table permissions When specified, the returned token will be scoped to only the requested tables | | [RequestedResource](Interface.RequestedResource.md) | Resource to request permissions for in Unity Catalog | | [ResourceEntry](Interface.ResourceEntry.md) | Internal representation of a resource in the registry. Extends ResourceRequirement with resolution state and plugin ownership. | -| [ResourceFieldEntry](Interface.ResourceFieldEntry.md) | Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key). | -| [ResourceRequirement](Interface.ResourceRequirement.md) | Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements(). Narrows the generated base: type → ResourceType enum, permission → ResourcePermission union. | +| [ResourceRequirement](Interface.ResourceRequirement.md) | Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements(). | | [ServingEndpointEntry](Interface.ServingEndpointEntry.md) | Shape of a single registry entry. | | [ServingEndpointRegistry](Interface.ServingEndpointRegistry.md) | Registry interface for serving endpoint type generation. Empty by default — augmented by the Vite type generator's `.d.ts` output via module augmentation. When populated, provides autocomplete for alias names and typed request/response/chunk per endpoint. | | [StreamExecutionSettings](Interface.StreamExecutionSettings.md) | Execution settings for streaming endpoints. Extends PluginExecutionSettings with SSE stream configuration. | @@ -68,6 +67,7 @@ plugin architecture, and React integration. | [JobHandle](TypeAlias.JobHandle.md) | Job handle returned by `appkit.jobs("etl")`. Supports OBO access via `.asUser(req)`. | | [JobsExport](TypeAlias.JobsExport.md) | Public API shape of the jobs plugin. Callable to select a job by key. | | [PluginData](TypeAlias.PluginData.md) | Tuple of plugin class, config, and name. Created by `toPlugin()` and passed to `createApp()`. | +| [ResourceFieldEntry](TypeAlias.ResourceFieldEntry.md) | - | | [ResourcePermission](TypeAlias.ResourcePermission.md) | Union of all possible permission levels across all resource types. | | [ServingFactory](TypeAlias.ServingFactory.md) | Factory function returned by `AppKit.serving`. | | [ToPlugin](TypeAlias.ToPlugin.md) | Factory function type returned by `toPlugin()`. Accepts optional config and returns a PluginData tuple. | diff --git a/docs/docs/api/appkit/typedoc-sidebar.ts b/docs/docs/api/appkit/typedoc-sidebar.ts index 162c3e68b..92371d735 100644 --- a/docs/docs/api/appkit/typedoc-sidebar.ts +++ b/docs/docs/api/appkit/typedoc-sidebar.ts @@ -172,11 +172,6 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.ResourceEntry", label: "ResourceEntry" }, - { - type: "doc", - id: "api/appkit/Interface.ResourceFieldEntry", - label: "ResourceFieldEntry" - }, { type: "doc", id: "api/appkit/Interface.ResourceRequirement", @@ -253,6 +248,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/TypeAlias.PluginData", label: "PluginData" }, + { + type: "doc", + id: "api/appkit/TypeAlias.ResourceFieldEntry", + label: "ResourceFieldEntry" + }, { type: "doc", id: "api/appkit/TypeAlias.ResourcePermission", diff --git a/packages/appkit/src/plugins/analytics/manifest.json b/packages/appkit/src/plugins/analytics/manifest.json index ba717683e..de4858069 100644 --- a/packages/appkit/src/plugins/analytics/manifest.json +++ b/packages/appkit/src/plugins/analytics/manifest.json @@ -16,9 +16,8 @@ "env": "DATABRICKS_WAREHOUSE_ID", "description": "SQL Warehouse ID", "discovery": { - "cliCommand": "databricks warehouses list --profile --output json", - "selectField": ".id", - "displayField": ".name" + "type": "kind", + "resourceKind": "warehouse" } } } diff --git a/packages/appkit/src/plugins/files/manifest.json b/packages/appkit/src/plugins/files/manifest.json index 6442c6795..79d1bdf5b 100644 --- a/packages/appkit/src/plugins/files/manifest.json +++ b/packages/appkit/src/plugins/files/manifest.json @@ -16,9 +16,9 @@ "env": "DATABRICKS_VOLUME_FILES", "description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)", "discovery": { - "cliCommand": "databricks volumes list . --profile --output json", - "selectField": ".full_name", - "displayField": ".name" + "type": "kind", + "resourceKind": "volume", + "select": "full_name" } } } diff --git a/packages/appkit/src/plugins/genie/manifest.json b/packages/appkit/src/plugins/genie/manifest.json index 34ae97e5f..06e4bce70 100644 --- a/packages/appkit/src/plugins/genie/manifest.json +++ b/packages/appkit/src/plugins/genie/manifest.json @@ -16,9 +16,8 @@ "env": "DATABRICKS_GENIE_SPACE_ID", "description": "Default Genie Space ID", "discovery": { - "cliCommand": "databricks genie list --profile --output json", - "selectField": ".id", - "displayField": ".name" + "type": "kind", + "resourceKind": "genie_space" } } } diff --git a/packages/appkit/src/plugins/lakebase/manifest.json b/packages/appkit/src/plugins/lakebase/manifest.json index b23b1426e..dac55a474 100644 --- a/packages/appkit/src/plugins/lakebase/manifest.json +++ b/packages/appkit/src/plugins/lakebase/manifest.json @@ -17,9 +17,9 @@ "description": "Full Lakebase Postgres branch resource name. Obtain by running `databricks postgres list-branches projects/{project-id}`, select the desired item from the output array and use its .name value.", "examples": ["projects/{project-id}/branches/{branch-id}"], "discovery": { - "cliCommand": "databricks postgres list-branches --profile --output json", - "selectField": ".name", - "displayField": ".name" + "type": "kind", + "resourceKind": "postgres_branch", + "select": "name" } }, "database": { @@ -28,9 +28,9 @@ "projects/{project-id}/branches/{branch-id}/databases/{database-id}" ], "discovery": { - "cliCommand": "databricks postgres list-databases {branch} --profile --output json", - "selectField": ".name", - "displayField": ".name", + "type": "kind", + "resourceKind": "postgres_database", + "select": "name", "dependsOn": "branch" } }, diff --git a/packages/appkit/src/registry/types.ts b/packages/appkit/src/registry/types.ts index b26227df7..96c3c9664 100644 --- a/packages/appkit/src/registry/types.ts +++ b/packages/appkit/src/registry/types.ts @@ -29,33 +29,46 @@ export { type ResourcePermission, }; -// Re-export generated base type from shared (schema-derived, used directly). +// Re-export schema-derived base type from shared. export type { ResourceFieldEntry } from "shared"; // Import shared base types for strict extension below. import type { + ResourceFieldEntry, PluginManifest as SharedPluginManifest, - ResourceRequirement as SharedResourceRequirement, } from "shared"; // ============================================================================ -// Strict appkit wrappers — narrow generated base types for registry use +// Strict appkit wrappers — narrow shared base types for registry use // ============================================================================ /** * Declares a resource requirement for a plugin. * Can be defined statically in a manifest or dynamically via getResourceRequirements(). - * Narrows the generated base: type → ResourceType enum, permission → ResourcePermission union. * - * @see `packages/shared/src/schemas/plugin-manifest.generated.ts` `ResourceRequirement` — generated base - * @see {@link SharedResourceRequirement} — shared re-export with runtime `fields` and `required` + * Defined as a flat structural shape rather than narrowing the shared + * discriminated-union `ResourceRequirement`. The registry needs to construct + * and pass these values around at runtime where `type` and `permission` are + * looked up dynamically from a string; preserving per-variant tightness here + * would force every constructor to thread variant types end-to-end. The + * runtime guarantee (permission valid for type) is enforced by the manifest + * schema's parse, not by the registry's static types. */ -export interface ResourceRequirement extends SharedResourceRequirement { +export interface ResourceRequirement { /** Type of Databricks resource required (narrowed to enum) */ type: ResourceType; - /** Required permission level for the resource (narrowed to union) */ permission: ResourcePermission; + /** Human-readable label for UI/display only. */ + alias: string; + /** Stable key for machine use: deduplication, env naming, composite keys, app.yaml. */ + resourceKey: string; + /** Human-readable description of why this resource is needed. */ + description: string; + /** Map of field name to field entry. Required at runtime. */ + fields: Record; + /** Whether the resource is mandatory at runtime. */ + required: boolean; } /** diff --git a/packages/shared/src/cli/commands/plugin/add-resource/add-resource.ts b/packages/shared/src/cli/commands/plugin/add-resource/add-resource.ts index 621700987..9bd6ac553 100644 --- a/packages/shared/src/cli/commands/plugin/add-resource/add-resource.ts +++ b/packages/shared/src/cli/commands/plugin/add-resource/add-resource.ts @@ -99,8 +99,11 @@ function buildEntry( } } - const entry: ResourceRequirement = { - type: type as ResourceRequirement["type"], + // Constructed dynamically from a string `type` and string `permission`; + // the per-type permission tightness from the discriminated union is + // recovered at runtime by `validateManifest` before the entry is written. + const entry = { + type, alias, resourceKey: opts.resourceKey ?? resourceKeyFromType(type), description: @@ -109,7 +112,7 @@ function buildEntry( permission: opts.permission ?? DEFAULT_PERMISSION_BY_TYPE[type] ?? "CAN_VIEW", fields, - }; + } as ResourceRequirement; return { entry, isRequired }; } @@ -163,14 +166,16 @@ async function runInteractive(opts: AddResourceOptions): Promise { } const alias = humanizeResourceType(spec.type); - const entry: ResourceRequirement = { - type: spec.type as ResourceRequirement["type"], + // See note on the non-interactive variant in `buildEntry` — the entry is + // assembled from prompt strings and validated by Zod at write time. + const entry = { + type: spec.type, alias, resourceKey: spec.resourceKey, description: spec.description || `Required for ${alias} functionality.`, permission: spec.permission, fields: spec.fields, - }; + } as ResourceRequirement; if (spec.required) { manifest.resources.required.push(entry); diff --git a/packages/shared/src/cli/commands/plugin/manifest-types.ts b/packages/shared/src/cli/commands/plugin/manifest-types.ts index af7f05d68..95121864e 100644 --- a/packages/shared/src/cli/commands/plugin/manifest-types.ts +++ b/packages/shared/src/cli/commands/plugin/manifest-types.ts @@ -1,13 +1,14 @@ /** * Shared types for plugin manifests used across CLI commands. - * Base types (ResourceFieldEntry, ResourceRequirement, PluginManifest) are - * generated from plugin-manifest.schema.json — only CLI-specific extensions - * (TemplatePlugin, TemplatePluginsManifest) are hand-written here. * - * Origin computation moved to `schemas/manifest.ts` in Phase 3 — origin is - * now a `.transform()` output of `templateFieldEntrySchema`, not a helper - * called by sync. Phase 5 will fold these hand-written types into the Zod - * module and delete the legacy generated types. + * Phase 4 update: `DiscoveryDescriptor`, `ResourceFieldEntry`, + * `ResourceRequirement`, `PluginManifest`, and `PostScaffoldStep` are now + * sourced from the Zod schema module (the canonical contract). The legacy + * `plugin-manifest.generated.ts` types are stale because the discovery + * contract reshaped to a discriminated union; re-exporting from there would + * drop the union and break downstream type checks. CLI-specific extensions + * (`TemplatePlugin`, `TemplatePluginsManifest`) are still hand-written here + * — Phase 5 will fold those into the Zod module and delete this shim. */ export type { @@ -16,12 +17,12 @@ export type { PostScaffoldStep, ResourceFieldEntry, ResourceRequirement, -} from "../../../schemas/plugin-manifest.generated"; +} from "../../../schemas/manifest"; import type { PluginManifest, PostScaffoldStep, -} from "../../../schemas/plugin-manifest.generated"; +} from "../../../schemas/manifest"; export interface ScaffoldingFlag { description: string; diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts index 86d2e0034..78026df76 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts @@ -379,6 +379,7 @@ describe("validate-manifest", () => { env: "BRANCH", description: "Branch name", discovery: { + type: "cli", cliCommand: "databricks postgres list-branches --profile ", selectField: ".name", @@ -413,6 +414,7 @@ describe("validate-manifest", () => { a: { env: "A", discovery: { + type: "cli", cliCommand: "databricks cmd --profile ", selectField: ".id", dependsOn: "b", @@ -421,6 +423,7 @@ describe("validate-manifest", () => { b: { env: "B", discovery: { + type: "cli", cliCommand: "databricks cmd --profile ", selectField: ".id", dependsOn: "a", @@ -442,7 +445,49 @@ describe("validate-manifest", () => { expect(cycleErrors[0].path).toContain("resources.required[0]"); }); - it("detects missing in cliCommand", () => { + it("detects cyclic dependsOn chain across kind variants", () => { + const manifest = { + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "postgres", + alias: "Postgres", + resourceKey: "postgres", + description: "test", + permission: "CAN_CONNECT_AND_CREATE", + fields: { + branch: { + env: "BRANCH", + discovery: { + type: "kind", + resourceKind: "postgres_branch", + dependsOn: "database", + }, + }, + database: { + env: "DATABASE", + discovery: { + type: "kind", + resourceKind: "postgres_database", + dependsOn: "branch", + }, + }, + }, + }, + ], + optional: [], + }, + }; + const result = validateManifest(manifest); + expect(result.valid).toBe(false); + const cycleErrors = (result.errors ?? []).filter((e) => + e.message.includes("cycle"), + ); + expect(cycleErrors.length).toBeGreaterThan(0); + }); + + it("detects missing in cli variant cliCommand", () => { const manifest = { ...VALID_MANIFEST, resources: { @@ -457,6 +502,7 @@ describe("validate-manifest", () => { id: { env: "WAREHOUSE_ID", discovery: { + type: "cli", cliCommand: "databricks warehouses list --output json", selectField: ".id", }, @@ -504,6 +550,7 @@ describe("validate-manifest", () => { env: "WAREHOUSE_ID", description: "Warehouse ID", discovery: { + type: "cli", cliCommand: "databricks warehouses list --profile --output json", selectField: ".id", @@ -520,4 +567,202 @@ describe("validate-manifest", () => { expect(result.valid).toBe(true); }); }); + + describe("discovery descriptor (discriminated union)", () => { + function buildManifestWithDiscovery( + type: string, + permission: string, + discovery: unknown, + ): unknown { + return { + ...VALID_MANIFEST, + resources: { + required: [ + { + type, + alias: "Test Resource", + resourceKey: "test", + description: "test", + permission, + fields: { + id: { + env: "TEST_ID", + discovery, + }, + }, + }, + ], + optional: [], + }, + }; + } + + it("accepts a manifest with no discovery descriptor (v1.0 fixture)", () => { + // No `discovery` field on the resource field — minimal v1.0-shaped manifest. + const result = validateManifest({ + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "sql_warehouse", + alias: "SQL Warehouse", + resourceKey: "sql-warehouse", + description: "Required for queries", + permission: "CAN_USE", + fields: { + id: { + env: "DATABRICKS_WAREHOUSE_ID", + description: "SQL Warehouse ID", + }, + }, + }, + ], + optional: [], + }, + }); + expect(result.valid).toBe(true); + }); + + it("accepts kind variant for warehouse", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + type: "kind", + resourceKind: "warehouse", + }), + ); + expect(result.valid).toBe(true); + }); + + it("accepts kind variant for genie_space", () => { + const result = validateManifest( + buildManifestWithDiscovery("genie_space", "CAN_RUN", { + type: "kind", + resourceKind: "genie_space", + }), + ); + expect(result.valid).toBe(true); + }); + + it("accepts kind variant for postgres_branch", () => { + const result = validateManifest( + buildManifestWithDiscovery("postgres", "CAN_CONNECT_AND_CREATE", { + type: "kind", + resourceKind: "postgres_branch", + }), + ); + expect(result.valid).toBe(true); + }); + + it("accepts kind variant for postgres_database with dependsOn", () => { + const manifest = { + ...VALID_MANIFEST, + resources: { + required: [ + { + type: "postgres", + alias: "Postgres", + resourceKey: "postgres", + description: "test", + permission: "CAN_CONNECT_AND_CREATE", + fields: { + branch: { + env: "BRANCH", + discovery: { + type: "kind", + resourceKind: "postgres_branch", + }, + }, + database: { + env: "DATABASE", + discovery: { + type: "kind", + resourceKind: "postgres_database", + dependsOn: "branch", + }, + }, + }, + }, + ], + optional: [], + }, + }; + const result = validateManifest(manifest); + expect(result.valid).toBe(true); + }); + + it("accepts kind variant for volume with custom select", () => { + const result = validateManifest( + buildManifestWithDiscovery("volume", "READ_VOLUME", { + type: "kind", + resourceKind: "volume", + select: "full_name", + }), + ); + expect(result.valid).toBe(true); + }); + + it("rejects kind variant with unrecognized resourceKind", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + type: "kind", + resourceKind: "unknown_kind", + }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("resourceKind"); + }); + + it("accepts cli variant with ", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + type: "cli", + cliCommand: + "databricks warehouses list --profile --output json", + selectField: ".id", + displayField: ".name", + }), + ); + expect(result.valid).toBe(true); + }); + + it("rejects cli variant missing cliCommand", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + type: "cli", + selectField: ".id", + }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("cliCommand"); + }); + + it("rejects cli variant missing selectField", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + type: "cli", + cliCommand: + "databricks warehouses list --profile --output json", + }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("selectField"); + }); + + it("rejects discovery descriptor missing type discriminator", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + cliCommand: + "databricks warehouses list --profile --output json", + selectField: ".id", + }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + // Either a missing-discriminator error, or a "type" path issue. + expect(formatted).toMatch(/type|discriminator/i); + }); + }); }); diff --git a/packages/shared/src/plugin.ts b/packages/shared/src/plugin.ts index 0ac0375d4..00ba3e217 100644 --- a/packages/shared/src/plugin.ts +++ b/packages/shared/src/plugin.ts @@ -3,12 +3,14 @@ import type { JSONSchema7 } from "json-schema"; import type { DiscoveryDescriptor, PluginManifest as GeneratedPluginManifest, - ResourceRequirement as GeneratedResourceRequirement, PostScaffoldStep, ResourceFieldEntry, -} from "./schemas/plugin-manifest.generated"; +} from "./schemas/manifest"; -// Re-export generated types as the shared canonical definitions. +// Re-export the canonical schema-derived types as the shared definitions. +// Phase 4: sourced from `./schemas/manifest` (the Zod canonical) instead of +// the legacy generated file, which has a stale `DiscoveryDescriptor` shape +// (free-form vs. discriminated union after the Phase 4 reshape). export type { ResourceFieldEntry, DiscoveryDescriptor, PostScaffoldStep }; /** Base plugin interface. */ @@ -121,10 +123,21 @@ export interface PluginManifest * - `fields` is made required (schema has it optional, but registry always populates it) * - `required` boolean tracks whether the resource is mandatory at runtime * - * @see {@link GeneratedResourceRequirement} — generated base from plugin-manifest.schema.json + * Defined as a flat structural shape rather than narrowing the schema-derived + * discriminated union. Per-type permission tightness is enforced by the + * Zod parse at validation time; expressing it in the consumer-facing + * `ResourceRequirement` would force every plugin to thread the variant type + * through `getResourceRequirements()` for no runtime benefit. + * + * @see {@link GeneratedResourceRequirement} — schema-derived discriminated union from `schemas/manifest` * @see `packages/appkit/src/registry/types.ts` `ResourceRequirement` — strict appkit narrowing (enum types) */ -export interface ResourceRequirement extends GeneratedResourceRequirement { +export interface ResourceRequirement { + type: string; + alias: string; + resourceKey: string; + description: string; + permission: string; fields: Record; required: boolean; } diff --git a/packages/shared/src/schemas/__tests__/json-schema-parity.test.ts b/packages/shared/src/schemas/__tests__/json-schema-parity.test.ts deleted file mode 100644 index ac7cf903d..000000000 --- a/packages/shared/src/schemas/__tests__/json-schema-parity.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -/** - * Parity gate for the Zod-authored manifest schema. - * - * Strategy: fixture equivalence (Strategy B). - * - * Rationale: byte-equivalence (Strategy A) is not achievable. Zod 4's - * `toJSONSchema` represents per-`type` permission constraints via - * `oneOf` over discriminated-union variants, while the hand-written JSON - * Schema uses an `allOf + if/then` block over a base resourceRequirement - * definition with a `$defs`-resolved permission. Both shapes are valid - * JSON Schema draft-07, and AJV accepts both, but they are not byte - * identical and cannot be made so without substantial post-processing - * that would not survive future schema changes. - * - * Documented diffs observed between the legacy hand-written JSON Schema - * and the Zod-generated output: - * - * - The legacy schema uses `$defs/resourceRequirement` + `allOf+if/then` - * to constrain `permission` per `type`. The Zod output inlines a - * `oneOf` of variants discriminated on `type`. - * - Legacy `examples` arrays at the property level are not emitted by - * Zod's `toJSONSchema`. Examples in `.describe()` text are preserved. - * - Legacy schemas use `$defs` cross-references (`$ref` to shared - * permission enums); Zod inlines `enum` arrays at each call site. - * - Legacy `fields` uses `minProperties: 1`; Zod `refine` cannot be - * represented in JSON Schema, so this constraint exists only at - * parse time. - * - Legacy `version` enum on the template uses array order - * ["1.0", "1.1", "2.0"]; Zod preserves source order. - * - * The test below asserts that for every fixture, the AJV-with-legacy-schema - * verdict matches the Zod-with-new-schema verdict. That is the contract - * the parity gate guards: equivalent acceptance/rejection on real and - * synthetic manifests, not byte identity of the schema files. - */ - -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import Ajv from "ajv"; -import addFormats from "ajv-formats"; -import { describe, expect, it } from "vitest"; -import { - pluginManifestSchema, - templatePluginsManifestSchema, -} from "../manifest"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const REPO_ROOT = path.resolve(__dirname, "../../../../.."); -const PLUGIN_SCHEMA_PATH = path.join( - REPO_ROOT, - "packages/shared/src/schemas/plugin-manifest.schema.json", -); -const TEMPLATE_SCHEMA_PATH = path.join( - REPO_ROOT, - "packages/shared/src/schemas/template-plugins.schema.json", -); - -const PLUGINS_DIR = path.join(REPO_ROOT, "packages/appkit/src/plugins"); - -function loadJson(filePath: string): unknown { - return JSON.parse(fs.readFileSync(filePath, "utf-8")); -} - -function buildAjvForPlugin() { - const ajv = new Ajv({ allErrors: true, strict: false }); - addFormats(ajv); - return ajv.compile(loadJson(PLUGIN_SCHEMA_PATH) as object); -} - -function buildAjvForTemplate() { - const ajv = new Ajv({ allErrors: true, strict: false }); - addFormats(ajv); - ajv.addSchema(loadJson(PLUGIN_SCHEMA_PATH) as object); - return ajv.compile(loadJson(TEMPLATE_SCHEMA_PATH) as object); -} - -const validateLegacyPlugin = buildAjvForPlugin(); -const validateLegacyTemplate = buildAjvForTemplate(); - -interface PluginFixture { - label: string; - manifest: unknown; -} - -const corePluginFixtures: PluginFixture[] = [ - "analytics", - "files", - "genie", - "lakebase", -].map((name) => ({ - label: `core/${name}`, - manifest: loadJson(path.join(PLUGINS_DIR, name, "manifest.json")), -})); - -const minimalValid: PluginFixture = { - label: "synthetic/minimal-valid", - manifest: { - $schema: - "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json", - name: "test-plugin", - displayName: "Test Plugin", - description: "A test plugin", - resources: { - required: [], - optional: [], - }, - }, -}; - -const invalidUnknownProperty: PluginFixture = { - label: "synthetic/invalid-unknown-property", - manifest: { - ...(minimalValid.manifest as object), - nonsenseField: "boom", - }, -}; - -const invalidPermissionForType: PluginFixture = { - label: "synthetic/invalid-permission-for-type", - manifest: { - name: "bad", - displayName: "Bad", - description: "Wrong permission for sql_warehouse", - resources: { - required: [ - { - type: "sql_warehouse", - alias: "Warehouse", - resourceKey: "wh", - description: "wh", - // Wrong: this enum is for `genie_space` - permission: "CAN_RUN", - }, - ], - optional: [], - }, - }, -}; - -const invalidNamePattern: PluginFixture = { - label: "synthetic/invalid-name-pattern", - manifest: { - name: "Invalid_Name", - displayName: "Bad", - description: "Bad name pattern", - resources: { required: [], optional: [] }, - }, -}; - -const missingRequired: PluginFixture = { - label: "synthetic/missing-required", - manifest: { - displayName: "Missing name", - description: "...", - resources: { required: [], optional: [] }, - }, -}; - -const pluginFixtures: PluginFixture[] = [ - ...corePluginFixtures, - minimalValid, - invalidUnknownProperty, - invalidPermissionForType, - invalidNamePattern, - missingRequired, -]; - -interface TemplateFixture { - label: string; - manifest: unknown; -} - -const minimalTemplateValid: TemplateFixture = { - label: "synthetic/template-minimal-valid", - manifest: { - version: "1.1", - plugins: { - example: { - name: "example", - displayName: "Example", - description: "An example plugin", - package: "@databricks/appkit", - resources: { required: [], optional: [] }, - }, - }, - }, -}; - -const templateMissingScaffolding: TemplateFixture = { - label: "synthetic/template-2.0-missing-scaffolding", - manifest: { - version: "2.0", - plugins: {}, - }, -}; - -const templateInvalidVersion: TemplateFixture = { - label: "synthetic/template-bad-version", - manifest: { - version: "9.9", - plugins: {}, - }, -}; - -const templateFixtures: TemplateFixture[] = [ - minimalTemplateValid, - templateMissingScaffolding, - templateInvalidVersion, -]; - -describe("plugin manifest schema parity (Strategy B: fixture equivalence)", () => { - for (const fixture of pluginFixtures) { - it(`agrees on ${fixture.label}`, () => { - const legacyValid = validateLegacyPlugin(fixture.manifest); - const zodResult = pluginManifestSchema.safeParse(fixture.manifest); - expect( - zodResult.success, - legacyValid - ? "legacy schema accepted but Zod schema rejected" - : "legacy schema rejected but Zod schema accepted", - ).toBe(legacyValid); - }); - } -}); - -describe("template manifest schema parity (Strategy B: fixture equivalence)", () => { - for (const fixture of templateFixtures) { - it(`agrees on ${fixture.label}`, () => { - const legacyValid = validateLegacyTemplate(fixture.manifest); - const zodResult = templatePluginsManifestSchema.safeParse( - fixture.manifest, - ); - expect( - zodResult.success, - legacyValid - ? "legacy template schema accepted but Zod template schema rejected" - : "legacy template schema rejected but Zod template schema accepted", - ).toBe(legacyValid); - }); - } -}); diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts index 2880b0a3e..c522b9a0c 100644 --- a/packages/shared/src/schemas/manifest.ts +++ b/packages/shared/src/schemas/manifest.ts @@ -14,8 +14,13 @@ * from `localOnly`/`value`/`resolve`. The input slot is still allowed so * re-parsing previously-synced template manifests does not fail, but the * transform always overwrites it — drift-by-construction for hand-edits. - * - Phase 4: `discoveryDescriptorSchema` will become a discriminated union. - * For now it remains in the existing free-form shape. + * - Phase 4: `discoveryDescriptorSchema` is a discriminated union over a + * `type` literal. The `kind` variant references one of five well-known + * `resourceKind` values for which AppKit owns the CLI command map (see + * `RESOURCE_KIND_COMMANDS` below). The `cli` variant is the escape hatch + * carrying the existing free-form fields (with the `` + * refinement). Hierarchical context for volumes (catalog/schema parent + * walk) is encoded as a Phase 6 MUST rule, not modeled in the schema. * - Phase 6: scaffolding rule items get a `maxLength`, and a volume MUST * rule lands in `TEMPLATE_SCAFFOLDING`. Neither happens here. */ @@ -102,10 +107,77 @@ export const appPermissionSchema = z .enum(["CAN_USE"]) .describe("Permission for Databricks App resources"); -// ── Discovery descriptor (free-form shape until Phase 4) ───────────────── +// ── Discovery descriptor (discriminated union) ─────────────────────────── -export const discoveryDescriptorSchema = z +/** + * Well-known Databricks resource kinds for which AppKit owns the CLI + * command map. Plugins reference one of these via the `kind` variant of the + * discovery descriptor; everything else falls back to the free-form `cli` + * variant. + * + * Kept narrow on purpose: each entry costs an addition to + * `RESOURCE_KIND_COMMANDS` below, which is the single source of truth for + * how that kind is enumerated. + */ +export const resourceKindSchema = z + .enum([ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume", + ]) + .describe( + "Well-known Databricks resource kind whose listing command is owned by AppKit (see RESOURCE_KIND_COMMANDS).", + ); + +export const kindDiscoveryDescriptorSchema = z .object({ + type: z + .literal("kind") + .describe( + "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + ), + resourceKind: resourceKindSchema.describe( + "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + ), + select: z + .string() + .optional() + .describe( + "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + ), + display: z + .string() + .optional() + .describe( + "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + ), + dependsOn: z + .string() + .optional() + .describe( + "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + ), + shortcut: z + .string() + .optional() + .describe( + "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + ), + }) + .strict() + .describe( + "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + ); + +export const cliDiscoveryDescriptorSchema = z + .object({ + type: z + .literal("cli") + .describe( + "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + ), cliCommand: z .string() .describe( @@ -141,9 +213,84 @@ export const discoveryDescriptorSchema = z path: ["cliCommand"], }) .describe( - "Describes how the CLI discovers values for a resource field via a Databricks CLI command.", + "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + ); + +export const discoveryDescriptorSchema = z + .discriminatedUnion("type", [ + kindDiscoveryDescriptorSchema, + cliDiscoveryDescriptorSchema, + ]) + .describe( + "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", ); +// ── Resource kind → CLI command map ────────────────────────────────────── + +/** + * Descriptor for how a well-known resource kind is listed via the + * Databricks CLI. + * + * - `command` is the CLI invocation template. It carries two kinds of + * placeholders: + * - `` — substituted with the user's CLI profile by the runner. + * - `{}` — substituted with the resolved value of the named + * sibling field (used for `dependsOn` chains). + * - `unwrap`, when set, is the JSON path into the response wrapper (e.g., + * `"warehouses"` for `{ warehouses: [...] }`). Omitted when the response + * is already a flat array. + */ +export type ResourceKindCommand = { + command: string; + unwrap?: string; +}; + +/** + * Single source of truth for AppKit-owned discovery commands. + * + * To add a new resource kind: extend `resourceKindSchema` and add an entry + * here. Plugins reference the kind via `discovery: { type: "kind", + * resourceKind: "..." }` and inherit the command + response shape. + * + * `unwrap` defaults are unset: the existing core plugin manifests use simple + * jq paths (`.id`, `.name`, `.full_name`), implying the listed CLI commands + * return flat arrays. Refine in a follow-up if a kind's CLI returns wrapped + * data. + * + * Volume's catalog/schema parent context is NOT modeled here. The + * `databricks volumes list` command requires a `.` argument + * that AppKit does not auto-discover; the CLI is expected to prompt the user + * via a Phase 6 MUST rule before invoking the listing. + */ +export const RESOURCE_KIND_COMMANDS: Record< + z.infer, + ResourceKindCommand +> = { + warehouse: { + command: "databricks warehouses list --profile --output json", + }, + genie_space: { + command: "databricks genie list --profile --output json", + }, + postgres_branch: { + command: + "databricks postgres list-branches --profile --output json", + }, + postgres_database: { + // {branch} is a placeholder for the resolved value of the `branch` + // sibling field (declared via `dependsOn: "branch"` on the kind variant). + command: + "databricks postgres list-databases {branch} --profile --output json", + }, + volume: { + // . parent context must be supplied by the CLI runner — + // it is encoded as a Phase 6 MUST rule (prompt the user for catalog and + // schema before listing volumes), not as a schema-level placeholder. + command: + "databricks volumes list . --profile --output json", + }, +}; + // ── Resource field entry (plugin manifest variant) ─────────────────────── export const resourceFieldEntrySchema = z @@ -786,6 +933,13 @@ export type PostgresPermission = z.infer; export type GenieSpacePermission = z.infer; export type ExperimentPermission = z.infer; export type AppPermission = z.infer; +export type ResourceKind = z.infer; +export type KindDiscoveryDescriptor = z.infer< + typeof kindDiscoveryDescriptorSchema +>; +export type CliDiscoveryDescriptor = z.infer< + typeof cliDiscoveryDescriptorSchema +>; export type DiscoveryDescriptor = z.infer; export type ResourceFieldEntry = z.infer; export type ResourceRequirement = z.infer; diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index 0672e376b..001eb82cc 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -20,9 +20,8 @@ "env": "DATABRICKS_WAREHOUSE_ID", "description": "SQL Warehouse ID", "discovery": { - "cliCommand": "databricks warehouses list --profile --output json", - "selectField": ".id", - "displayField": ".name" + "type": "kind", + "resourceKind": "warehouse" }, "origin": "user" } @@ -60,9 +59,9 @@ "env": "DATABRICKS_VOLUME_FILES", "description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)", "discovery": { - "cliCommand": "databricks volumes list . --profile --output json", - "selectField": ".full_name", - "displayField": ".name" + "type": "kind", + "resourceKind": "volume", + "select": "full_name" }, "origin": "user" } @@ -100,9 +99,8 @@ "env": "DATABRICKS_GENIE_SPACE_ID", "description": "Default Genie Space ID", "discovery": { - "cliCommand": "databricks genie list --profile --output json", - "selectField": ".id", - "displayField": ".name" + "type": "kind", + "resourceKind": "genie_space" }, "origin": "user" } @@ -167,9 +165,9 @@ "projects/{project-id}/branches/{branch-id}" ], "discovery": { - "cliCommand": "databricks postgres list-branches --profile --output json", - "selectField": ".name", - "displayField": ".name" + "type": "kind", + "resourceKind": "postgres_branch", + "select": "name" }, "origin": "user" }, @@ -179,9 +177,9 @@ "projects/{project-id}/branches/{branch-id}/databases/{database-id}" ], "discovery": { - "cliCommand": "databricks postgres list-databases {branch} --profile --output json", - "selectField": ".name", - "displayField": ".name", + "type": "kind", + "resourceKind": "postgres_database", + "select": "name", "dependsOn": "branch" }, "origin": "user" From 57125edbda77ef20d693e2d3e8b9508dd0ee8918 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 21:52:52 +0200 Subject: [PATCH 12/17] chore: delete legacy json-schema pipeline (phase 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that Zod is canonical and the validation runtime calls ~standard.validate, the legacy artifacts have nothing left to do. This phase deletes them and the build steps that produced them. Deleted: - packages/shared/src/schemas/plugin-manifest.generated.ts (orphaned since phase 4 switched manifest-types.ts re-exports to Zod). - packages/shared/src/schemas/plugin-manifest.schema.json, template-plugins.schema.json (legacy hand-written JSON Schema; Zod is now the source, JSON Schema is generated into docs/static/schemas by tools/generate-json-schema.ts). - tools/generate-schema-types.ts (JSON Schema → TS codegen, replaced by tools/generate-json-schema.ts going the other way). - docs/scripts/copy-schemas.ts (copied the legacy schemas to docs/static, now no-op). Dependencies removed from packages/shared: - ajv, ajv-formats — runtime validator gone in phase 2. - json-schema-to-typescript — codegen tool gone above. Build pipeline updated: - root package.json generate:types and packages/shared build:package scripts drop generate-schema-types.ts. - packages/shared/tsdown.config.ts drops the copy: block (the .json files no longer exist). - docs/package.json drops the copy-schemas script and removes it from the gen chain. - knip.json drops json-schema-to-typescript from ignoreDependencies. - .github/workflows/ci.yml `Check generated types are up to date` step drops the deleted plugin-manifest.generated.ts and adds docs/static/schemas/*.schema.json (now owned by generate-json-schema.ts). Source migrations to Zod: - schema-resources.ts: was reading plugin-manifest.schema.json at runtime to derive resource type options and per-type permissions. Now imports the per-type permission schemas and resourceTypeSchema from the Zod module and reads .options. No filesystem reads, no caching, no defensive null branches — values are module-level constants now. Public API preserved. - tools/generate-registry-types.ts: hidden consumer that also read the legacy JSON schema. Same migration. - packages/shared/src/cli/commands/plugin/manifest-types.ts: shrunk to a thin re-export shim of z.infer types and StandardSchemaV1. Type-level fix: - TemplatePlugin / TemplateResourceRequirement / TemplateFieldEntry / TemplatePluginsManifest type aliases switched to z.input instead of z.infer/z.output. The field-level origin transform makes origin REQUIRED on z.output, but consumer code (sync.ts) constructs template plugins without origin before writeManifest runs the transform at write time. z.input gives the pre-transform shape, matching the runtime invariant. Stale JSDoc references to GeneratedPluginManifest and plugin-manifest.generated.ts updated. The published JSON Schema URL is unchanged. Plugin authors' VSCode intellisense continues to work; the docs/static/schemas/*.json files are now byte-stable across runs (generated solely by the Zod-fed generate-json-schema.ts) and contain the new discriminated-union discovery shape. Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .github/workflows/ci.yml | 9 +- .../api/appkit/Enumeration.ResourceType.md | 2 +- .../api/appkit/Interface.PluginManifest.md | 2 +- docs/docs/api/appkit/index.md | 2 +- docs/package.json | 3 +- docs/scripts/copy-schemas.ts | 44 - .../schemas/plugin-manifest.schema.json | 4504 ++++++++++++++-- .../schemas/template-plugins.schema.json | 4765 +++++++++++++++-- knip.json | 1 - package.json | 3 +- .../appkit/src/registry/types.generated.ts | 6 +- packages/appkit/src/registry/types.ts | 8 +- packages/shared/package.json | 4 +- .../plugin/create/resource-defaults.ts | 3 +- .../src/cli/commands/plugin/manifest-types.ts | 63 +- .../cli/commands/plugin/schema-resources.ts | 129 +- packages/shared/src/plugin.ts | 8 +- packages/shared/src/schemas/manifest.ts | 23 +- .../src/schemas/plugin-manifest.generated.ts | 338 -- .../src/schemas/plugin-manifest.schema.json | 553 -- .../src/schemas/template-plugins.schema.json | 455 -- packages/shared/tsdown.config.ts | 10 - pnpm-lock.yaml | 57 - tools/generate-plugin-entries.ts | 3 +- tools/generate-registry-types.ts | 145 +- tools/generate-schema-types.ts | 56 - 26 files changed, 8601 insertions(+), 2595 deletions(-) delete mode 100644 docs/scripts/copy-schemas.ts delete mode 100644 packages/shared/src/schemas/plugin-manifest.generated.ts delete mode 100644 packages/shared/src/schemas/plugin-manifest.schema.json delete mode 100644 packages/shared/src/schemas/template-plugins.schema.json delete mode 100644 tools/generate-schema-types.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 368067f5e..158c85581 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,10 +56,11 @@ jobs: run: | pnpm run generate:types if ! git diff --exit-code \ - packages/shared/src/schemas/plugin-manifest.generated.ts \ packages/appkit/src/registry/types.generated.ts \ packages/appkit/src/plugins/ga-exports.generated.ts \ packages/appkit/src/plugins/beta-exports.generated.ts \ + docs/static/schemas/plugin-manifest.schema.json \ + docs/static/schemas/template-plugins.schema.json \ docs/docs/plugins/analytics.md \ docs/docs/plugins/files.md \ docs/docs/plugins/genie.md \ @@ -80,11 +81,7 @@ jobs: # `pnpm run sync:template` runs through `packages/shared/bin/appkit.js` # which imports the built `packages/shared/dist/cli/index.js`. The # lint job doesn't otherwise build, so build just shared's dist - # before invoking sync. We invoke tsdown directly rather than - # `pnpm --filter shared build:package` because the latter also - # re-runs `generate-schema-types.ts`, which writes the raw (unformatted) - # version of `plugin-manifest.generated.ts` and trips the biome - # check that runs immediately after. ~1s. + # before invoking sync. run: pnpm --filter shared exec tsdown --config tsdown.config.ts - name: Check synced template manifest is up to date run: | diff --git a/docs/docs/api/appkit/Enumeration.ResourceType.md b/docs/docs/api/appkit/Enumeration.ResourceType.md index 2dc2e2b31..7d8c6f5de 100644 --- a/docs/docs/api/appkit/Enumeration.ResourceType.md +++ b/docs/docs/api/appkit/Enumeration.ResourceType.md @@ -1,6 +1,6 @@ # Enumeration: ResourceType -Resource types from schema $defs.resourceType.enum +Resource types from resourceTypeSchema.options ## Enumeration Members diff --git a/docs/docs/api/appkit/Interface.PluginManifest.md b/docs/docs/api/appkit/Interface.PluginManifest.md index 1747c8e62..b5f9ff206 100644 --- a/docs/docs/api/appkit/Interface.PluginManifest.md +++ b/docs/docs/api/appkit/Interface.PluginManifest.md @@ -6,7 +6,7 @@ Extends the shared PluginManifest with strict resource types. ## See - - `packages/shared/src/schemas/plugin-manifest.generated.ts` `PluginManifest` — generated base + - `packages/shared/src/schemas/manifest.ts` `pluginManifestSchema` — Zod source of truth - SharedPluginManifest — shared re-export with JSONSchema7 config ## Extends diff --git a/docs/docs/api/appkit/index.md b/docs/docs/api/appkit/index.md index 104f0bfdd..c30cd0f45 100644 --- a/docs/docs/api/appkit/index.md +++ b/docs/docs/api/appkit/index.md @@ -8,7 +8,7 @@ plugin architecture, and React integration. | Enumeration | Description | | ------ | ------ | | [RequestedClaimsPermissionSet](Enumeration.RequestedClaimsPermissionSet.md) | Permission set for Unity Catalog table access | -| [ResourceType](Enumeration.ResourceType.md) | Resource types from schema $defs.resourceType.enum | +| [ResourceType](Enumeration.ResourceType.md) | Resource types from resourceTypeSchema.options | ## Classes diff --git a/docs/package.json b/docs/package.json index dfc52a896..f3b9cd84c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -6,9 +6,8 @@ "docusaurus": "docusaurus", "dev": "pnpm run gen && docusaurus start --no-open", "build": "pnpm run gen && docusaurus build", - "gen": "pnpm run build-appkit-ui-styles && pnpm run generate-component-docs && pnpm run copy-schemas", + "gen": "pnpm run build-appkit-ui-styles && pnpm run generate-component-docs", "build-appkit-ui-styles": "tsx scripts/build-appkit-ui-styles.ts", - "copy-schemas": "tsx scripts/copy-schemas.ts", "generate-component-docs": "tsx ../tools/generate-component-mdx.ts", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", diff --git a/docs/scripts/copy-schemas.ts b/docs/scripts/copy-schemas.ts deleted file mode 100644 index 7a465eb08..000000000 --- a/docs/scripts/copy-schemas.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copies JSON schemas from packages to docs/static for hosting. - * - * Schemas are served at: - * https://databricks.github.io/appkit/schemas/{schema-name}.json - */ - -import { copyFileSync, existsSync, mkdirSync, readdirSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const SCHEMAS_SOURCE = join(__dirname, "../../packages/shared/src/schemas"); -const SCHEMAS_DEST = join(__dirname, "../static/schemas"); - -function copySchemas() { - console.log("Copying JSON schemas to docs/static/schemas..."); - - // Ensure destination directory exists - if (!existsSync(SCHEMAS_DEST)) { - mkdirSync(SCHEMAS_DEST, { recursive: true }); - } - - // Check if source directory exists - if (!existsSync(SCHEMAS_SOURCE)) { - console.warn(`Schemas source directory not found: ${SCHEMAS_SOURCE}`); - return; - } - - // Copy all .json files - const files = readdirSync(SCHEMAS_SOURCE).filter((f) => f.endsWith(".json")); - - for (const file of files) { - const src = join(SCHEMAS_SOURCE, file); - const dest = join(SCHEMAS_DEST, file); - copyFileSync(src, dest); - console.log(` Copied: ${file}`); - } - - console.log(`Done! ${files.length} schema(s) copied.`); -} - -copySchemas(); diff --git a/docs/static/schemas/plugin-manifest.schema.json b/docs/static/schemas/plugin-manifest.schema.json index 45fea2723..72ab9f0d1 100644 --- a/docs/static/schemas/plugin-manifest.schema.json +++ b/docs/static/schemas/plugin-manifest.schema.json @@ -4,426 +4,4154 @@ "title": "AppKit Plugin Manifest", "description": "Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options.", "type": "object", - "required": ["name", "displayName", "description", "resources"], "properties": { "$schema": { - "type": "string", - "description": "Reference to the JSON Schema for validation" + "description": "Reference to the JSON Schema for validation", + "type": "string" }, "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", - "examples": ["analytics", "server", "my-custom-plugin"] + "type": "string" }, "displayName": { - "type": "string", - "minLength": 1, "description": "Human-readable display name for UI and CLI", - "examples": ["Analytics Plugin", "Server Plugin"] + "type": "string" }, "description": { - "type": "string", - "minLength": 1, "description": "Brief description of what the plugin does", - "examples": ["SQL query execution against Databricks SQL Warehouses"] + "type": "string" }, "resources": { - "type": "object", - "required": ["required", "optional"], "description": "Databricks resource requirements for this plugin", + "type": "object", "properties": { "required": { - "type": "array", "description": "Resources that must be available for the plugin to function", + "type": "array", "items": { - "$ref": "#/$defs/resourceRequirement" + "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().", + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "secret" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ", "WRITE", "MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "job" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "sql_warehouse" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "serving_endpoint" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "volume" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ_VOLUME", "WRITE_VOLUME"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "vector_search_index" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["SELECT"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_function" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["EXECUTE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_connection" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["USE_CONNECTION"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "database" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "postgres" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "genie_space" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "experiment" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "app" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + } + ] } }, "optional": { - "type": "array", "description": "Resources that enhance functionality but are not mandatory", + "type": "array", "items": { - "$ref": "#/$defs/resourceRequirement" + "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().", + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "secret" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ", "WRITE", "MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "job" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "sql_warehouse" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "serving_endpoint" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "volume" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ_VOLUME", "WRITE_VOLUME"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "vector_search_index" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["SELECT"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_function" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["EXECUTE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_connection" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["USE_CONNECTION"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "database" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "postgres" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "genie_space" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "experiment" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "app" + }, + "alias": { + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "additionalProperties": { + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "cliCommand", "selectField"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + } + ] } } }, + "required": ["required", "optional"], "additionalProperties": false }, "config": { - "type": "object", "description": "Configuration schema for the plugin", + "type": "object", "properties": { "schema": { - "$ref": "#/$defs/configSchema" + "$ref": "#/definitions/__schema0" } }, "additionalProperties": false }, "author": { - "type": "string", - "description": "Author name or organization" + "description": "Author name or organization", + "type": "string" }, "version": { - "type": "string", - "pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?$", "description": "Plugin version (semver format)", - "examples": ["1.0.0", "2.1.0-beta.1"] + "type": "string" }, "repository": { + "description": "URL to the plugin's source repository", "type": "string", - "format": "uri", - "description": "URL to the plugin's source repository" + "format": "uri" }, "keywords": { + "description": "Keywords for plugin discovery", "type": "array", "items": { "type": "string" - }, - "description": "Keywords for plugin discovery" + } }, "license": { - "type": "string", "description": "SPDX license identifier", - "examples": ["Apache-2.0", "MIT"] + "type": "string" }, "onSetupMessage": { - "type": "string", - "description": "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning)." + "description": "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning).", + "type": "string" }, "hidden": { - "type": "boolean", - "default": false, - "description": "When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync." + "description": "When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync.", + "type": "boolean" }, "stability": { + "description": "Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly.", "type": "string", - "enum": ["beta", "ga"], - "default": "ga", - "description": "Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly." + "enum": ["beta", "ga"] }, "postScaffold": { + "description": "Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order.", "type": "array", "items": { - "$ref": "#/$defs/postScaffoldStep" - }, - "description": "Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order." + "description": "A post-scaffolding instruction shown to the user after project initialization.", + "type": "object", + "properties": { + "instruction": { + "description": "Human-readable instruction for the user to follow after scaffolding.", + "type": "string" + }, + "required": { + "description": "Whether this step is required for the plugin to function correctly.", + "type": "boolean" + } + }, + "required": ["instruction"], + "additionalProperties": false + } } }, + "required": ["name", "displayName", "description", "resources"], "additionalProperties": false, - "$defs": { - "resourceType": { - "type": "string", - "enum": [ - "secret", - "job", - "sql_warehouse", - "serving_endpoint", - "volume", - "vector_search_index", - "uc_function", - "uc_connection", - "database", - "postgres", - "genie_space", - "experiment", - "app" - ], - "description": "Type of Databricks resource" - }, - "secretPermission": { - "type": "string", - "enum": ["READ", "WRITE", "MANAGE"], - "description": "Permission for secret resources (order: weakest to strongest)" - }, - "jobPermission": { - "type": "string", - "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"], - "description": "Permission for job resources (order: weakest to strongest)" - }, - "sqlWarehousePermission": { - "type": "string", - "enum": ["CAN_USE", "CAN_MANAGE"], - "description": "Permission for SQL warehouse resources (order: weakest to strongest)" - }, - "servingEndpointPermission": { - "type": "string", - "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"], - "description": "Permission for serving endpoint resources (order: weakest to strongest)" - }, - "volumePermission": { - "type": "string", - "enum": ["READ_VOLUME", "WRITE_VOLUME"], - "description": "Permission for Unity Catalog volume resources" - }, - "vectorSearchIndexPermission": { - "type": "string", - "enum": ["SELECT"], - "description": "Permission for vector search index resources" - }, - "ucFunctionPermission": { - "type": "string", - "enum": ["EXECUTE"], - "description": "Permission for Unity Catalog function resources" - }, - "ucConnectionPermission": { - "type": "string", - "enum": ["USE_CONNECTION"], - "description": "Permission for Unity Catalog connection resources" - }, - "databasePermission": { - "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"], - "description": "Permission for database resources" - }, - "postgresPermission": { - "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"], - "description": "Permission for Postgres resources" - }, - "genieSpacePermission": { - "type": "string", - "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"], - "description": "Permission for Genie Space resources (order: weakest to strongest)" - }, - "experimentPermission": { - "type": "string", - "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"], - "description": "Permission for MLflow experiment resources (order: weakest to strongest)" - }, - "appPermission": { - "type": "string", - "enum": ["CAN_USE"], - "description": "Permission for Databricks App resources" - }, - "resourceFieldEntry": { + "definitions": { + "__schema0": { "type": "object", - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", - "properties": { - "env": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9_]*$", - "description": "Environment variable name for this field", - "examples": ["DATABRICKS_CACHE_INSTANCE", "SECRET_SCOPE"] - }, - "description": { - "type": "string", - "description": "Human-readable description for this field" - }, - "bundleIgnore": { - "type": "boolean", - "default": false, - "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation." - }, - "examples": { - "type": "array", - "items": { "type": "string" }, - "description": "Example values showing the expected format for this field" - }, - "localOnly": { - "type": "boolean", - "default": false, - "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time." - }, - "value": { - "type": "string", - "description": "Static value for this field. Used when no prompted or resolved value exists." - }, - "resolve": { - "type": "string", - "pattern": "^[a-z_]+:[a-zA-Z]+$", - "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow." - }, - "discovery": { - "$ref": "#/$defs/discoveryDescriptor" - } - }, - "additionalProperties": false - }, - "resourceRequirement": { - "type": "object", - "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().", - "required": ["type", "alias", "resourceKey", "description", "permission"], "properties": { "type": { - "$ref": "#/$defs/resourceType" - }, - "alias": { - "type": "string", - "minLength": 1, - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "examples": ["SQL Warehouse", "Secret", "Vector search index"] - }, - "resourceKey": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "examples": ["sql-warehouse", "database", "secret"] - }, - "description": { - "type": "string", - "minLength": 1, - "description": "Human-readable description of why this resource is needed" - }, - "permission": { "type": "string", - "description": "Required permission level. Validated per resource type by the allOf/if-then rules below." + "enum": ["object", "array", "string", "number", "boolean"] }, - "fields": { + "properties": { "type": "object", "additionalProperties": { - "$ref": "#/$defs/resourceFieldEntry" - }, - "minProperties": 1, - "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." - } - }, - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { "type": { "const": "secret" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/secretPermission" } - } + "$ref": "#/definitions/__schema1" } }, - { - "if": { - "properties": { "type": { "const": "job" } }, - "required": ["type"] - }, - "then": { - "properties": { "permission": { "$ref": "#/$defs/jobPermission" } } - } - }, - { - "if": { - "properties": { "type": { "const": "sql_warehouse" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/sqlWarehousePermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "serving_endpoint" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/servingEndpointPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "volume" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/volumePermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "vector_search_index" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/vectorSearchIndexPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_function" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/ucFunctionPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_connection" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/ucConnectionPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "database" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/databasePermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "postgres" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/postgresPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "genie_space" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/genieSpacePermission" } - } - } + "items": { + "$ref": "#/definitions/__schema0" }, - { - "if": { - "properties": { "type": { "const": "experiment" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/experimentPermission" } - } + "required": { + "type": "array", + "items": { + "type": "string" } }, - { - "if": { - "properties": { "type": { "const": "app" } }, - "required": ["type"] - }, - "then": { - "properties": { "permission": { "$ref": "#/$defs/appPermission" } } - } + "additionalProperties": { + "type": "boolean" } - ] + }, + "required": ["type"] }, - "configSchemaProperty": { + "__schema1": { "type": "object", - "required": ["type"], "properties": { "type": { "type": "string", @@ -434,16 +4162,17 @@ }, "default": {}, "enum": { - "type": "array" + "type": "array", + "items": {} }, "properties": { "type": "object", "additionalProperties": { - "$ref": "#/$defs/configSchemaProperty" + "$ref": "#/definitions/__schema1" } }, "items": { - "$ref": "#/$defs/configSchemaProperty" + "$ref": "#/definitions/__schema1" }, "minimum": { "type": "number" @@ -452,102 +4181,19 @@ "type": "number" }, "minLength": { - "type": "integer", - "minimum": 0 + "type": "number" }, "maxLength": { - "type": "integer", - "minimum": 0 - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "configSchema": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["object", "array", "string", "number", "boolean"] - }, - "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/configSchemaProperty" - } - }, - "items": { - "$ref": "#/$defs/configSchema" + "type": "number" }, "required": { "type": "array", "items": { "type": "string" } - }, - "additionalProperties": { - "type": "boolean" - } - } - }, - "discoveryDescriptor": { - "type": "object", - "description": "Describes how the CLI discovers values for a resource field via a Databricks CLI command.", - "required": ["cliCommand", "selectField"], - "properties": { - "cliCommand": { - "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "examples": [ - "databricks warehouses list --profile --output json" - ] - }, - "selectField": { - "type": "string", - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "examples": [".id", ".name", ".catalog_name"] - }, - "displayField": { - "type": "string", - "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", - "examples": [".name", ".display_name"] - }, - "dependsOn": { - "type": "string", - "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", - "examples": ["branch", "catalog"] - }, - "shortcut": { - "type": "string", - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", - "examples": [ - "databricks warehouses get --profile --output json" - ] } }, - "additionalProperties": false - }, - "postScaffoldStep": { - "type": "object", - "description": "A post-scaffolding instruction shown to the user after project initialization.", - "required": ["instruction"], - "properties": { - "instruction": { - "type": "string", - "description": "Human-readable instruction for the user to follow after scaffolding." - }, - "required": { - "type": "boolean", - "default": true, - "description": "Whether this step is required for the plugin to function correctly." - } - }, - "additionalProperties": false + "required": ["type"] } } } diff --git a/docs/static/schemas/template-plugins.schema.json b/docs/static/schemas/template-plugins.schema.json index 20c459785..c634bbc36 100644 --- a/docs/static/schemas/template-plugins.schema.json +++ b/docs/static/schemas/template-plugins.schema.json @@ -4,452 +4,4393 @@ "title": "AppKit Template Plugins Manifest", "description": "Aggregated plugin manifest for AppKit templates. Read by Databricks CLI during init to discover available plugins and their resource requirements.", "type": "object", - "required": ["version", "plugins"], "properties": { "$schema": { - "type": "string", - "description": "Reference to the JSON Schema for validation" + "description": "Reference to the JSON Schema for validation", + "type": "string" }, "version": { + "description": "Schema version for the template plugins manifest", "type": "string", - "enum": ["1.0", "1.1", "2.0"], - "description": "Schema version for the template plugins manifest" + "enum": ["1.0", "1.1", "2.0"] }, "plugins": { - "type": "object", "description": "Map of plugin name to plugin manifest with package source", - "additionalProperties": { - "$ref": "#/$defs/templatePlugin" - } - }, - "scaffolding": { - "$ref": "#/$defs/scaffoldingDescriptor", - "description": "Describes the scaffolding command and its configuration for project initialization." - } - }, - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { "version": { "const": "2.0" } }, - "required": ["version"] - }, - "then": { - "required": ["version", "plugins", "scaffolding"] - } - } - ], - "$defs": { - "templatePlugin": { - "type": "object", - "required": [ - "name", - "displayName", - "description", - "package", - "resources" - ], - "description": "Plugin manifest with package source information", - "properties": { - "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", - "examples": ["analytics", "server", "my-custom-plugin"] - }, - "displayName": { - "type": "string", - "minLength": 1, - "description": "Human-readable display name for UI and CLI", - "examples": ["Analytics Plugin", "Server Plugin"] - }, - "description": { - "type": "string", - "minLength": 1, - "description": "Brief description of what the plugin does", - "examples": ["SQL query execution against Databricks SQL Warehouses"] - }, - "package": { - "type": "string", - "minLength": 1, - "description": "NPM package name that provides this plugin", - "examples": ["@databricks/appkit", "@my-org/custom-plugin"] - }, - "requiredByTemplate": { - "type": "boolean", - "default": false, - "description": "When true, this plugin is required by the template and cannot be deselected during CLI init. The user will only be prompted to configure its resources. When absent or false, the plugin is optional and the user can choose whether to include it." - }, - "onSetupMessage": { - "type": "string", - "description": "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning)." - }, - "stability": { - "type": "string", - "enum": ["beta", "ga"], - "default": "ga", - "description": "Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver." - }, - "postScaffold": { - "type": "array", - "items": { - "$ref": "plugin-manifest.schema.json#/$defs/postScaffoldStep" - }, - "description": "Ordered list of post-scaffolding instructions propagated from the plugin manifest." - }, - "resources": { - "type": "object", - "required": ["required", "optional"], - "description": "Databricks resource requirements for this plugin", - "properties": { - "required": { - "type": "array", - "description": "Resources that must be available for the plugin to function", - "items": { - "$ref": "#/$defs/resourceRequirement" - } - }, - "optional": { - "type": "array", - "description": "Resources that enhance functionality but are not mandatory", - "items": { - "$ref": "#/$defs/resourceRequirement" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "resourceType": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceType" - }, - "resourceFieldEntry": { "type": "object", - "description": "Extends the plugin manifest resourceFieldEntry with a computed origin field for template manifests.", - "properties": { - "env": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9_]*$", - "description": "Environment variable name for this field" - }, - "description": { - "type": "string", - "description": "Human-readable description for this field" - }, - "bundleIgnore": { - "type": "boolean", - "default": false, - "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation." - }, - "examples": { - "type": "array", - "items": { "type": "string" }, - "description": "Example values showing the expected format for this field" - }, - "localOnly": { - "type": "boolean", - "default": false, - "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time." - }, - "value": { - "type": "string", - "description": "Static value for this field." - }, - "resolve": { - "type": "string", - "pattern": "^[a-z_]+:[a-zA-Z]+$", - "description": "Named resolver prefixed by resource type (e.g., 'postgres:host')." - }, - "discovery": { - "$ref": "plugin-manifest.schema.json#/$defs/discoveryDescriptor", - "description": "How the CLI discovers values for this field via a Databricks CLI command." - }, - "origin": { - "$ref": "#/$defs/origin" - } - }, - "additionalProperties": false - }, - "resourceRequirement": { - "type": "object", - "description": "Resource requirement with template-specific field entries (includes computed origin).", - "required": ["type", "alias", "resourceKey", "description", "permission"], - "properties": { - "type": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceType" - }, - "alias": { - "type": "string", - "minLength": 1, - "description": "Human-readable label for UI/display only." - }, - "resourceKey": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Stable key for machine use: deduplication, env naming, composite keys." - }, - "description": { - "type": "string", - "minLength": 1, - "description": "Human-readable description of why this resource is needed" - }, - "permission": { - "type": "string", - "description": "Required permission level." - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/resourceFieldEntry" - }, - "minProperties": 1, - "description": "Map of field name to field entry with computed origin." - } - }, - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { "type": { "const": "secret" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/secretPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "job" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/jobPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "sql_warehouse" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/sqlWarehousePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "serving_endpoint" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/servingEndpointPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "volume" } }, - "required": ["type"] + "additionalProperties": { + "description": "Plugin manifest with package source information", + "type": "object", + "properties": { + "name": { + "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", + "type": "string" }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/volumePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "vector_search_index" } }, - "required": ["type"] + "displayName": { + "description": "Human-readable display name for UI and CLI", + "type": "string" }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/vectorSearchIndexPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_function" } }, - "required": ["type"] + "description": { + "description": "Brief description of what the plugin does", + "type": "string" }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/ucFunctionPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_connection" } }, - "required": ["type"] + "package": { + "description": "NPM package name that provides this plugin", + "type": "string" }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/ucConnectionPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "database" } }, - "required": ["type"] + "requiredByTemplate": { + "description": "When true, this plugin is required by the template and cannot be deselected during CLI init. The user will only be prompted to configure its resources. When absent or false, the plugin is optional and the user can choose whether to include it.", + "type": "boolean" }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/databasePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "postgres" } }, - "required": ["type"] + "onSetupMessage": { + "description": "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning).", + "type": "string" }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/postgresPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "genie_space" } }, - "required": ["type"] + "stability": { + "description": "Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver.", + "type": "string", + "enum": ["beta", "ga"] }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/genieSpacePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "experiment" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/experimentPermission" - } + "postScaffold": { + "description": "Ordered list of post-scaffolding instructions propagated from the plugin manifest.", + "type": "array", + "items": { + "description": "A post-scaffolding instruction shown to the user after project initialization.", + "type": "object", + "properties": { + "instruction": { + "description": "Human-readable instruction for the user to follow after scaffolding.", + "type": "string" + }, + "required": { + "description": "Whether this step is required for the plugin to function correctly.", + "type": "boolean" + } + }, + "required": ["instruction"], + "additionalProperties": false } - } - }, - { - "if": { - "properties": { "type": { "const": "app" } }, - "required": ["type"] }, - "then": { + "resources": { + "description": "Databricks resource requirements for this plugin", + "type": "object", "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/appPermission" + "required": { + "description": "Resources that must be available for the plugin to function", + "type": "array", + "items": { + "description": "Resource requirement with template-specific field entries (includes computed origin).", + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "secret" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ", "WRITE", "MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "job" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "sql_warehouse" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "serving_endpoint" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "volume" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ_VOLUME", "WRITE_VOLUME"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "vector_search_index" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["SELECT"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_function" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["EXECUTE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_connection" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["USE_CONNECTION"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "database" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "postgres" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "genie_space" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": [ + "CAN_VIEW", + "CAN_RUN", + "CAN_EDIT", + "CAN_MANAGE" + ] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "experiment" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "app" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + } + ] + } + }, + "optional": { + "description": "Resources that enhance functionality but are not mandatory", + "type": "array", + "items": { + "description": "Resource requirement with template-specific field entries (includes computed origin).", + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "secret" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ", "WRITE", "MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "job" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "sql_warehouse" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "serving_endpoint" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "volume" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["READ_VOLUME", "WRITE_VOLUME"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "vector_search_index" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["SELECT"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_function" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["EXECUTE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "uc_connection" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["USE_CONNECTION"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "database" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "postgres" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_CONNECT_AND_CREATE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "genie_space" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": [ + "CAN_VIEW", + "CAN_RUN", + "CAN_EDIT", + "CAN_MANAGE" + ] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "experiment" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "app" + }, + "alias": { + "description": "Human-readable label for UI/display only.", + "type": "string" + }, + "resourceKey": { + "description": "Stable key for machine use: deduplication, env naming, composite keys.", + "type": "string" + }, + "description": { + "description": "Human-readable description of why this resource is needed", + "type": "string" + }, + "fields": { + "description": "Map of field name to field entry with computed origin.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "env": { + "description": "Environment variable name for this field", + "type": "string" + }, + "description": { + "description": "Human-readable description for this field", + "type": "string" + }, + "bundleIgnore": { + "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation.", + "type": "boolean" + }, + "examples": { + "description": "Example values showing the expected format for this field", + "type": "array", + "items": { + "type": "string" + } + }, + "localOnly": { + "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time.", + "type": "boolean" + }, + "value": { + "description": "Static value for this field. Used when no prompted or resolved value exists.", + "type": "string" + }, + "resolve": { + "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", + "type": "string" + }, + "discovery": { + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", + "oneOf": [ + { + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", + "type": "string", + "const": "kind" + }, + "resourceKind": { + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", + "type": "string", + "enum": [ + "warehouse", + "genie_space", + "postgres_branch", + "postgres_database", + "volume" + ] + }, + "select": { + "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", + "type": "string" + }, + "display": { + "description": "Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": ["type", "resourceKind"], + "additionalProperties": false + }, + { + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "type": "object", + "properties": { + "type": { + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", + "type": "string", + "const": "cli" + }, + "cliCommand": { + "description": "Databricks CLI command that lists resources. Must include placeholder.", + "type": "string" + }, + "selectField": { + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", + "type": "string" + }, + "displayField": { + "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", + "type": "string" + }, + "dependsOn": { + "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", + "type": "string" + }, + "shortcut": { + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "type": "string" + } + }, + "required": [ + "type", + "cliCommand", + "selectField" + ], + "additionalProperties": false + } + ] + }, + "origin": { + "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", + "type": "string", + "enum": ["user", "platform", "static", "cli"] + } + }, + "additionalProperties": false + } + }, + "permission": { + "description": "Required permission level. Validated per resource type.", + "type": "string", + "enum": ["CAN_USE"] + } + }, + "required": [ + "type", + "alias", + "resourceKey", + "description", + "permission" + ], + "additionalProperties": false + } + ] + } } - } + }, + "required": ["required", "optional"], + "additionalProperties": false } - } - ] - }, - "origin": { - "type": "string", - "enum": ["user", "platform", "static", "cli"], - "description": "How the field value is determined. Computed during sync, not authored by plugin developers." - }, - "scaffoldingFlag": { - "type": "object", - "description": "A flag for the scaffolding command.", - "required": ["description"], - "properties": { - "description": { - "type": "string", - "description": "Human-readable description of the flag." - }, - "required": { - "type": "boolean", - "default": false, - "description": "Whether this flag is required." }, - "pattern": { - "type": "string", - "description": "Regex pattern for validating the flag value." - }, - "default": { - "type": "string", - "description": "Default value for this flag." - } - }, - "additionalProperties": false + "required": [ + "name", + "displayName", + "description", + "package", + "resources" + ], + "additionalProperties": false + } }, - "scaffoldingRules": { + "scaffolding": { + "description": "Describes the scaffolding command and its configuration for project initialization.", "type": "object", - "description": "Structured rules for scaffolding agents.", - "properties": { - "never": { - "type": "array", - "items": { "type": "string" }, - "description": "Actions the scaffolding agent must never perform." - }, - "must": { - "type": "array", - "items": { "type": "string" }, - "description": "Actions the scaffolding agent must always perform." - } - }, - "additionalProperties": false - }, - "scaffoldingDescriptor": { - "type": "object", - "description": "Describes the scaffolding command, flags, and rules for project initialization.", - "required": ["command"], "properties": { "command": { - "type": "string", - "description": "The scaffolding command (e.g., 'databricks apps init')." + "description": "The scaffolding command (e.g., 'databricks apps init').", + "type": "string" }, "flags": { + "description": "Map of flag name to flag descriptor.", "type": "object", "additionalProperties": { - "$ref": "#/$defs/scaffoldingFlag" - }, - "description": "Map of flag name to flag descriptor." + "description": "A flag for the scaffolding command.", + "type": "object", + "properties": { + "description": { + "description": "Human-readable description of the flag.", + "type": "string" + }, + "required": { + "description": "Whether this flag is required.", + "type": "boolean" + }, + "pattern": { + "description": "Regex pattern for validating the flag value.", + "type": "string" + }, + "default": { + "description": "Default value for this flag.", + "type": "string" + } + }, + "required": ["description"], + "additionalProperties": false + } }, "rules": { - "$ref": "#/$defs/scaffoldingRules", - "description": "Structured rules for scaffolding agents." + "description": "Structured rules for scaffolding agents.", + "type": "object", + "properties": { + "never": { + "description": "Actions the scaffolding agent must never perform.", + "type": "array", + "items": { + "type": "string" + } + }, + "must": { + "description": "Actions the scaffolding agent must always perform.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false } }, + "required": ["command"], "additionalProperties": false } - } + }, + "required": ["version", "plugins"], + "additionalProperties": false } diff --git a/knip.json b/knip.json index 101b9253a..c6f54d22e 100644 --- a/knip.json +++ b/knip.json @@ -27,6 +27,5 @@ "docs/**", ".github/scripts/**" ], - "ignoreDependencies": ["json-schema-to-typescript"], "ignoreBinaries": ["tarball"] } diff --git a/package.json b/package.json index a01e0de81..733abdc12 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build:watch": "pnpm -r --filter=!dev-playground --filter=!docs build:watch", "check:fix": "biome check --write .", "check": "biome check .", - "generate:types": "tsx tools/generate-schema-types.ts && tsx tools/generate-json-schema.ts && tsx tools/generate-registry-types.ts && tsx tools/generate-plugin-entries.ts && tsx tools/generate-plugin-doc-banners.ts", + "generate:types": "tsx tools/generate-json-schema.ts && tsx tools/generate-registry-types.ts && tsx tools/generate-plugin-entries.ts && tsx tools/generate-plugin-doc-banners.ts", "generate:plugin-entries": "tsx tools/generate-plugin-entries.ts", "generate:plugin-doc-banners": "tsx tools/generate-plugin-doc-banners.ts", "generate:app-templates": "tsx tools/generate-app-templates.ts", @@ -64,7 +64,6 @@ "husky": "9.1.7", "jsdom": "27.0.0", "knip": "5.86.0", - "json-schema-to-typescript": "15.0.4", "lint-staged": "15.5.2", "publint": "0.3.15", "release-it": "19.2.0", diff --git a/packages/appkit/src/registry/types.generated.ts b/packages/appkit/src/registry/types.generated.ts index 7e38af9bd..5739f0a9c 100644 --- a/packages/appkit/src/registry/types.generated.ts +++ b/packages/appkit/src/registry/types.generated.ts @@ -1,7 +1,7 @@ -// AUTO-GENERATED from packages/shared/src/schemas/plugin-manifest.schema.json +// AUTO-GENERATED from packages/shared/src/schemas/manifest.ts (Zod canonical). // Do not edit. Run: pnpm exec tsx tools/generate-registry-types.ts -/** Resource types from schema $defs.resourceType.enum */ +/** Resource types from resourceTypeSchema.options */ export enum ResourceType { SECRET = "secret", JOB = "job", @@ -19,7 +19,7 @@ export enum ResourceType { } // ============================================================================ -// Permissions per resource type (from schema permission $defs) +// Permissions per resource type (from per-type permission enum schemas) // ============================================================================ /** Permissions for SECRET resources */ export type SecretPermission = "READ" | "WRITE" | "MANAGE"; diff --git a/packages/appkit/src/registry/types.ts b/packages/appkit/src/registry/types.ts index 96c3c9664..a18a2881e 100644 --- a/packages/appkit/src/registry/types.ts +++ b/packages/appkit/src/registry/types.ts @@ -6,9 +6,9 @@ * in a machine-readable format. * * Base interfaces (ResourceFieldEntry, ResourceRequirement, PluginManifest) - * are generated from plugin-manifest.schema.json and imported via shared. - * This module re-exports them with strict narrowing (ResourceType enum, - * ResourcePermission union) for appkit-internal use. + * are inferred from the Zod schemas in `packages/shared/src/schemas/manifest.ts` + * and imported via shared. This module re-exports them with strict narrowing + * (ResourceType enum, ResourcePermission union) for appkit-internal use. * * Resource types, permissions, and hierarchy constants are generated by * tools/generate-registry-types.ts into types.generated.ts. @@ -122,7 +122,7 @@ export type ConfigSchema = JSONSchema7; * Attached to plugin classes as a static property. * Extends the shared PluginManifest with strict resource types. * - * @see `packages/shared/src/schemas/plugin-manifest.generated.ts` `PluginManifest` — generated base + * @see `packages/shared/src/schemas/manifest.ts` `pluginManifestSchema` — Zod source of truth * @see {@link SharedPluginManifest} — shared re-export with JSONSchema7 config */ export interface PluginManifest diff --git a/packages/shared/package.json b/packages/shared/package.json index b2313054c..00aafed93 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -15,7 +15,7 @@ "./package.json": "./package.json" }, "scripts": { - "build:package": "pnpm exec tsx ../../tools/generate-schema-types.ts && pnpm exec tsx ../../tools/generate-json-schema.ts && tsdown --config tsdown.config.ts", + "build:package": "pnpm exec tsx ../../tools/generate-json-schema.ts && tsdown --config tsdown.config.ts", "build:watch": "tsdown --config tsdown.config.ts --watch", "typecheck": "tsc --noEmit", "clean": "rm -rf dist", @@ -38,8 +38,6 @@ "dependencies": { "@ast-grep/napi": "0.37.0", "@standard-schema/spec": "1.1.0", - "ajv": "8.17.1", - "ajv-formats": "3.0.1", "@clack/prompts": "1.0.1", "commander": "12.1.0", "zod": "4.3.6" diff --git a/packages/shared/src/cli/commands/plugin/create/resource-defaults.ts b/packages/shared/src/cli/commands/plugin/create/resource-defaults.ts index de1eaabd9..1e078926b 100644 --- a/packages/shared/src/cli/commands/plugin/create/resource-defaults.ts +++ b/packages/shared/src/cli/commands/plugin/create/resource-defaults.ts @@ -1,6 +1,7 @@ /** * Resource type and permission defaults for plugin scaffolding. - * Values are derived from plugin-manifest.schema.json via schema-resources. + * Values are sourced from the canonical Zod schemas in `schemas/manifest`, + * surfaced through the `schema-resources` module. */ import { diff --git a/packages/shared/src/cli/commands/plugin/manifest-types.ts b/packages/shared/src/cli/commands/plugin/manifest-types.ts index 95121864e..c06b13d1b 100644 --- a/packages/shared/src/cli/commands/plugin/manifest-types.ts +++ b/packages/shared/src/cli/commands/plugin/manifest-types.ts @@ -1,60 +1,25 @@ /** - * Shared types for plugin manifests used across CLI commands. + * Thin re-export shim for plugin manifest types used across CLI commands. * - * Phase 4 update: `DiscoveryDescriptor`, `ResourceFieldEntry`, - * `ResourceRequirement`, `PluginManifest`, and `PostScaffoldStep` are now - * sourced from the Zod schema module (the canonical contract). The legacy - * `plugin-manifest.generated.ts` types are stale because the discovery - * contract reshaped to a discriminated union; re-exporting from there would - * drop the union and break downstream type checks. CLI-specific extensions - * (`TemplatePlugin`, `TemplatePluginsManifest`) are still hand-written here - * — Phase 5 will fold those into the Zod module and delete this shim. + * Phase 5: source of truth is the Zod schema module (`../../../schemas/manifest`). + * All type aliases and the Standard Schema interface re-export from there. The + * legacy `plugin-manifest.generated.ts` is gone; kept this shim only so existing + * callers (`sync.ts`, `validate.ts`, `create.ts`, `add-resource.ts`, etc.) + * continue importing from the same path without rewrite. */ +export type { StandardSchemaV1 } from "@standard-schema/spec"; export type { DiscoveryDescriptor, + Origin, PluginManifest, PostScaffoldStep, ResourceFieldEntry, + ResourceKind, ResourceRequirement, + ScaffoldingDescriptor, + ScaffoldingFlag, + ScaffoldingRules, + TemplatePlugin, + TemplatePluginsManifest, } from "../../../schemas/manifest"; - -import type { - PluginManifest, - PostScaffoldStep, -} from "../../../schemas/manifest"; - -export interface ScaffoldingFlag { - description: string; - required?: boolean; - pattern?: string; - default?: string; -} - -export interface ScaffoldingRules { - never?: string[]; - must?: string[]; -} - -export interface ScaffoldingDescriptor { - command: string; - flags?: Record; - rules?: ScaffoldingRules; -} - -export interface TemplatePlugin extends Omit { - package: string; - /** When true, this plugin is required by the template and cannot be deselected during CLI init. */ - requiredByTemplate?: boolean; - /** Plugin stability level. Absent or undefined means "ga" (general availability). */ - stability?: "beta" | "ga"; - /** Ordered list of post-scaffolding instructions propagated from the plugin manifest. */ - postScaffold?: PostScaffoldStep[]; -} - -export interface TemplatePluginsManifest { - $schema: string; - version: string; - plugins: Record; - scaffolding?: ScaffoldingDescriptor; -} diff --git a/packages/shared/src/cli/commands/plugin/schema-resources.ts b/packages/shared/src/cli/commands/plugin/schema-resources.ts index 167f08dfb..7b75ea656 100644 --- a/packages/shared/src/cli/commands/plugin/schema-resources.ts +++ b/packages/shared/src/cli/commands/plugin/schema-resources.ts @@ -1,40 +1,33 @@ /** - * Resource types and permissions derived from plugin-manifest.schema.json. - * Single source of truth so create, add-resource, and validate stay in sync with the schema. + * Resource types and permissions sourced from the canonical Zod schemas in + * `schemas/manifest.ts`. Single source of truth so create, add-resource, and + * validate stay in sync with the schema. + * + * Phase 5 update: replaced runtime JSON-schema reads with direct imports from + * the Zod module. Values are now constant at module load — no caching needed. */ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const SCHEMA_NAME = "plugin-manifest.schema.json"; -// Try dist/schemas first (shared build + appkit pack), then dist/cli/schemas -const SCHEMA_PATHS = [ - path.join(__dirname, "..", "..", "..", "schemas", SCHEMA_NAME), - path.join(__dirname, "..", "..", "schemas", SCHEMA_NAME), -]; +import { + appPermissionSchema, + databasePermissionSchema, + experimentPermissionSchema, + genieSpacePermissionSchema, + jobPermissionSchema, + postgresPermissionSchema, + resourceTypeSchema, + secretPermissionSchema, + servingEndpointPermissionSchema, + sqlWarehousePermissionSchema, + ucConnectionPermissionSchema, + ucFunctionPermissionSchema, + vectorSearchIndexPermissionSchema, + volumePermissionSchema, +} from "../../../schemas/manifest"; export interface ResourceTypeOption { value: string; label: string; } -function loadSchema(): Record | null { - for (const schemaPath of SCHEMA_PATHS) { - try { - if (fs.existsSync(schemaPath)) { - return JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as Record< - string, - unknown - >; - } - } catch { - // try next path - } - } - return null; -} - /** Optional display overrides for acronyms (e.g. SQL, UC). Omitted entries use title-case of value. */ const LABEL_OVERRIDES: Record = { sql_warehouse: "SQL Warehouse", @@ -47,69 +40,47 @@ function humanize(value: string): string { return value.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } -let cachedOptions: ResourceTypeOption[] | null = null; -let cachedPermissions: Record | null = null; +/** + * Map from resource type literal to its permission enum schema. Mirrors the + * `allOf + if/then` block in the previous hand-written JSON Schema, but driven + * directly from the Zod source. Add an entry here when extending + * `resourceTypeSchema`. + */ +const PERMISSION_SCHEMAS_BY_TYPE = { + secret: secretPermissionSchema, + job: jobPermissionSchema, + sql_warehouse: sqlWarehousePermissionSchema, + serving_endpoint: servingEndpointPermissionSchema, + volume: volumePermissionSchema, + vector_search_index: vectorSearchIndexPermissionSchema, + uc_function: ucFunctionPermissionSchema, + uc_connection: ucConnectionPermissionSchema, + database: databasePermissionSchema, + postgres: postgresPermissionSchema, + genie_space: genieSpacePermissionSchema, + experiment: experimentPermissionSchema, + app: appPermissionSchema, +} as const; /** - * Resource type options (value + label) from schema $defs.resourceType.enum. + * Resource type options (value + label) from `resourceTypeSchema.options`. */ export function getResourceTypeOptions(): ResourceTypeOption[] { - if (cachedOptions) return cachedOptions; - const schema = loadSchema(); - const defs = schema?.$defs as Record | undefined; - const resourceType = defs?.resourceType as { enum?: string[] } | undefined; - const enumArr = resourceType?.enum; - if (!Array.isArray(enumArr)) { - cachedOptions = []; - return cachedOptions; - } - cachedOptions = enumArr.map((value) => ({ + return resourceTypeSchema.options.map((value) => ({ value, label: humanize(value), })); - return cachedOptions; } /** - * Permissions per resource type from schema resourceRequirement.allOf (if/then). + * Permissions per resource type, derived from each type's per-type permission + * schema's `.options` array. Order is weakest-to-strongest (matches Zod enum + * declaration order, which the original JSON Schema also preserved). */ export function getResourceTypePermissions(): Record { - if (cachedPermissions) return cachedPermissions; - const schema = loadSchema(); const out: Record = {}; - if (!schema?.$defs || typeof schema.$defs !== "object") { - cachedPermissions = out; - return out; - } - const defs = schema.$defs as Record; - const resourceReq = defs.resourceRequirement as - | Record - | undefined; - const allOf = resourceReq?.allOf as - | Array<{ - if?: { properties?: { type?: { const?: string } } }; - then?: { properties?: { permission?: { $ref?: string } } }; - }> - | undefined; - if (!Array.isArray(allOf)) { - cachedPermissions = out; - return out; - } - for (const branch of allOf) { - const typeConst = branch?.if?.properties?.type?.const; - const ref = branch?.then?.properties?.permission?.$ref; - if (typeof typeConst !== "string" || typeof ref !== "string") continue; - const refSegments = ref.replace(/^#\//, "").split("/"); - let def: unknown = schema; - for (const seg of refSegments) { - if (def == null || typeof def !== "object") break; - def = (def as Record)[seg]; - } - const enumArr = Array.isArray((def as { enum?: string[] })?.enum) - ? (def as { enum: string[] }).enum - : undefined; - if (enumArr?.length) out[typeConst] = enumArr; + for (const [type, schema] of Object.entries(PERMISSION_SCHEMAS_BY_TYPE)) { + out[type] = [...schema.options]; } - cachedPermissions = out; return out; } diff --git a/packages/shared/src/plugin.ts b/packages/shared/src/plugin.ts index 00ba3e217..73fd87519 100644 --- a/packages/shared/src/plugin.ts +++ b/packages/shared/src/plugin.ts @@ -96,11 +96,11 @@ export type PluginConstructor< /** * Manifest declaration for plugins. - * Extends the generated PluginManifest with a generic name parameter - * and uses JSONSchema7 for config.schema (the generated ConfigSchema + * Extends the schema-derived PluginManifest with a generic name parameter + * and uses JSONSchema7 for config.schema (the schema-inferred ConfigSchema * is too restrictive for plugin consumers). * - * @see {@link GeneratedPluginManifest} — generated base from plugin-manifest.schema.json + * @see {@link GeneratedPluginManifest} — Zod-inferred base from `schemas/manifest` * @see `packages/appkit/src/registry/types.ts` `PluginManifest` — strict appkit narrowing (enum types) */ export interface PluginManifest @@ -129,7 +129,7 @@ export interface PluginManifest * `ResourceRequirement` would force every plugin to thread the variant type * through `getResourceRequirements()` for no runtime benefit. * - * @see {@link GeneratedResourceRequirement} — schema-derived discriminated union from `schemas/manifest` + * @see `packages/shared/src/schemas/manifest.ts` `resourceRequirementSchema` — Zod discriminated union * @see `packages/appkit/src/registry/types.ts` `ResourceRequirement` — strict appkit narrowing (enum types) */ export interface ResourceRequirement { diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts index c522b9a0c..5206b01fe 100644 --- a/packages/shared/src/schemas/manifest.ts +++ b/packages/shared/src/schemas/manifest.ts @@ -1,10 +1,10 @@ /** * Zod-authoring module for AppKit plugin manifest schemas. * - * Single source of truth for plugin manifest contract. Mirrors the legacy - * hand-written JSON Schema files (plugin-manifest.schema.json, - * template-plugins.schema.json) faithfully so byte-equivalence/parity can - * be asserted before downstream consumers are migrated. + * Single source of truth for the plugin manifest contract. JSON Schema + * artifacts published at the docs URL are emitted from these schemas via + * `tools/generate-json-schema.ts` and live only in `docs/static/schemas/` + * (no package-internal copies). * * - Phase 2: cross-field constraints (cycle/dangling-reference checks, * `` placeholder, post-scaffold instruction non-empty) are @@ -948,14 +948,21 @@ export type ConfigSchema = z.infer; export type PostScaffoldStep = z.infer; export type PluginManifest = z.infer; export type Origin = z.infer; -export type TemplateFieldEntry = z.infer; -export type TemplateResourceRequirement = z.infer< +// Template-side types use `z.input` so callers can construct a TemplatePlugin +// from a parsed PluginManifest before the field-level origin transform runs. +// `writeManifest` parses every field through `templateFieldEntrySchema` at +// write-time, so the on-disk shape always has origin populated. The runtime +// invariant: origin is *always* present after sync writes; the type slot +// stays optional so the in-memory pipeline does not need to fabricate origin +// before assignment. +export type TemplateFieldEntry = z.input; +export type TemplateResourceRequirement = z.input< typeof templateResourceRequirementSchema >; -export type TemplatePlugin = z.infer; +export type TemplatePlugin = z.input; export type ScaffoldingFlag = z.infer; export type ScaffoldingRules = z.infer; export type ScaffoldingDescriptor = z.infer; -export type TemplatePluginsManifest = z.infer< +export type TemplatePluginsManifest = z.input< typeof templatePluginsManifestSchema >; diff --git a/packages/shared/src/schemas/plugin-manifest.generated.ts b/packages/shared/src/schemas/plugin-manifest.generated.ts deleted file mode 100644 index 2acdb4d1d..000000000 --- a/packages/shared/src/schemas/plugin-manifest.generated.ts +++ /dev/null @@ -1,338 +0,0 @@ -// AUTO-GENERATED from plugin-manifest.schema.json — do not edit. -// Run: pnpm exec tsx tools/generate-schema-types.ts -/** - * Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements(). - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "resourceRequirement". - */ -export type ResourceRequirement = { - type: ResourceType; - /** - * Human-readable label for UI/display only. Deduplication uses resourceKey, not alias. - */ - alias: string; - /** - * Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup. - */ - resourceKey: string; - /** - * Human-readable description of why this resource is needed - */ - description: string; - /** - * Required permission level. Validated per resource type by the allOf/if-then rules below. - */ - permission: string; - /** - * Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key). - */ - fields?: { - [k: string]: ResourceFieldEntry; - }; -}; -/** - * Type of Databricks resource - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "resourceType". - */ -export type ResourceType = - | "secret" - | "job" - | "sql_warehouse" - | "serving_endpoint" - | "volume" - | "vector_search_index" - | "uc_function" - | "uc_connection" - | "database" - | "postgres" - | "genie_space" - | "experiment" - | "app"; -/** - * Permission for secret resources (order: weakest to strongest) - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "secretPermission". - */ -export type SecretPermission = "READ" | "WRITE" | "MANAGE"; -/** - * Permission for job resources (order: weakest to strongest) - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "jobPermission". - */ -export type JobPermission = "CAN_VIEW" | "CAN_MANAGE_RUN" | "CAN_MANAGE"; -/** - * Permission for SQL warehouse resources (order: weakest to strongest) - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "sqlWarehousePermission". - */ -export type SqlWarehousePermission = "CAN_USE" | "CAN_MANAGE"; -/** - * Permission for serving endpoint resources (order: weakest to strongest) - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "servingEndpointPermission". - */ -export type ServingEndpointPermission = "CAN_VIEW" | "CAN_QUERY" | "CAN_MANAGE"; -/** - * Permission for Unity Catalog volume resources - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "volumePermission". - */ -export type VolumePermission = "READ_VOLUME" | "WRITE_VOLUME"; -/** - * Permission for vector search index resources - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "vectorSearchIndexPermission". - */ -export type VectorSearchIndexPermission = "SELECT"; -/** - * Permission for Unity Catalog function resources - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "ucFunctionPermission". - */ -export type UcFunctionPermission = "EXECUTE"; -/** - * Permission for Unity Catalog connection resources - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "ucConnectionPermission". - */ -export type UcConnectionPermission = "USE_CONNECTION"; -/** - * Permission for database resources - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "databasePermission". - */ -export type DatabasePermission = "CAN_CONNECT_AND_CREATE"; -/** - * Permission for Postgres resources - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "postgresPermission". - */ -export type PostgresPermission = "CAN_CONNECT_AND_CREATE"; -/** - * Permission for Genie Space resources (order: weakest to strongest) - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "genieSpacePermission". - */ -export type GenieSpacePermission = - | "CAN_VIEW" - | "CAN_RUN" - | "CAN_EDIT" - | "CAN_MANAGE"; -/** - * Permission for MLflow experiment resources (order: weakest to strongest) - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "experimentPermission". - */ -export type ExperimentPermission = "CAN_READ" | "CAN_EDIT" | "CAN_MANAGE"; -/** - * Permission for Databricks App resources - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "appPermission". - */ -export type AppPermission = "CAN_USE"; - -/** - * Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options. - */ -export interface PluginManifest { - /** - * Reference to the JSON Schema for validation - */ - $schema?: string; - /** - * Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens. - */ - name: string; - /** - * Human-readable display name for UI and CLI - */ - displayName: string; - /** - * Brief description of what the plugin does - */ - description: string; - /** - * Databricks resource requirements for this plugin - */ - resources: { - /** - * Resources that must be available for the plugin to function - */ - required: ResourceRequirement[]; - /** - * Resources that enhance functionality but are not mandatory - */ - optional: ResourceRequirement[]; - }; - /** - * Configuration schema for the plugin - */ - config?: { - schema?: ConfigSchema; - }; - /** - * Author name or organization - */ - author?: string; - /** - * Plugin version (semver format) - */ - version?: string; - /** - * URL to the plugin's source repository - */ - repository?: string; - /** - * Keywords for plugin discovery - */ - keywords?: string[]; - /** - * SPDX license identifier - */ - license?: string; - /** - * Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning). - */ - onSetupMessage?: string; - /** - * When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync. - */ - hidden?: boolean; - /** - * Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly. - */ - stability?: "beta" | "ga"; - /** - * Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order. - */ - postScaffold?: PostScaffoldStep[]; -} -/** - * Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key). - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "resourceFieldEntry". - */ -export interface ResourceFieldEntry { - /** - * Environment variable name for this field - */ - env?: string; - /** - * Human-readable description for this field - */ - description?: string; - /** - * When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation. - */ - bundleIgnore?: boolean; - /** - * Example values showing the expected format for this field - */ - examples?: string[]; - /** - * When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time. - */ - localOnly?: boolean; - /** - * Static value for this field. Used when no prompted or resolved value exists. - */ - value?: string; - /** - * Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow. - */ - resolve?: string; - discovery?: DiscoveryDescriptor; -} -/** - * Describes how the CLI discovers values for a resource field via a Databricks CLI command. - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "discoveryDescriptor". - */ -export interface DiscoveryDescriptor { - /** - * Databricks CLI command that lists resources. Must include placeholder. - */ - cliCommand: string; - /** - * jq-style path to the field used as the selected value (e.g., '.id', '.name'). - */ - selectField: string; - /** - * jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted. - */ - displayField?: string; - /** - * Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields. - */ - dependsOn?: string; - /** - * Single-value fast-path command that returns exactly one value, skipping interactive selection. - */ - shortcut?: string; -} -/** - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "configSchema". - */ -export interface ConfigSchema { - type: "object" | "array" | "string" | "number" | "boolean"; - properties?: { - [k: string]: ConfigSchemaProperty; - }; - items?: ConfigSchema; - required?: string[]; - additionalProperties?: boolean; -} -/** - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "configSchemaProperty". - */ -export interface ConfigSchemaProperty { - type: "object" | "array" | "string" | "number" | "boolean" | "integer"; - description?: string; - default?: unknown; - enum?: unknown[]; - properties?: { - [k: string]: ConfigSchemaProperty; - }; - items?: ConfigSchemaProperty; - minimum?: number; - maximum?: number; - minLength?: number; - maxLength?: number; - required?: string[]; -} -/** - * A post-scaffolding instruction shown to the user after project initialization. - * - * This interface was referenced by `PluginManifest`'s JSON-Schema - * via the `definition` "postScaffoldStep". - */ -export interface PostScaffoldStep { - /** - * Human-readable instruction for the user to follow after scaffolding. - */ - instruction: string; - /** - * Whether this step is required for the plugin to function correctly. - */ - required?: boolean; -} diff --git a/packages/shared/src/schemas/plugin-manifest.schema.json b/packages/shared/src/schemas/plugin-manifest.schema.json deleted file mode 100644 index 45fea2723..000000000 --- a/packages/shared/src/schemas/plugin-manifest.schema.json +++ /dev/null @@ -1,553 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json", - "title": "AppKit Plugin Manifest", - "description": "Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options.", - "type": "object", - "required": ["name", "displayName", "description", "resources"], - "properties": { - "$schema": { - "type": "string", - "description": "Reference to the JSON Schema for validation" - }, - "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", - "examples": ["analytics", "server", "my-custom-plugin"] - }, - "displayName": { - "type": "string", - "minLength": 1, - "description": "Human-readable display name for UI and CLI", - "examples": ["Analytics Plugin", "Server Plugin"] - }, - "description": { - "type": "string", - "minLength": 1, - "description": "Brief description of what the plugin does", - "examples": ["SQL query execution against Databricks SQL Warehouses"] - }, - "resources": { - "type": "object", - "required": ["required", "optional"], - "description": "Databricks resource requirements for this plugin", - "properties": { - "required": { - "type": "array", - "description": "Resources that must be available for the plugin to function", - "items": { - "$ref": "#/$defs/resourceRequirement" - } - }, - "optional": { - "type": "array", - "description": "Resources that enhance functionality but are not mandatory", - "items": { - "$ref": "#/$defs/resourceRequirement" - } - } - }, - "additionalProperties": false - }, - "config": { - "type": "object", - "description": "Configuration schema for the plugin", - "properties": { - "schema": { - "$ref": "#/$defs/configSchema" - } - }, - "additionalProperties": false - }, - "author": { - "type": "string", - "description": "Author name or organization" - }, - "version": { - "type": "string", - "pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?$", - "description": "Plugin version (semver format)", - "examples": ["1.0.0", "2.1.0-beta.1"] - }, - "repository": { - "type": "string", - "format": "uri", - "description": "URL to the plugin's source repository" - }, - "keywords": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Keywords for plugin discovery" - }, - "license": { - "type": "string", - "description": "SPDX license identifier", - "examples": ["Apache-2.0", "MIT"] - }, - "onSetupMessage": { - "type": "string", - "description": "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning)." - }, - "hidden": { - "type": "boolean", - "default": false, - "description": "When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync." - }, - "stability": { - "type": "string", - "enum": ["beta", "ga"], - "default": "ga", - "description": "Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly." - }, - "postScaffold": { - "type": "array", - "items": { - "$ref": "#/$defs/postScaffoldStep" - }, - "description": "Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order." - } - }, - "additionalProperties": false, - "$defs": { - "resourceType": { - "type": "string", - "enum": [ - "secret", - "job", - "sql_warehouse", - "serving_endpoint", - "volume", - "vector_search_index", - "uc_function", - "uc_connection", - "database", - "postgres", - "genie_space", - "experiment", - "app" - ], - "description": "Type of Databricks resource" - }, - "secretPermission": { - "type": "string", - "enum": ["READ", "WRITE", "MANAGE"], - "description": "Permission for secret resources (order: weakest to strongest)" - }, - "jobPermission": { - "type": "string", - "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"], - "description": "Permission for job resources (order: weakest to strongest)" - }, - "sqlWarehousePermission": { - "type": "string", - "enum": ["CAN_USE", "CAN_MANAGE"], - "description": "Permission for SQL warehouse resources (order: weakest to strongest)" - }, - "servingEndpointPermission": { - "type": "string", - "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"], - "description": "Permission for serving endpoint resources (order: weakest to strongest)" - }, - "volumePermission": { - "type": "string", - "enum": ["READ_VOLUME", "WRITE_VOLUME"], - "description": "Permission for Unity Catalog volume resources" - }, - "vectorSearchIndexPermission": { - "type": "string", - "enum": ["SELECT"], - "description": "Permission for vector search index resources" - }, - "ucFunctionPermission": { - "type": "string", - "enum": ["EXECUTE"], - "description": "Permission for Unity Catalog function resources" - }, - "ucConnectionPermission": { - "type": "string", - "enum": ["USE_CONNECTION"], - "description": "Permission for Unity Catalog connection resources" - }, - "databasePermission": { - "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"], - "description": "Permission for database resources" - }, - "postgresPermission": { - "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"], - "description": "Permission for Postgres resources" - }, - "genieSpacePermission": { - "type": "string", - "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"], - "description": "Permission for Genie Space resources (order: weakest to strongest)" - }, - "experimentPermission": { - "type": "string", - "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"], - "description": "Permission for MLflow experiment resources (order: weakest to strongest)" - }, - "appPermission": { - "type": "string", - "enum": ["CAN_USE"], - "description": "Permission for Databricks App resources" - }, - "resourceFieldEntry": { - "type": "object", - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", - "properties": { - "env": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9_]*$", - "description": "Environment variable name for this field", - "examples": ["DATABRICKS_CACHE_INSTANCE", "SECRET_SCOPE"] - }, - "description": { - "type": "string", - "description": "Human-readable description for this field" - }, - "bundleIgnore": { - "type": "boolean", - "default": false, - "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation." - }, - "examples": { - "type": "array", - "items": { "type": "string" }, - "description": "Example values showing the expected format for this field" - }, - "localOnly": { - "type": "boolean", - "default": false, - "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time." - }, - "value": { - "type": "string", - "description": "Static value for this field. Used when no prompted or resolved value exists." - }, - "resolve": { - "type": "string", - "pattern": "^[a-z_]+:[a-zA-Z]+$", - "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow." - }, - "discovery": { - "$ref": "#/$defs/discoveryDescriptor" - } - }, - "additionalProperties": false - }, - "resourceRequirement": { - "type": "object", - "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().", - "required": ["type", "alias", "resourceKey", "description", "permission"], - "properties": { - "type": { - "$ref": "#/$defs/resourceType" - }, - "alias": { - "type": "string", - "minLength": 1, - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "examples": ["SQL Warehouse", "Secret", "Vector search index"] - }, - "resourceKey": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "examples": ["sql-warehouse", "database", "secret"] - }, - "description": { - "type": "string", - "minLength": 1, - "description": "Human-readable description of why this resource is needed" - }, - "permission": { - "type": "string", - "description": "Required permission level. Validated per resource type by the allOf/if-then rules below." - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/resourceFieldEntry" - }, - "minProperties": 1, - "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." - } - }, - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { "type": { "const": "secret" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/secretPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "job" } }, - "required": ["type"] - }, - "then": { - "properties": { "permission": { "$ref": "#/$defs/jobPermission" } } - } - }, - { - "if": { - "properties": { "type": { "const": "sql_warehouse" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/sqlWarehousePermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "serving_endpoint" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/servingEndpointPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "volume" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/volumePermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "vector_search_index" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/vectorSearchIndexPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_function" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/ucFunctionPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_connection" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/ucConnectionPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "database" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/databasePermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "postgres" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/postgresPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "genie_space" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/genieSpacePermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "experiment" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { "$ref": "#/$defs/experimentPermission" } - } - } - }, - { - "if": { - "properties": { "type": { "const": "app" } }, - "required": ["type"] - }, - "then": { - "properties": { "permission": { "$ref": "#/$defs/appPermission" } } - } - } - ] - }, - "configSchemaProperty": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["object", "array", "string", "number", "boolean", "integer"] - }, - "description": { - "type": "string" - }, - "default": {}, - "enum": { - "type": "array" - }, - "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/configSchemaProperty" - } - }, - "items": { - "$ref": "#/$defs/configSchemaProperty" - }, - "minimum": { - "type": "number" - }, - "maximum": { - "type": "number" - }, - "minLength": { - "type": "integer", - "minimum": 0 - }, - "maxLength": { - "type": "integer", - "minimum": 0 - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "configSchema": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["object", "array", "string", "number", "boolean"] - }, - "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/configSchemaProperty" - } - }, - "items": { - "$ref": "#/$defs/configSchema" - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - }, - "additionalProperties": { - "type": "boolean" - } - } - }, - "discoveryDescriptor": { - "type": "object", - "description": "Describes how the CLI discovers values for a resource field via a Databricks CLI command.", - "required": ["cliCommand", "selectField"], - "properties": { - "cliCommand": { - "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "examples": [ - "databricks warehouses list --profile --output json" - ] - }, - "selectField": { - "type": "string", - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "examples": [".id", ".name", ".catalog_name"] - }, - "displayField": { - "type": "string", - "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", - "examples": [".name", ".display_name"] - }, - "dependsOn": { - "type": "string", - "description": "Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields.", - "examples": ["branch", "catalog"] - }, - "shortcut": { - "type": "string", - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", - "examples": [ - "databricks warehouses get --profile --output json" - ] - } - }, - "additionalProperties": false - }, - "postScaffoldStep": { - "type": "object", - "description": "A post-scaffolding instruction shown to the user after project initialization.", - "required": ["instruction"], - "properties": { - "instruction": { - "type": "string", - "description": "Human-readable instruction for the user to follow after scaffolding." - }, - "required": { - "type": "boolean", - "default": true, - "description": "Whether this step is required for the plugin to function correctly." - } - }, - "additionalProperties": false - } - } -} diff --git a/packages/shared/src/schemas/template-plugins.schema.json b/packages/shared/src/schemas/template-plugins.schema.json deleted file mode 100644 index 20c459785..000000000 --- a/packages/shared/src/schemas/template-plugins.schema.json +++ /dev/null @@ -1,455 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://databricks.github.io/appkit/schemas/template-plugins.schema.json", - "title": "AppKit Template Plugins Manifest", - "description": "Aggregated plugin manifest for AppKit templates. Read by Databricks CLI during init to discover available plugins and their resource requirements.", - "type": "object", - "required": ["version", "plugins"], - "properties": { - "$schema": { - "type": "string", - "description": "Reference to the JSON Schema for validation" - }, - "version": { - "type": "string", - "enum": ["1.0", "1.1", "2.0"], - "description": "Schema version for the template plugins manifest" - }, - "plugins": { - "type": "object", - "description": "Map of plugin name to plugin manifest with package source", - "additionalProperties": { - "$ref": "#/$defs/templatePlugin" - } - }, - "scaffolding": { - "$ref": "#/$defs/scaffoldingDescriptor", - "description": "Describes the scaffolding command and its configuration for project initialization." - } - }, - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { "version": { "const": "2.0" } }, - "required": ["version"] - }, - "then": { - "required": ["version", "plugins", "scaffolding"] - } - } - ], - "$defs": { - "templatePlugin": { - "type": "object", - "required": [ - "name", - "displayName", - "description", - "package", - "resources" - ], - "description": "Plugin manifest with package source information", - "properties": { - "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", - "examples": ["analytics", "server", "my-custom-plugin"] - }, - "displayName": { - "type": "string", - "minLength": 1, - "description": "Human-readable display name for UI and CLI", - "examples": ["Analytics Plugin", "Server Plugin"] - }, - "description": { - "type": "string", - "minLength": 1, - "description": "Brief description of what the plugin does", - "examples": ["SQL query execution against Databricks SQL Warehouses"] - }, - "package": { - "type": "string", - "minLength": 1, - "description": "NPM package name that provides this plugin", - "examples": ["@databricks/appkit", "@my-org/custom-plugin"] - }, - "requiredByTemplate": { - "type": "boolean", - "default": false, - "description": "When true, this plugin is required by the template and cannot be deselected during CLI init. The user will only be prompted to configure its resources. When absent or false, the plugin is optional and the user can choose whether to include it." - }, - "onSetupMessage": { - "type": "string", - "description": "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning)." - }, - "stability": { - "type": "string", - "enum": ["beta", "ga"], - "default": "ga", - "description": "Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver." - }, - "postScaffold": { - "type": "array", - "items": { - "$ref": "plugin-manifest.schema.json#/$defs/postScaffoldStep" - }, - "description": "Ordered list of post-scaffolding instructions propagated from the plugin manifest." - }, - "resources": { - "type": "object", - "required": ["required", "optional"], - "description": "Databricks resource requirements for this plugin", - "properties": { - "required": { - "type": "array", - "description": "Resources that must be available for the plugin to function", - "items": { - "$ref": "#/$defs/resourceRequirement" - } - }, - "optional": { - "type": "array", - "description": "Resources that enhance functionality but are not mandatory", - "items": { - "$ref": "#/$defs/resourceRequirement" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "resourceType": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceType" - }, - "resourceFieldEntry": { - "type": "object", - "description": "Extends the plugin manifest resourceFieldEntry with a computed origin field for template manifests.", - "properties": { - "env": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9_]*$", - "description": "Environment variable name for this field" - }, - "description": { - "type": "string", - "description": "Human-readable description for this field" - }, - "bundleIgnore": { - "type": "boolean", - "default": false, - "description": "When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation." - }, - "examples": { - "type": "array", - "items": { "type": "string" }, - "description": "Example values showing the expected format for this field" - }, - "localOnly": { - "type": "boolean", - "default": false, - "description": "When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time." - }, - "value": { - "type": "string", - "description": "Static value for this field." - }, - "resolve": { - "type": "string", - "pattern": "^[a-z_]+:[a-zA-Z]+$", - "description": "Named resolver prefixed by resource type (e.g., 'postgres:host')." - }, - "discovery": { - "$ref": "plugin-manifest.schema.json#/$defs/discoveryDescriptor", - "description": "How the CLI discovers values for this field via a Databricks CLI command." - }, - "origin": { - "$ref": "#/$defs/origin" - } - }, - "additionalProperties": false - }, - "resourceRequirement": { - "type": "object", - "description": "Resource requirement with template-specific field entries (includes computed origin).", - "required": ["type", "alias", "resourceKey", "description", "permission"], - "properties": { - "type": { - "$ref": "plugin-manifest.schema.json#/$defs/resourceType" - }, - "alias": { - "type": "string", - "minLength": 1, - "description": "Human-readable label for UI/display only." - }, - "resourceKey": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Stable key for machine use: deduplication, env naming, composite keys." - }, - "description": { - "type": "string", - "minLength": 1, - "description": "Human-readable description of why this resource is needed" - }, - "permission": { - "type": "string", - "description": "Required permission level." - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/resourceFieldEntry" - }, - "minProperties": 1, - "description": "Map of field name to field entry with computed origin." - } - }, - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { "type": { "const": "secret" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/secretPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "job" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/jobPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "sql_warehouse" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/sqlWarehousePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "serving_endpoint" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/servingEndpointPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "volume" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/volumePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "vector_search_index" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/vectorSearchIndexPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_function" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/ucFunctionPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "uc_connection" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/ucConnectionPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "database" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/databasePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "postgres" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/postgresPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "genie_space" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/genieSpacePermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "experiment" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/experimentPermission" - } - } - } - }, - { - "if": { - "properties": { "type": { "const": "app" } }, - "required": ["type"] - }, - "then": { - "properties": { - "permission": { - "$ref": "plugin-manifest.schema.json#/$defs/appPermission" - } - } - } - } - ] - }, - "origin": { - "type": "string", - "enum": ["user", "platform", "static", "cli"], - "description": "How the field value is determined. Computed during sync, not authored by plugin developers." - }, - "scaffoldingFlag": { - "type": "object", - "description": "A flag for the scaffolding command.", - "required": ["description"], - "properties": { - "description": { - "type": "string", - "description": "Human-readable description of the flag." - }, - "required": { - "type": "boolean", - "default": false, - "description": "Whether this flag is required." - }, - "pattern": { - "type": "string", - "description": "Regex pattern for validating the flag value." - }, - "default": { - "type": "string", - "description": "Default value for this flag." - } - }, - "additionalProperties": false - }, - "scaffoldingRules": { - "type": "object", - "description": "Structured rules for scaffolding agents.", - "properties": { - "never": { - "type": "array", - "items": { "type": "string" }, - "description": "Actions the scaffolding agent must never perform." - }, - "must": { - "type": "array", - "items": { "type": "string" }, - "description": "Actions the scaffolding agent must always perform." - } - }, - "additionalProperties": false - }, - "scaffoldingDescriptor": { - "type": "object", - "description": "Describes the scaffolding command, flags, and rules for project initialization.", - "required": ["command"], - "properties": { - "command": { - "type": "string", - "description": "The scaffolding command (e.g., 'databricks apps init')." - }, - "flags": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scaffoldingFlag" - }, - "description": "Map of flag name to flag descriptor." - }, - "rules": { - "$ref": "#/$defs/scaffoldingRules", - "description": "Structured rules for scaffolding agents." - } - }, - "additionalProperties": false - } - } -} diff --git a/packages/shared/tsdown.config.ts b/packages/shared/tsdown.config.ts index d118f7ab9..9db6274a7 100644 --- a/packages/shared/tsdown.config.ts +++ b/packages/shared/tsdown.config.ts @@ -21,14 +21,4 @@ export default defineConfig({ exports: { devExports: "development", }, - copy: [ - { - from: "src/schemas/plugin-manifest.schema.json", - to: "dist/schemas", - }, - { - from: "src/schemas/template-plugins.schema.json", - to: "dist/schemas", - }, - ], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08b94b3e8..cab9d9682 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,6 @@ importers: jsdom: specifier: 27.0.0 version: 27.0.0(bufferutil@4.0.9)(postcss@8.5.6) - json-schema-to-typescript: - specifier: 15.0.4 - version: 15.0.4 knip: specifier: 5.86.0 version: 5.86.0(@types/node@24.7.2)(typescript@5.9.3) @@ -542,12 +539,6 @@ importers: '@standard-schema/spec': specifier: 1.1.0 version: 1.1.0 - ajv: - specifier: 8.17.1 - version: 8.17.1 - ajv-formats: - specifier: 3.0.1 - version: 3.0.1(ajv@8.17.1) commander: specifier: 12.1.0 version: 12.1.0 @@ -676,10 +667,6 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@apidevtools/json-schema-ref-parser@11.9.3': - resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} - engines: {node: '>= 16'} - '@appthreat/atom-common@1.1.0': resolution: {integrity: sha512-vGZpJdy9e0l83o8XwDAsQUDpkMVzR4DLb7mbi5npu8Nz/R+2m5oyHlpBGuLYBKm5+KkjTInXP9eNTcMa0Mn3Uw==} @@ -2616,9 +2603,6 @@ packages: '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} - '@jsdevtools/ono@7.1.3': - resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - '@jsonjoy.com/base64@1.1.2': resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} @@ -4975,9 +4959,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash@4.17.24': - resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} - '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -8181,11 +8162,6 @@ packages: resolution: {integrity: sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==} engines: {node: ^20.17.0 || >=22.9.0} - json-schema-to-typescript@15.0.4: - resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} - engines: {node: '>=16.0.0'} - hasBin: true - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -9851,11 +9827,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} - hasBin: true - pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} @@ -12100,12 +12071,6 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 - '@apidevtools/json-schema-ref-parser@11.9.3': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - js-yaml: 4.1.1 - '@appthreat/atom-common@1.1.0': optional: true @@ -14839,8 +14804,6 @@ snapshots: '@js-sdsl/ordered-map@4.4.2': {} - '@jsdevtools/ono@7.1.3': {} - '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -17324,8 +17287,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/lodash@4.17.24': {} - '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -17814,10 +17775,6 @@ snapshots: optionalDependencies: ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -20961,18 +20918,6 @@ snapshots: json-parse-even-better-errors@5.0.0: {} - json-schema-to-typescript@15.0.4: - dependencies: - '@apidevtools/json-schema-ref-parser': 11.9.3 - '@types/json-schema': 7.0.15 - '@types/lodash': 4.17.24 - is-glob: 4.0.3 - js-yaml: 4.1.1 - lodash: 4.17.21 - minimist: 1.2.8 - prettier: 3.8.1 - tinyglobby: 0.2.15 - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -22986,8 +22931,6 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.8.1: {} - pretty-error@4.0.0: dependencies: lodash: 4.17.21 diff --git a/tools/generate-plugin-entries.ts b/tools/generate-plugin-entries.ts index f7517defa..c2aa6ec7a 100644 --- a/tools/generate-plugin-entries.ts +++ b/tools/generate-plugin-entries.ts @@ -145,8 +145,7 @@ function main(): void { fs.writeFileSync(BETA_OUT, renderBarrel(beta), "utf-8"); // Self-format so a fresh `pnpm build` doesn't leave the generated // barrels dirty against biome's canonical formatting (matches the - // pattern set by tools/generate-schema-types.ts and - // tools/generate-registry-types.ts after PR #324). + // pattern set by tools/generate-registry-types.ts after PR #324). formatWithBiome(GA_OUT); formatWithBiome(BETA_OUT); diff --git a/tools/generate-registry-types.ts b/tools/generate-registry-types.ts index 531802260..0a769d790 100644 --- a/tools/generate-registry-types.ts +++ b/tools/generate-registry-types.ts @@ -1,109 +1,109 @@ /** * Generates registry types (ResourceType enum, permission types, hierarchy) from - * plugin-manifest.schema.json. Single source of truth for resource types and permissions. + * the canonical Zod schemas in `packages/shared/src/schemas/manifest.ts`. Single + * source of truth for resource types and permissions. + * + * Phase 5: replaced JSON-Schema runtime read with direct Zod imports. The + * resource-type enum + per-type permission enums are extracted via Zod 4's + * native `.options` accessor on enum schemas. * * Run from repo root: pnpm exec tsx tools/generate-registry-types.ts */ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { + appPermissionSchema, + databasePermissionSchema, + experimentPermissionSchema, + genieSpacePermissionSchema, + jobPermissionSchema, + postgresPermissionSchema, + resourceTypeSchema, + secretPermissionSchema, + servingEndpointPermissionSchema, + sqlWarehousePermissionSchema, + ucConnectionPermissionSchema, + ucFunctionPermissionSchema, + vectorSearchIndexPermissionSchema, + volumePermissionSchema, +} from "../packages/shared/src/schemas/manifest.ts"; import { formatWithBiome } from "./format-with-biome.ts"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = path.join(__dirname, ".."); -const SCHEMA_PATH = path.join( - REPO_ROOT, - "packages/shared/src/schemas/plugin-manifest.schema.json", -); const OUT_PATH = path.join( REPO_ROOT, "packages/appkit/src/registry/types.generated.ts", ); -interface SchemaDefs { - resourceType?: { enum?: string[] }; - resourceRequirement?: { - allOf?: Array<{ - if?: { properties?: { type?: { const?: string } } }; - then?: { properties?: { permission?: { $ref?: string } } }; - }>; - }; - [key: string]: unknown; -} - -function loadSchema(): Record { - const raw = fs.readFileSync(SCHEMA_PATH, "utf-8"); - return JSON.parse(raw) as Record; -} - /** value "sql_warehouse" -> "SQL_WAREHOUSE" */ function toEnumKey(value: string): string { return value.toUpperCase().replace(/-/g, "_"); } -/** def key "secretPermission" -> "SecretPermission" */ -function toPermissionTypeName(defKey: string): string { - return defKey.charAt(0).toUpperCase() + defKey.slice(1); +/** type "sql_warehouse" -> "SqlWarehousePermission" */ +function toPermissionTypeName(type: string): string { + return ( + type + .split("_") + .map((p) => p.charAt(0).toUpperCase() + p.slice(1)) + .join("") + "Permission" + ); } -function generate(schema: Record): string { - const defs = (schema.$defs ?? {}) as SchemaDefs; - const resourceType = defs.resourceType; - const resourceReq = defs.resourceRequirement; - const allOf = resourceReq?.allOf ?? []; - - const resourceTypes: string[] = resourceType?.enum ?? []; - const typeToPermissionRef: Array<{ type: string; ref: string }> = []; - - for (const branch of allOf) { - const typeConst = branch?.if?.properties?.type?.const; - const ref = branch?.then?.properties?.permission?.$ref; - if (typeof typeConst === "string" && typeof ref === "string") { - typeToPermissionRef.push({ type: typeConst, ref }); - } - } - - // Resolve ref to def key: "#/$defs/secretPermission" -> "secretPermission" - const refToDefKey = (ref: string): string => { - const segments = ref.replace(/^#\//, "").split("/"); - return segments[segments.length - 1] ?? ""; - }; - - const defKeyToPermissionTypeName: Record = {}; - const typeToPermissions: Record = {}; - - for (const { type, ref } of typeToPermissionRef) { - const defKey = refToDefKey(ref); - const permDef = defs[defKey] as { enum?: string[] } | undefined; - const enumArr = permDef?.enum ?? []; - if (enumArr.length > 0) { - defKeyToPermissionTypeName[defKey] = toPermissionTypeName(defKey); - // Schema enum order is weakest to strongest (see schema descriptions) - typeToPermissions[type] = [...enumArr]; - } - } +/** + * Per-type permission enum schemas, keyed by the resource type literal. + * Mirrors `schema-resources.ts`'s `PERMISSION_SCHEMAS_BY_TYPE`. Add an entry + * when extending `resourceTypeSchema`. + */ +const PERMISSION_SCHEMAS_BY_TYPE = { + secret: secretPermissionSchema, + job: jobPermissionSchema, + sql_warehouse: sqlWarehousePermissionSchema, + serving_endpoint: servingEndpointPermissionSchema, + volume: volumePermissionSchema, + vector_search_index: vectorSearchIndexPermissionSchema, + uc_function: ucFunctionPermissionSchema, + uc_connection: ucConnectionPermissionSchema, + database: databasePermissionSchema, + postgres: postgresPermissionSchema, + genie_space: genieSpacePermissionSchema, + experiment: experimentPermissionSchema, + app: appPermissionSchema, +} as const; + +function generate(): string { + const resourceTypes = [...resourceTypeSchema.options]; const lines: string[] = [ - "// AUTO-GENERATED from packages/shared/src/schemas/plugin-manifest.schema.json", + "// AUTO-GENERATED from packages/shared/src/schemas/manifest.ts (Zod canonical).", "// Do not edit. Run: pnpm exec tsx tools/generate-registry-types.ts", "", - "/** Resource types from schema $defs.resourceType.enum */", + "/** Resource types from resourceTypeSchema.options */", "export enum ResourceType {", ...resourceTypes.map((v) => ` ${toEnumKey(v)} = "${v}",`), "}", "", "// ============================================================================", - "// Permissions per resource type (from schema permission $defs)", + "// Permissions per resource type (from per-type permission enum schemas)", "// ============================================================================", ]; const permissionTypeNames: string[] = []; - for (const { type, ref } of typeToPermissionRef) { - const defKey = refToDefKey(ref); - const typeName = defKeyToPermissionTypeName[defKey]; - if (!typeName) continue; - const perms = typeToPermissions[type]; - if (!perms?.length) continue; + const typeToPermissions: Record = {}; + + for (const type of resourceTypes) { + const schema = PERMISSION_SCHEMAS_BY_TYPE[type]; + if (!schema) { + throw new Error( + `generate-registry-types: missing permission schema for resource type '${type}'. ` + + `Add it to PERMISSION_SCHEMAS_BY_TYPE in tools/generate-registry-types.ts.`, + ); + } + const perms = [...schema.options]; + typeToPermissions[type] = perms; + const typeName = toPermissionTypeName(type); permissionTypeNames.push(typeName); const union = perms.map((p) => `"${p}"`).join(" | "); lines.push(`/** Permissions for ${toEnumKey(type)} resources */`); @@ -115,9 +115,7 @@ function generate(schema: Record): string { "/** Union of all possible permission levels across all resource types. */", ); lines.push( - "export type ResourcePermission =\n | " + - permissionTypeNames.join("\n | ") + - ";", + `export type ResourcePermission =\n | ${permissionTypeNames.join("\n | ")};`, ); lines.push(""); lines.push( @@ -146,8 +144,7 @@ function generate(schema: Record): string { } function main(): void { - const schema = loadSchema(); - const out = generate(schema); + const out = generate(); fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true }); fs.writeFileSync(OUT_PATH, out, "utf-8"); formatWithBiome(OUT_PATH); diff --git a/tools/generate-schema-types.ts b/tools/generate-schema-types.ts deleted file mode 100644 index 18360fb2f..000000000 --- a/tools/generate-schema-types.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Generates TypeScript interfaces from plugin-manifest.schema.json using - * json-schema-to-typescript. Single source of truth for structural types - * (ResourceFieldEntry, ResourceRequirement, PluginManifest). - * - * Run from repo root: pnpm exec tsx tools/generate-schema-types.ts - */ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { compileFromFile } from "json-schema-to-typescript"; -import { formatWithBiome } from "./format-with-biome.ts"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const REPO_ROOT = path.join(__dirname, ".."); -const SCHEMA_PATH = path.join( - REPO_ROOT, - "packages/shared/src/schemas/plugin-manifest.schema.json", -); -const OUT_PATH = path.join( - REPO_ROOT, - "packages/shared/src/schemas/plugin-manifest.generated.ts", -); - -const BANNER = `// AUTO-GENERATED from plugin-manifest.schema.json — do not edit. -// Run: pnpm exec tsx tools/generate-schema-types.ts -`; - -async function main(): Promise { - const raw = await compileFromFile(SCHEMA_PATH, { - bannerComment: "", - additionalProperties: false, - strictIndexSignatures: false, - unreachableDefinitions: true, - format: false, - style: { semi: true, singleQuote: false }, - // Rename the root type (derived from schema title "AppKit Plugin Manifest") - // to "PluginManifest" for ergonomic imports. - customName: (schema) => - schema.title === "AppKit Plugin Manifest" ? "PluginManifest" : undefined, - }); - - // Post-processing: work around json-schema-to-typescript limitations that - // have no config options. Track upstream: https://github.com/bcherny/json-schema-to-typescript/issues/428 - // allOf/if-then produces `{ [k: string]: unknown } & { … }` — strip the index-signature part. - const output = raw.replace(/\{\s*\[k: string\]: unknown;?\s*\}\s*&\s*/g, ""); - - const result = BANNER + output; - - fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true }); - fs.writeFileSync(OUT_PATH, result, "utf-8"); - formatWithBiome(OUT_PATH); - console.log("Wrote", OUT_PATH); -} - -main(); From 380749752f96bd30f5ffba306be59d11fe19f865 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 22:00:38 +0200 Subject: [PATCH 13/17] feat: harden scaffolding directives (phase 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final phase of the manifest zod refactor. Three small, targeted changes to the scaffolding descriptor: 1. Rule items are constrained to ≤ 120 chars via z.string().max(120) on both rules.never[] and rules.must[]. The schema can't validate prose content but it can stop a directive from growing into a paragraph — enforces the "short directive" intent at the only boundary the schema can express. 2. TEMPLATE_SCAFFOLDING moves from sync.ts into the schema module. The constant lives next to the schemas it conforms to, with a `satisfies z.infer<typeof scaffoldingDescriptorSchema>` clause for compile-time validation against the input shape. sync.ts imports it. 3. New MUST rule directive describing volume parent-context handling: "When discovering volume resources, prompt the user for catalog and schema before listing volumes." The kind variant for `volume` doesn't model catalog/schema parents in the schema (per PRD design decision #7 — hierarchical context as MUST rule, not schema structure); this directive carries the requirement to LLM scaffolding agents instead. Tests added under "scaffolding rule item maxLength (Phase 6)": - never[]/must[] items exceeding 120 chars produce errors with the right path and message. - 120 chars exactly is accepted (≤ semantics). - A mixed-length array flags only the offending entry. - TEMPLATE_SCAFFOLDING parses cleanly against scaffoldingDescriptorSchema. - The synced template manifest carries the new volume MUST rule string. template/appkit.plugins.json regenerated by sync:template — the new rule string is now in scaffolding.rules.must. Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../src/cli/commands/plugin/sync/sync.ts | 40 +-------- .../plugin/validate/validate-manifest.test.ts | 87 +++++++++++++++++++ packages/shared/src/schemas/manifest.ts | 66 +++++++++++++- template/appkit.plugins.json | 3 +- 4 files changed, 155 insertions(+), 41 deletions(-) diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.ts b/packages/shared/src/cli/commands/plugin/sync/sync.ts index 372f7806e..8371856dd 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.ts @@ -2,7 +2,10 @@ import fs from "node:fs"; import path from "node:path"; import { Lang, parse, type SgNode } from "@ast-grep/napi"; import { Command } from "commander"; -import { templateFieldEntrySchema } from "../../../../schemas/manifest"; +import { + TEMPLATE_SCAFFOLDING, + templateFieldEntrySchema, +} from "../../../../schemas/manifest"; import { loadManifestFromFile, type ResolvedManifest, @@ -10,7 +13,6 @@ import { } from "../manifest-resolve"; import type { PluginManifest, - ScaffoldingDescriptor, TemplatePlugin, TemplatePluginsManifest, } from "../manifest-types"; @@ -121,40 +123,6 @@ const SERVER_FILE_CANDIDATES = ["server/server.ts", "server/index.ts"]; */ const CONVENTIONAL_LOCAL_PLUGIN_DIRS = ["plugins", "server"]; -/** - * Scaffolding descriptor for the `databricks apps init` command. - * Included in v2.0 template manifests to guide scaffolding agents. - */ -const TEMPLATE_SCAFFOLDING: ScaffoldingDescriptor = { - command: "databricks apps init", - flags: { - "--template-dir": { - description: "Path to the template directory containing the app scaffold", - required: true, - }, - "--config-dir": { - description: "Path to the output directory for the initialized app", - required: true, - }, - "--profile": { - description: "Databricks CLI profile to use for authentication", - required: false, - }, - }, - rules: { - never: [ - "Modify files inside the template directory", - "Skip resource configuration prompts", - "Hardcode workspace-specific values in template files", - ], - must: [ - "Use the template manifest (appkit.plugins.json) as the source of truth for available plugins", - "Respect requiredByTemplate flags when presenting plugin selection", - "Generate .env files with all required environment variables from selected plugins", - ], - }, -}; - /** * Find the server entry file by checking candidate paths in order. * diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts index 78026df76..d66402446 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts @@ -1,4 +1,8 @@ import { describe, expect, it } from "vitest"; +import { + scaffoldingDescriptorSchema, + TEMPLATE_SCAFFOLDING, +} from "../../../../schemas/manifest"; import { detectSchemaType, formatValidationErrors, @@ -765,4 +769,87 @@ describe("validate-manifest", () => { expect(formatted).toMatch(/type|discriminator/i); }); }); + + describe("scaffolding rule item maxLength (Phase 6)", () => { + function buildTemplateManifestWithRules(rules: { + never?: string[]; + must?: string[]; + }): unknown { + return { + $schema: + "https://databricks.github.io/appkit/schemas/template-plugins.schema.json", + version: "2.0", + plugins: {}, + scaffolding: { + command: "databricks apps init", + rules, + }, + }; + } + + // 121-char string — one past the boundary. + const TOO_LONG = "x".repeat(121); + // 120-char string — at the boundary. + const AT_MAX = "x".repeat(120); + + it("rejects a never[] item exceeding 120 characters", () => { + const result = validateTemplateManifest( + buildTemplateManifestWithRules({ never: [TOO_LONG] }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("scaffolding.rules.never[0]"); + expect(formatted).toMatch(/120/); + }); + + it("rejects a must[] item exceeding 120 characters", () => { + const result = validateTemplateManifest( + buildTemplateManifestWithRules({ must: [TOO_LONG] }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("scaffolding.rules.must[0]"); + expect(formatted).toMatch(/120/); + }); + + it("accepts rule items at exactly 120 characters", () => { + const result = validateTemplateManifest( + buildTemplateManifestWithRules({ + never: [AT_MAX], + must: [AT_MAX], + }), + ); + expect(result.valid).toBe(true); + }); + + it("flags only the offending entry in a mixed-length array", () => { + const result = validateTemplateManifest( + buildTemplateManifestWithRules({ + must: ["short directive", TOO_LONG, "another short one"], + }), + ); + expect(result.valid).toBe(false); + const paths = (result.errors ?? []).map((e) => e.path); + expect(paths).toContain("scaffolding.rules.must[1]"); + expect(paths).not.toContain("scaffolding.rules.must[0]"); + expect(paths).not.toContain("scaffolding.rules.must[2]"); + }); + + it("TEMPLATE_SCAFFOLDING parses against scaffoldingDescriptorSchema", () => { + // The exported constant must validate against its own schema, including + // the maxLength ceiling on each rule item. + const parsed = scaffoldingDescriptorSchema.parse(TEMPLATE_SCAFFOLDING); + expect(parsed.command).toBe("databricks apps init"); + expect(parsed.rules?.must).toBeDefined(); + expect(parsed.rules?.never).toBeDefined(); + }); + + it("TEMPLATE_SCAFFOLDING.rules.must includes the volume parent-walk MUST directive", () => { + // Phase 6: hierarchical context for volumes is encoded as a MUST rule + // rather than a schema-level dependency graph. + expect(TEMPLATE_SCAFFOLDING.rules.must).toContain( + "When discovering volume resources, prompt the user for catalog and schema before listing volumes.", + ); + }); + }); }); diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts index 5206b01fe..2a7f8802e 100644 --- a/packages/shared/src/schemas/manifest.ts +++ b/packages/shared/src/schemas/manifest.ts @@ -21,8 +21,10 @@ * carrying the existing free-form fields (with the `` * refinement). Hierarchical context for volumes (catalog/schema parent * walk) is encoded as a Phase 6 MUST rule, not modeled in the schema. - * - Phase 6: scaffolding rule items get a `maxLength`, and a volume MUST - * rule lands in `TEMPLATE_SCAFFOLDING`. Neither happens here. + * - Phase 6: scaffolding rule items carry a `maxLength` (120 chars) so + * `rules.never[]` / `rules.must[]` stay short directives by contract, and + * the canonical `TEMPLATE_SCAFFOLDING` constant lives co-located with the + * scaffolding schemas (sync.ts imports it). */ import { z } from "zod"; @@ -843,14 +845,28 @@ export const scaffoldingFlagSchema = z .strict() .describe("A flag for the scaffolding command."); +/** + * Per-item upper bound on scaffolding rule strings. The intent is to enforce + * "short directive" by contract — long paragraphs fail validation and force + * authors to split prose into discrete actionable items. + */ +const SCAFFOLDING_RULE_MAX_LENGTH = 120; + +const scaffoldingRuleItemSchema = z + .string() + .max( + SCAFFOLDING_RULE_MAX_LENGTH, + `rule item must be ≤ ${SCAFFOLDING_RULE_MAX_LENGTH} chars`, + ); + export const scaffoldingRulesSchema = z .object({ never: z - .array(z.string()) + .array(scaffoldingRuleItemSchema) .optional() .describe("Actions the scaffolding agent must never perform."), must: z - .array(z.string()) + .array(scaffoldingRuleItemSchema) .optional() .describe("Actions the scaffolding agent must always perform."), }) @@ -875,6 +891,48 @@ export const scaffoldingDescriptorSchema = z "Describes the scaffolding command, flags, and rules for project initialization.", ); +/** + * Canonical scaffolding descriptor for the `databricks apps init` command, + * embedded in v2.0 template manifests to guide scaffolding agents. + * + * Co-located with `scaffoldingDescriptorSchema` so any change to the rule set + * (or the schema's `maxLength` ceiling) shows up next to its consumer. The + * `satisfies` annotation gives compile-time validation that the literal + * matches the schema's input shape; if a `must`/`never` entry exceeds the + * `maxLength` ceiling at runtime, `scaffoldingDescriptorSchema.parse` would + * surface the breach in tests. + */ +export const TEMPLATE_SCAFFOLDING = { + command: "databricks apps init", + flags: { + "--template-dir": { + description: "Path to the template directory containing the app scaffold", + required: true, + }, + "--config-dir": { + description: "Path to the output directory for the initialized app", + required: true, + }, + "--profile": { + description: "Databricks CLI profile to use for authentication", + required: false, + }, + }, + rules: { + never: [ + "Modify files inside the template directory", + "Skip resource configuration prompts", + "Hardcode workspace-specific values in template files", + ], + must: [ + "Use the template manifest (appkit.plugins.json) as the source of truth for available plugins", + "Respect requiredByTemplate flags when presenting plugin selection", + "Generate .env files with all required environment variables from selected plugins", + "When discovering volume resources, prompt the user for catalog and schema before listing volumes.", + ], + }, +} satisfies z.infer; + // ── Template plugins manifest (root) ───────────────────────────────────── export const templatePluginsManifestSchema = z diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index 001eb82cc..da4c4620a 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -300,7 +300,8 @@ "must": [ "Use the template manifest (appkit.plugins.json) as the source of truth for available plugins", "Respect requiredByTemplate flags when presenting plugin selection", - "Generate .env files with all required environment variables from selected plugins" + "Generate .env files with all required environment variables from selected plugins", + "When discovering volume resources, prompt the user for catalog and schema before listing volumes." ] } } From a3a9233e73a5d840819fc95deda8215541f7c1a6 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 22:21:05 +0200 Subject: [PATCH 14/17] fix: dedupe zod resolution for json-schema generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's `Check generated types are up to date` step was failing because two zod versions live in the workspace: - 4.1.13 hoisted at root (transitive via clean-app's eslint-plugin-react-hooks → zod-validation-error peer) - 4.3.6 in packages/shared (explicit dep) `tools/generate-json-schema.ts` imports zod from its own location, which resolves to root's 4.1.13. `packages/shared/src/schemas/manifest.ts` imports zod, resolving to shared's 4.3.6. The two runtimes operate on each other's schema objects, and the older zod's `toJSONSchema` doesn't extract all the constraints (pattern, minLength, propertyNames) that the newer zod baked into the schemas. CI's pnpm install resolves them consistently and emits the richer output, which then drifts from what's committed. Adding zod@4.3.6 as a root devDependency makes pnpm hoist the matching version to the top-level node_modules. The generator now resolves the same zod runtime as the schema module, and the JSON Schema output is byte-stable across local and CI. Regenerated docs/static/schemas/*.schema.json carry the now-emitted constraints (~135 minLength/pattern entries on plugin-manifest, similar on template-plugins). The constraints were always in the Zod schemas since phase 1 — they just weren't surviving the cross-version emit. Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../schemas/plugin-manifest.schema.json | 1745 +++++++++------- .../schemas/template-plugins.schema.json | 1753 ++++++++++------- package.json | 3 +- pnpm-lock.yaml | 3 + 4 files changed, 1982 insertions(+), 1522 deletions(-) diff --git a/docs/static/schemas/plugin-manifest.schema.json b/docs/static/schemas/plugin-manifest.schema.json index 72ab9f0d1..1fde0a200 100644 --- a/docs/static/schemas/plugin-manifest.schema.json +++ b/docs/static/schemas/plugin-manifest.schema.json @@ -2,7 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json", "title": "AppKit Plugin Manifest", - "description": "Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options.", "type": "object", "properties": { "$schema": { @@ -10,26 +9,26 @@ "type": "string" }, "name": { - "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens." }, "displayName": { - "description": "Human-readable display name for UI and CLI", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable display name for UI and CLI" }, "description": { - "description": "Brief description of what the plugin does", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Brief description of what the plugin does" }, "resources": { - "description": "Databricks resource requirements for this plugin", "type": "object", "properties": { "required": { - "description": "Resources that must be available for the plugin to function", "type": "array", "items": { - "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().", "oneOf": [ { "type": "object", @@ -39,27 +38,33 @@ "const": "secret" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -86,22 +91,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -109,7 +112,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -129,24 +133,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -162,18 +166,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ", "WRITE", "MANAGE"] + "enum": ["READ", "WRITE", "MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -193,27 +200,33 @@ "const": "job" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -240,22 +253,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -263,7 +274,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -283,24 +295,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -316,18 +328,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -347,27 +362,33 @@ "const": "sql_warehouse" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -394,22 +415,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -417,7 +436,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -437,24 +457,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -470,18 +490,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE", "CAN_MANAGE"] + "enum": ["CAN_USE", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -501,27 +524,33 @@ "const": "serving_endpoint" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -548,22 +577,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -571,7 +598,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -591,24 +619,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -624,18 +652,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -655,27 +686,33 @@ "const": "volume" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -702,22 +739,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -725,7 +760,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -745,24 +781,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -778,18 +814,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ_VOLUME", "WRITE_VOLUME"] + "enum": ["READ_VOLUME", "WRITE_VOLUME"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -809,27 +848,33 @@ "const": "vector_search_index" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -856,22 +901,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -879,7 +922,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -899,24 +943,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -932,18 +976,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["SELECT"] + "enum": ["SELECT"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -963,27 +1010,33 @@ "const": "uc_function" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1010,22 +1063,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1033,7 +1084,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1053,24 +1105,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1086,18 +1138,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["EXECUTE"] + "enum": ["EXECUTE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1117,27 +1172,33 @@ "const": "uc_connection" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1164,22 +1225,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1187,7 +1246,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1207,24 +1267,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1240,18 +1300,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["USE_CONNECTION"] + "enum": ["USE_CONNECTION"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1271,27 +1334,33 @@ "const": "database" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1318,22 +1387,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1341,7 +1408,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1361,24 +1429,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1394,18 +1462,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1425,27 +1496,33 @@ "const": "postgres" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1472,22 +1549,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1495,7 +1570,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1515,24 +1591,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1548,18 +1624,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1579,27 +1658,33 @@ "const": "genie_space" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1626,22 +1711,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1649,7 +1732,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1669,24 +1753,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1702,18 +1786,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1733,27 +1820,33 @@ "const": "experiment" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1780,22 +1873,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1803,7 +1894,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1823,24 +1915,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1856,18 +1948,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1887,27 +1982,33 @@ "const": "app" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1934,22 +2035,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1957,7 +2056,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1977,24 +2077,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2010,18 +2110,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE"] + "enum": ["CAN_USE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2033,14 +2136,14 @@ ], "additionalProperties": false } - ] - } + ], + "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements()." + }, + "description": "Resources that must be available for the plugin to function" }, "optional": { - "description": "Resources that enhance functionality but are not mandatory", "type": "array", "items": { - "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().", "oneOf": [ { "type": "object", @@ -2050,27 +2153,33 @@ "const": "secret" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2097,22 +2206,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2120,7 +2227,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2140,24 +2248,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2173,18 +2281,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ", "WRITE", "MANAGE"] + "enum": ["READ", "WRITE", "MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2204,27 +2315,33 @@ "const": "job" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2251,22 +2368,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2274,7 +2389,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2294,24 +2410,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2327,18 +2443,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2358,27 +2477,33 @@ "const": "sql_warehouse" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2405,22 +2530,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2428,7 +2551,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2448,24 +2572,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2481,18 +2605,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE", "CAN_MANAGE"] + "enum": ["CAN_USE", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2512,27 +2639,33 @@ "const": "serving_endpoint" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2559,22 +2692,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2582,7 +2713,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2602,24 +2734,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2635,18 +2767,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2666,27 +2801,33 @@ "const": "volume" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2713,22 +2854,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2736,7 +2875,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2756,24 +2896,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2789,18 +2929,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ_VOLUME", "WRITE_VOLUME"] + "enum": ["READ_VOLUME", "WRITE_VOLUME"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2820,27 +2963,33 @@ "const": "vector_search_index" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2867,22 +3016,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2890,7 +3037,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2910,24 +3058,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2943,18 +3091,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["SELECT"] + "enum": ["SELECT"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2974,27 +3125,33 @@ "const": "uc_function" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3021,22 +3178,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3044,7 +3199,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3064,24 +3220,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3097,18 +3253,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["EXECUTE"] + "enum": ["EXECUTE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3128,27 +3287,33 @@ "const": "uc_connection" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3175,22 +3340,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3198,7 +3361,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3218,24 +3382,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3251,18 +3415,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["USE_CONNECTION"] + "enum": ["USE_CONNECTION"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3282,27 +3449,33 @@ "const": "database" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3329,22 +3502,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3352,7 +3523,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3372,24 +3544,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3405,18 +3577,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3436,27 +3611,33 @@ "const": "postgres" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3483,22 +3664,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3506,7 +3685,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3526,24 +3706,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3559,18 +3739,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3590,27 +3773,33 @@ "const": "genie_space" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3637,22 +3826,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3660,7 +3847,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3680,24 +3868,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3713,18 +3901,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3744,27 +3935,33 @@ "const": "experiment" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3791,22 +3988,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3814,7 +4009,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3834,24 +4030,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3867,18 +4063,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3898,27 +4097,33 @@ "const": "app" }, "alias": { - "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only. Deduplication uses resourceKey, not alias." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).", "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3945,22 +4150,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3968,7 +4171,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3988,24 +4192,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -4021,18 +4225,21 @@ } }, "required": ["type", "cliCommand", "selectField"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key)." } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE"] + "enum": ["CAN_USE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -4044,19 +4251,26 @@ ], "additionalProperties": false } - ] - } + ], + "description": "Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements()." + }, + "description": "Resources that enhance functionality but are not mandatory" } }, "required": ["required", "optional"], - "additionalProperties": false + "additionalProperties": false, + "description": "Databricks resource requirements for this plugin" }, "config": { "description": "Configuration schema for the plugin", "type": "object", "properties": { "schema": { - "$ref": "#/definitions/__schema0" + "allOf": [ + { + "$ref": "#/definitions/__schema0" + } + ] } }, "additionalProperties": false @@ -4067,7 +4281,8 @@ }, "version": { "description": "Plugin version (semver format)", - "type": "string" + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?$" }, "repository": { "description": "URL to the plugin's source repository", @@ -4102,12 +4317,12 @@ "description": "Ordered list of post-scaffolding instructions shown to the user after project initialization. Array position determines display order.", "type": "array", "items": { - "description": "A post-scaffolding instruction shown to the user after project initialization.", "type": "object", "properties": { "instruction": { - "description": "Human-readable instruction for the user to follow after scaffolding.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable instruction for the user to follow after scaffolding." }, "required": { "description": "Whether this step is required for the plugin to function correctly.", @@ -4115,12 +4330,14 @@ } }, "required": ["instruction"], - "additionalProperties": false + "additionalProperties": false, + "description": "A post-scaffolding instruction shown to the user after project initialization." } } }, "required": ["name", "displayName", "description", "resources"], "additionalProperties": false, + "description": "Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options.", "definitions": { "__schema0": { "type": "object", @@ -4131,12 +4348,19 @@ }, "properties": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "$ref": "#/definitions/__schema1" } }, "items": { - "$ref": "#/definitions/__schema0" + "allOf": [ + { + "$ref": "#/definitions/__schema0" + } + ] }, "required": { "type": "array", @@ -4167,12 +4391,19 @@ }, "properties": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "$ref": "#/definitions/__schema1" } }, "items": { - "$ref": "#/definitions/__schema1" + "allOf": [ + { + "$ref": "#/definitions/__schema1" + } + ] }, "minimum": { "type": "number" @@ -4181,10 +4412,14 @@ "type": "number" }, "minLength": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 }, "maxLength": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 }, "required": { "type": "array", diff --git a/docs/static/schemas/template-plugins.schema.json b/docs/static/schemas/template-plugins.schema.json index c634bbc36..15ae1e5fe 100644 --- a/docs/static/schemas/template-plugins.schema.json +++ b/docs/static/schemas/template-plugins.schema.json @@ -2,7 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://databricks.github.io/appkit/schemas/template-plugins.schema.json", "title": "AppKit Template Plugins Manifest", - "description": "Aggregated plugin manifest for AppKit templates. Read by Databricks CLI during init to discover available plugins and their resource requirements.", "type": "object", "properties": { "$schema": { @@ -10,32 +9,37 @@ "type": "string" }, "version": { - "description": "Schema version for the template plugins manifest", "type": "string", - "enum": ["1.0", "1.1", "2.0"] + "enum": ["1.0", "1.1", "2.0"], + "description": "Schema version for the template plugins manifest" }, "plugins": { - "description": "Map of plugin name to plugin manifest with package source", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "Plugin manifest with package source information", "type": "object", "properties": { "name": { - "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens." }, "displayName": { - "description": "Human-readable display name for UI and CLI", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable display name for UI and CLI" }, "description": { - "description": "Brief description of what the plugin does", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Brief description of what the plugin does" }, "package": { - "description": "NPM package name that provides this plugin", - "type": "string" + "type": "string", + "minLength": 1, + "description": "NPM package name that provides this plugin" }, "requiredByTemplate": { "description": "When true, this plugin is required by the template and cannot be deselected during CLI init. The user will only be prompted to configure its resources. When absent or false, the plugin is optional and the user can choose whether to include it.", @@ -54,12 +58,12 @@ "description": "Ordered list of post-scaffolding instructions propagated from the plugin manifest.", "type": "array", "items": { - "description": "A post-scaffolding instruction shown to the user after project initialization.", "type": "object", "properties": { "instruction": { - "description": "Human-readable instruction for the user to follow after scaffolding.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable instruction for the user to follow after scaffolding." }, "required": { "description": "Whether this step is required for the plugin to function correctly.", @@ -67,18 +71,16 @@ } }, "required": ["instruction"], - "additionalProperties": false + "additionalProperties": false, + "description": "A post-scaffolding instruction shown to the user after project initialization." } }, "resources": { - "description": "Databricks resource requirements for this plugin", "type": "object", "properties": { "required": { - "description": "Resources that must be available for the plugin to function", "type": "array", "items": { - "description": "Resource requirement with template-specific field entries (includes computed origin).", "oneOf": [ { "type": "object", @@ -88,26 +90,33 @@ "const": "secret" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -134,22 +143,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -157,7 +164,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -177,24 +185,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -214,23 +222,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ", "WRITE", "MANAGE"] + "enum": ["READ", "WRITE", "MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -250,26 +260,33 @@ "const": "job" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -296,22 +313,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -319,7 +334,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -339,24 +355,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -376,23 +392,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -412,26 +430,33 @@ "const": "sql_warehouse" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -458,22 +483,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -481,7 +504,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -501,24 +525,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -538,23 +562,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE", "CAN_MANAGE"] + "enum": ["CAN_USE", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -574,26 +600,33 @@ "const": "serving_endpoint" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -620,22 +653,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -643,7 +674,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -663,24 +695,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -700,23 +732,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -736,26 +770,33 @@ "const": "volume" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -782,22 +823,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -805,7 +844,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -825,24 +865,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -862,23 +902,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ_VOLUME", "WRITE_VOLUME"] + "enum": ["READ_VOLUME", "WRITE_VOLUME"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -898,26 +940,33 @@ "const": "vector_search_index" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -944,22 +993,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -967,7 +1014,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -987,24 +1035,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1024,23 +1072,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["SELECT"] + "enum": ["SELECT"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1060,26 +1110,33 @@ "const": "uc_function" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1106,22 +1163,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1129,7 +1184,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1149,24 +1205,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1186,23 +1242,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["EXECUTE"] + "enum": ["EXECUTE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1222,26 +1280,33 @@ "const": "uc_connection" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1268,22 +1333,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1291,7 +1354,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1311,24 +1375,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1348,23 +1412,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["USE_CONNECTION"] + "enum": ["USE_CONNECTION"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1384,26 +1450,33 @@ "const": "database" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1430,22 +1503,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1453,7 +1524,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1473,24 +1545,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1510,23 +1582,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1546,26 +1620,33 @@ "const": "postgres" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1592,22 +1673,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1615,7 +1694,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1635,24 +1715,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1672,23 +1752,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1708,26 +1790,33 @@ "const": "genie_space" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1754,22 +1843,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1777,7 +1864,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1797,24 +1885,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -1834,28 +1922,30 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", "enum": [ "CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE" - ] + ], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -1875,26 +1965,33 @@ "const": "experiment" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -1921,22 +2018,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -1944,7 +2039,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -1964,24 +2060,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2001,23 +2097,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2037,26 +2135,33 @@ "const": "app" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2083,22 +2188,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2106,7 +2209,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2126,24 +2230,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2163,23 +2267,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE"] + "enum": ["CAN_USE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2191,14 +2297,14 @@ ], "additionalProperties": false } - ] - } + ], + "description": "Resource requirement with template-specific field entries (includes computed origin)." + }, + "description": "Resources that must be available for the plugin to function" }, "optional": { - "description": "Resources that enhance functionality but are not mandatory", "type": "array", "items": { - "description": "Resource requirement with template-specific field entries (includes computed origin).", "oneOf": [ { "type": "object", @@ -2208,26 +2314,33 @@ "const": "secret" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2254,22 +2367,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2277,7 +2388,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2297,24 +2409,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2334,23 +2446,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ", "WRITE", "MANAGE"] + "enum": ["READ", "WRITE", "MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2370,26 +2484,33 @@ "const": "job" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2416,22 +2537,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2439,7 +2558,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2459,24 +2579,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2496,23 +2616,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2532,26 +2654,33 @@ "const": "sql_warehouse" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2578,22 +2707,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2601,7 +2728,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2621,24 +2749,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2658,23 +2786,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE", "CAN_MANAGE"] + "enum": ["CAN_USE", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2694,26 +2824,33 @@ "const": "serving_endpoint" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2740,22 +2877,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2763,7 +2898,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2783,24 +2919,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2820,23 +2956,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"] + "enum": ["CAN_VIEW", "CAN_QUERY", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -2856,26 +2994,33 @@ "const": "volume" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -2902,22 +3047,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -2925,7 +3068,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -2945,24 +3089,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -2982,23 +3126,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["READ_VOLUME", "WRITE_VOLUME"] + "enum": ["READ_VOLUME", "WRITE_VOLUME"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3018,26 +3164,33 @@ "const": "vector_search_index" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3064,22 +3217,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3087,7 +3238,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3107,24 +3259,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3144,23 +3296,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["SELECT"] + "enum": ["SELECT"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3180,26 +3334,33 @@ "const": "uc_function" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3226,22 +3387,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3249,7 +3408,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3269,24 +3429,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3306,23 +3466,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["EXECUTE"] + "enum": ["EXECUTE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3342,26 +3504,33 @@ "const": "uc_connection" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3388,22 +3557,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3411,7 +3578,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3431,24 +3599,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3468,23 +3636,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["USE_CONNECTION"] + "enum": ["USE_CONNECTION"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3504,26 +3674,33 @@ "const": "database" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3550,22 +3727,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3573,7 +3748,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3593,24 +3769,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3630,23 +3806,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3666,26 +3844,33 @@ "const": "postgres" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3712,22 +3897,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3735,7 +3918,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3755,24 +3939,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3792,23 +3976,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_CONNECT_AND_CREATE"] + "enum": ["CAN_CONNECT_AND_CREATE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3828,26 +4014,33 @@ "const": "genie_space" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -3874,22 +4067,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -3897,7 +4088,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -3917,24 +4109,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -3954,28 +4146,30 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", "enum": [ "CAN_VIEW", "CAN_RUN", "CAN_EDIT", "CAN_MANAGE" - ] + ], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -3995,26 +4189,33 @@ "const": "experiment" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -4041,22 +4242,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -4064,7 +4263,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -4084,24 +4284,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -4121,23 +4321,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"] + "enum": ["CAN_READ", "CAN_EDIT", "CAN_MANAGE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -4157,26 +4359,33 @@ "const": "app" }, "alias": { - "description": "Human-readable label for UI/display only.", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable label for UI/display only." }, "resourceKey": { - "description": "Stable key for machine use: deduplication, env naming, composite keys.", - "type": "string" + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Stable key for machine use: deduplication, env naming, composite keys." }, "description": { - "description": "Human-readable description of why this resource is needed", - "type": "string" + "type": "string", + "minLength": 1, + "description": "Human-readable description of why this resource is needed" }, "fields": { "description": "Map of field name to field entry with computed origin.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { "env": { "description": "Environment variable name for this field", - "type": "string" + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" }, "description": { "description": "Human-readable description for this field", @@ -4203,22 +4412,20 @@ }, "resolve": { "description": "Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow.", - "type": "string" + "type": "string", + "pattern": "^[a-z_]+:[a-zA-Z]+$" }, "discovery": { - "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.", "oneOf": [ { - "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind.", "type": "string", - "const": "kind" + "const": "kind", + "description": "Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind." }, "resourceKind": { - "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules.", "type": "string", "enum": [ "warehouse", @@ -4226,7 +4433,8 @@ "postgres_branch", "postgres_database", "volume" - ] + ], + "description": "Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules." }, "select": { "description": "Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted.", @@ -4246,24 +4454,24 @@ } }, "required": ["type", "resourceKind"], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind." }, { - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", "type": "object", "properties": { "type": { - "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin.", "type": "string", - "const": "cli" + "const": "cli", + "description": "Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin." }, "cliCommand": { - "description": "Databricks CLI command that lists resources. Must include placeholder.", - "type": "string" + "type": "string", + "description": "Databricks CLI command that lists resources. Must include placeholder." }, "selectField": { - "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name').", - "type": "string" + "type": "string", + "description": "jq-style path to the field used as the selected value (e.g., '.id', '.name')." }, "displayField": { "description": "jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted.", @@ -4283,23 +4491,25 @@ "cliCommand", "selectField" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." } - ] + ], + "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." }, "origin": { - "description": "How the field value is determined. Computed during sync, not authored by plugin developers.", "type": "string", - "enum": ["user", "platform", "static", "cli"] + "enum": ["user", "platform", "static", "cli"], + "description": "How the field value is determined. Computed during sync, not authored by plugin developers." } }, "additionalProperties": false } }, "permission": { - "description": "Required permission level. Validated per resource type.", "type": "string", - "enum": ["CAN_USE"] + "enum": ["CAN_USE"], + "description": "Required permission level. Validated per resource type." } }, "required": [ @@ -4311,12 +4521,15 @@ ], "additionalProperties": false } - ] - } + ], + "description": "Resource requirement with template-specific field entries (includes computed origin)." + }, + "description": "Resources that enhance functionality but are not mandatory" } }, "required": ["required", "optional"], - "additionalProperties": false + "additionalProperties": false, + "description": "Databricks resource requirements for this plugin" } }, "required": [ @@ -4326,27 +4539,31 @@ "package", "resources" ], - "additionalProperties": false - } + "additionalProperties": false, + "description": "Plugin manifest with package source information" + }, + "description": "Map of plugin name to plugin manifest with package source" }, "scaffolding": { "description": "Describes the scaffolding command and its configuration for project initialization.", "type": "object", "properties": { "command": { - "description": "The scaffolding command (e.g., 'databricks apps init').", - "type": "string" + "type": "string", + "description": "The scaffolding command (e.g., 'databricks apps init')." }, "flags": { "description": "Map of flag name to flag descriptor.", "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { - "description": "A flag for the scaffolding command.", "type": "object", "properties": { "description": { - "description": "Human-readable description of the flag.", - "type": "string" + "type": "string", + "description": "Human-readable description of the flag." }, "required": { "description": "Whether this flag is required.", @@ -4362,7 +4579,8 @@ } }, "required": ["description"], - "additionalProperties": false + "additionalProperties": false, + "description": "A flag for the scaffolding command." } }, "rules": { @@ -4373,14 +4591,16 @@ "description": "Actions the scaffolding agent must never perform.", "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 120 } }, "must": { "description": "Actions the scaffolding agent must always perform.", "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 120 } } }, @@ -4392,5 +4612,6 @@ } }, "required": ["version", "plugins"], - "additionalProperties": false + "additionalProperties": false, + "description": "Aggregated plugin manifest for AppKit templates. Read by Databricks CLI during init to discover available plugins and their resource requirements." } diff --git a/package.json b/package.json index 733abdc12..fff062625 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,8 @@ "turbo": "2.6.1", "typescript": "5.9.3", "vite-tsconfig-paths": "5.1.4", - "vitest": "3.2.4" + "vitest": "3.2.4", + "zod": "4.3.6" }, "resolutions": { "conventional-changelog-conventionalcommits": "9.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cab9d9682..a1827e369 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: vitest: specifier: 3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.7.2)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2) + zod: + specifier: 4.3.6 + version: 4.3.6 apps/clean-app: dependencies: From a6d0c7fd2fb98a6c5d18ebaabd6a3194d49accba Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 22:59:21 +0200 Subject: [PATCH 15/17] =?UTF-8?q?fix:=20address=20review=20findings=20?= =?UTF-8?q?=E2=80=94=20cli=20command=20syntax=20+=20lakebase=20parent=20+?= =?UTF-8?q?=20strict=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three real bugs flagged by the multi-model review of PR #261, fixed in this iteration. (CRITICAL cliCommand RCE hardening + HIGH z.lazy() perf deferred to follow-up PRs.) 1. RESOURCE_KIND_COMMANDS strings now match the real Databricks CLI (verified against v0.299.0 --help output): - `genie list` → `genie list-spaces` - `volumes list .` → `volumes list {catalog} {schema}` (two separate positionals, prompted via the volume MUST rule) - `postgres list-branches` → `postgres list-branches {project}` with a project parent (covered by Fix 2 below) 2. Lakebase branch discovery is now actually runnable: - resourceKindSchema gains `postgres_project`. RESOURCE_KIND_COMMANDS gains the corresponding `databricks postgres list-projects` entry. - lakebase/manifest.json gains a new `project` field with `discovery: { type: "kind", resourceKind: "postgres_project", select: "name" }`. - The existing `branch` field's discovery adds `dependsOn: "project"`, so the parent project name flows into the branch listing command. 3. configSchemaPropertySchema and configSchemaSchema gain `.strict()`, so plugin config-schema typos no longer pass validation silently. `additionalProperties` (a standard JSON Schema keyword used by three core plugins — serving, vector-search, genie — inside nested property entries) is added explicitly as `z.union([z.boolean(), configSchemaPropertySchema]).optional()` so those manifests keep validating; this is a deliberate canonical addition, not a loosening of strict mode. Auto-regenerated by the build pipeline: - docs/static/schemas/{plugin-manifest,template-plugins}.schema.json - template/appkit.plugins.json Backpressure: typecheck=0, test=0 (108 files / 2136 tests), build=0, docs:build=0, knip=0, check:fix=0. Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../schemas/plugin-manifest.schema.json | 42 ++++++++- .../schemas/template-plugins.schema.json | 26 ++++++ .../appkit/src/plugins/lakebase/manifest.json | 14 ++- packages/shared/src/schemas/manifest.ts | 86 ++++++++++++------- template/appkit.plugins.json | 17 +++- 5 files changed, 149 insertions(+), 36 deletions(-) diff --git a/docs/static/schemas/plugin-manifest.schema.json b/docs/static/schemas/plugin-manifest.schema.json index 1fde0a200..e3544a7e3 100644 --- a/docs/static/schemas/plugin-manifest.schema.json +++ b/docs/static/schemas/plugin-manifest.schema.json @@ -109,6 +109,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -271,6 +272,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -433,6 +435,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -595,6 +598,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -757,6 +761,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -919,6 +924,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1081,6 +1087,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1243,6 +1250,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1405,6 +1413,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1567,6 +1576,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1729,6 +1739,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1891,6 +1902,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2053,6 +2065,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2224,6 +2237,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2386,6 +2400,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2548,6 +2563,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2710,6 +2726,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2872,6 +2889,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3034,6 +3052,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3196,6 +3215,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3358,6 +3378,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3520,6 +3541,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3682,6 +3704,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3844,6 +3867,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -4006,6 +4030,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -4168,6 +4193,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -4372,7 +4398,8 @@ "type": "boolean" } }, - "required": ["type"] + "required": ["type"], + "additionalProperties": false }, "__schema1": { "type": "object", @@ -4426,9 +4453,20 @@ "items": { "type": "string" } + }, + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/__schema1" + } + ] } }, - "required": ["type"] + "required": ["type"], + "additionalProperties": false } } } diff --git a/docs/static/schemas/template-plugins.schema.json b/docs/static/schemas/template-plugins.schema.json index 15ae1e5fe..a92fd52dd 100644 --- a/docs/static/schemas/template-plugins.schema.json +++ b/docs/static/schemas/template-plugins.schema.json @@ -161,6 +161,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -331,6 +332,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -501,6 +503,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -671,6 +674,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -841,6 +845,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1011,6 +1016,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1181,6 +1187,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1351,6 +1358,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1521,6 +1529,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1691,6 +1700,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -1861,6 +1871,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2036,6 +2047,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2206,6 +2218,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2385,6 +2398,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2555,6 +2569,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2725,6 +2740,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -2895,6 +2911,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3065,6 +3082,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3235,6 +3253,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3405,6 +3424,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3575,6 +3595,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3745,6 +3766,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -3915,6 +3937,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -4085,6 +4108,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -4260,6 +4284,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" @@ -4430,6 +4455,7 @@ "enum": [ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume" diff --git a/packages/appkit/src/plugins/lakebase/manifest.json b/packages/appkit/src/plugins/lakebase/manifest.json index dac55a474..c42526916 100644 --- a/packages/appkit/src/plugins/lakebase/manifest.json +++ b/packages/appkit/src/plugins/lakebase/manifest.json @@ -13,13 +13,23 @@ "description": "Lakebase Postgres database for persistent storage", "permission": "CAN_CONNECT_AND_CREATE", "fields": { + "project": { + "description": "Full Lakebase Postgres project resource name. Obtain by running `databricks postgres list-projects`, select the desired item from the output array and use its .name value.", + "examples": ["projects/{project-id}"], + "discovery": { + "type": "kind", + "resourceKind": "postgres_project", + "select": "name" + } + }, "branch": { - "description": "Full Lakebase Postgres branch resource name. Obtain by running `databricks postgres list-branches projects/{project-id}`, select the desired item from the output array and use its .name value.", + "description": "Full Lakebase Postgres branch resource name. Obtain by running `databricks postgres list-branches {project-name}`, select the desired item from the output array and use its .name value. Requires the project resource name.", "examples": ["projects/{project-id}/branches/{branch-id}"], "discovery": { "type": "kind", "resourceKind": "postgres_branch", - "select": "name" + "select": "name", + "dependsOn": "project" } }, "database": { diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts index 2a7f8802e..7e55977fa 100644 --- a/packages/shared/src/schemas/manifest.ts +++ b/packages/shared/src/schemas/manifest.ts @@ -125,6 +125,7 @@ export const resourceKindSchema = z .enum([ "warehouse", "genie_space", + "postgres_project", "postgres_branch", "postgres_database", "volume", @@ -259,10 +260,9 @@ export type ResourceKindCommand = { * return flat arrays. Refine in a follow-up if a kind's CLI returns wrapped * data. * - * Volume's catalog/schema parent context is NOT modeled here. The - * `databricks volumes list` command requires a `.` argument - * that AppKit does not auto-discover; the CLI is expected to prompt the user - * via a Phase 6 MUST rule before invoking the listing. + * Volume's catalog/schema parent context is supplied via the `{catalog}` and + * `{schema}` placeholders, prompted from the user via a Phase 6 MUST rule + * (the CLI does not auto-discover these — it asks before listing volumes). */ export const RESOURCE_KIND_COMMANDS: Record< z.infer, @@ -272,11 +272,19 @@ export const RESOURCE_KIND_COMMANDS: Record< command: "databricks warehouses list --profile --output json", }, genie_space: { - command: "databricks genie list --profile --output json", + command: "databricks genie list-spaces --profile --output json", + }, + postgres_project: { + command: + "databricks postgres list-projects --profile --output json", }, postgres_branch: { + // {project} is a placeholder for the resolved value of the `project` + // sibling field (declared via `dependsOn: "project"` on the kind variant). + // The Databricks CLI requires the parent project resource name (format + // `projects/{project_id}`) as a positional argument. command: - "databricks postgres list-branches --profile --output json", + "databricks postgres list-branches {project} --profile --output json", }, postgres_database: { // {branch} is a placeholder for the resolved value of the `branch` @@ -285,11 +293,11 @@ export const RESOURCE_KIND_COMMANDS: Record< "databricks postgres list-databases {branch} --profile --output json", }, volume: { - // . parent context must be supplied by the CLI runner — - // it is encoded as a Phase 6 MUST rule (prompt the user for catalog and - // schema before listing volumes), not as a schema-level placeholder. + // {catalog} and {schema} parent context must be supplied by the CLI + // runner — they are encoded as a Phase 6 MUST rule (prompt the user for + // catalog and schema before listing volumes), not as `dependsOn` siblings. command: - "databricks volumes list . --profile --output json", + "databricks volumes list {catalog} {schema} --profile --output json", }, }; @@ -491,29 +499,47 @@ export const resourceRequirementSchema = z // ── Config schema (recursive) ──────────────────────────────────────────── export const configSchemaPropertySchema: z.ZodType = z.lazy(() => - z.object({ - type: z.enum(["object", "array", "string", "number", "boolean", "integer"]), - description: z.string().optional(), - default: z.unknown().optional(), - enum: z.array(z.unknown()).optional(), - properties: z.record(z.string(), configSchemaPropertySchema).optional(), - items: configSchemaPropertySchema.optional(), - minimum: z.number().optional(), - maximum: z.number().optional(), - minLength: z.number().int().min(0).optional(), - maxLength: z.number().int().min(0).optional(), - required: z.array(z.string()).optional(), - }), + z + .object({ + type: z.enum([ + "object", + "array", + "string", + "number", + "boolean", + "integer", + ]), + description: z.string().optional(), + default: z.unknown().optional(), + enum: z.array(z.unknown()).optional(), + properties: z.record(z.string(), configSchemaPropertySchema).optional(), + items: configSchemaPropertySchema.optional(), + minimum: z.number().optional(), + maximum: z.number().optional(), + minLength: z.number().int().min(0).optional(), + maxLength: z.number().int().min(0).optional(), + required: z.array(z.string()).optional(), + // `additionalProperties` is a standard JSON Schema keyword used by core + // plugin manifests (e.g., serving, vector-search, genie) to constrain + // dictionary-shaped properties. Allowed on nested property entries as + // either a boolean or a sub-schema, mirroring JSON Schema semantics. + additionalProperties: z + .union([z.boolean(), configSchemaPropertySchema]) + .optional(), + }) + .strict(), ); export const configSchemaSchema: z.ZodType = z.lazy(() => - z.object({ - type: z.enum(["object", "array", "string", "number", "boolean"]), - properties: z.record(z.string(), configSchemaPropertySchema).optional(), - items: configSchemaSchema.optional(), - required: z.array(z.string()).optional(), - additionalProperties: z.boolean().optional(), - }), + z + .object({ + type: z.enum(["object", "array", "string", "number", "boolean"]), + properties: z.record(z.string(), configSchemaPropertySchema).optional(), + items: configSchemaSchema.optional(), + required: z.array(z.string()).optional(), + additionalProperties: z.boolean().optional(), + }) + .strict(), ); // ── Post-scaffold step ─────────────────────────────────────────────────── diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index da4c4620a..61a577149 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -159,15 +159,28 @@ "description": "Lakebase Postgres database for persistent storage", "permission": "CAN_CONNECT_AND_CREATE", "fields": { + "project": { + "description": "Full Lakebase Postgres project resource name. Obtain by running `databricks postgres list-projects`, select the desired item from the output array and use its .name value.", + "examples": [ + "projects/{project-id}" + ], + "discovery": { + "type": "kind", + "resourceKind": "postgres_project", + "select": "name" + }, + "origin": "user" + }, "branch": { - "description": "Full Lakebase Postgres branch resource name. Obtain by running `databricks postgres list-branches projects/{project-id}`, select the desired item from the output array and use its .name value.", + "description": "Full Lakebase Postgres branch resource name. Obtain by running `databricks postgres list-branches {project-name}`, select the desired item from the output array and use its .name value. Requires the project resource name.", "examples": [ "projects/{project-id}/branches/{branch-id}" ], "discovery": { "type": "kind", "resourceKind": "postgres_branch", - "select": "name" + "select": "name", + "dependsOn": "project" }, "origin": "user" }, From d14389d3a1870e503912f647489deaf3742281e0 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 7 May 2026 23:14:45 +0200 Subject: [PATCH 16/17] chore: mark auto-generated artifacts as linguist-generated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub collapses these in the diff view by default and excludes them from language stats. The files are emitted by the build pipeline and should not need reviewer attention. - docs/static/schemas/*.schema.json — emitted by tools/generate-json-schema.ts - template/appkit.plugins.json — emitted by pnpm sync:template - packages/appkit/src/registry/types.generated.ts — generate-registry-types.ts - packages/appkit/src/plugins/*-exports.generated.ts — generate-plugin-entries.ts - docs/docs/api/** — typedoc API reference - pnpm-lock.yaml — pnpm Reduces perceived PR size on this branch by ~10k lines (two regenerated JSON Schema files alone account for ~91% of insertions on PR #261). Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .gitattributes | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..20e8e28c9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Mark auto-generated artifacts as such so GitHub collapses them in the +# diff view and they are excluded from language stats. The files are +# regenerated by the build pipeline; reviewers should not need to read +# the diffs. + +# JSON Schemas emitted from Zod by tools/generate-json-schema.ts +docs/static/schemas/*.schema.json linguist-generated=true + +# Synced template manifest emitted by `pnpm sync:template` +template/appkit.plugins.json linguist-generated=true + +# TypeScript types emitted by tools/generate-registry-types.ts and +# tools/generate-plugin-entries.ts +packages/appkit/src/registry/types.generated.ts linguist-generated=true +packages/appkit/src/plugins/*-exports.generated.ts linguist-generated=true + +# Typedoc API reference generated from JSDoc + inferred types +docs/docs/api/** linguist-generated=true + +# pnpm lockfile is generated; reviewers care about package.json changes, +# not the lockfile diff +pnpm-lock.yaml linguist-generated=true From 6f23cd8f9bae46cf8c83f557f6215f1f30bad305 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Fri, 8 May 2026 10:13:36 +0200 Subject: [PATCH 17/17] fix: deny shell metacharacters on cli discovery descriptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `cli` variant of discoveryDescriptor accepts a free-form Databricks CLI command supplied by the plugin author. With no further constraint beyond the existing `` placeholder check, the shape is open to two unrelated foot-guns reviewers flagged: - output-shape brittleness: a plugin writes `selectField: ".id"` but the CLI returns a wrapped object (e.g. `{warehouses: [...]}`); jq fails silently at scaffold time - shell-injection-if-executed: when an executor lands and passes the string to a shell, statement separators / pipes / command substitution / redirects all become attack surface The `kind` variant addresses both for first-party plugins (AppKit owns the command and unwrap rules). The `cli` variant is the escape hatch for third-party plugins that need bespoke commands. Tighten it cheaply now, before anyone ships against the loose shape: - new SHELL_METACHAR_RE blocks `;`, `|`, `&`, backtick, `$`, newlines on both `cliCommand` and `shortcut`. Angle brackets are still permitted so `` (and future `<…>` placeholders) work. - describes on cliCommand and the variant overall direct authors to use `kind` for first-party resources and call out that the cli shape is intentionally minimal and may tighten further. Not a security boundary on its own — executors must still spawn(argv) not shell-exec the string. argv-array form, denylist of shell operators in argv, and an output-shape contract are all separate decisions tied to the executor PR. Two new test cases cover the two refinements (cliCommand + shortcut). Co-authored-by: Isaac Signed-off-by: Atila Fassina --- .../schemas/plugin-manifest.schema.json | 156 +++++++++--------- .../schemas/template-plugins.schema.json | 156 +++++++++--------- .../plugin/validate/validate-manifest.test.ts | 32 ++++ packages/shared/src/schemas/manifest.ts | 30 +++- 4 files changed, 215 insertions(+), 159 deletions(-) diff --git a/docs/static/schemas/plugin-manifest.schema.json b/docs/static/schemas/plugin-manifest.schema.json index e3544a7e3..dee69aaaf 100644 --- a/docs/static/schemas/plugin-manifest.schema.json +++ b/docs/static/schemas/plugin-manifest.schema.json @@ -147,7 +147,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -162,13 +162,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -310,7 +310,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -325,13 +325,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -473,7 +473,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -488,13 +488,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -636,7 +636,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -651,13 +651,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -799,7 +799,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -814,13 +814,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -962,7 +962,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -977,13 +977,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1125,7 +1125,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1140,13 +1140,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1288,7 +1288,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1303,13 +1303,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1451,7 +1451,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1466,13 +1466,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1614,7 +1614,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1629,13 +1629,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1777,7 +1777,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1792,13 +1792,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1940,7 +1940,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1955,13 +1955,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2103,7 +2103,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2118,13 +2118,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2275,7 +2275,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2290,13 +2290,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2438,7 +2438,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2453,13 +2453,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2601,7 +2601,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2616,13 +2616,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2764,7 +2764,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2779,13 +2779,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2927,7 +2927,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2942,13 +2942,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3090,7 +3090,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3105,13 +3105,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3253,7 +3253,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3268,13 +3268,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3416,7 +3416,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3431,13 +3431,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3579,7 +3579,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3594,13 +3594,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3742,7 +3742,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3757,13 +3757,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3905,7 +3905,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3920,13 +3920,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -4068,7 +4068,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -4083,13 +4083,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -4231,7 +4231,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -4246,13 +4246,13 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, "required": ["type", "cliCommand", "selectField"], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." diff --git a/docs/static/schemas/template-plugins.schema.json b/docs/static/schemas/template-plugins.schema.json index a92fd52dd..0ead2544a 100644 --- a/docs/static/schemas/template-plugins.schema.json +++ b/docs/static/schemas/template-plugins.schema.json @@ -199,7 +199,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -214,7 +214,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -224,7 +224,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -370,7 +370,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -385,7 +385,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -395,7 +395,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -541,7 +541,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -556,7 +556,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -566,7 +566,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -712,7 +712,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -727,7 +727,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -737,7 +737,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -883,7 +883,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -898,7 +898,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -908,7 +908,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1054,7 +1054,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1069,7 +1069,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -1079,7 +1079,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1225,7 +1225,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1240,7 +1240,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -1250,7 +1250,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1396,7 +1396,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1411,7 +1411,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -1421,7 +1421,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1567,7 +1567,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1582,7 +1582,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -1592,7 +1592,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1738,7 +1738,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1753,7 +1753,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -1763,7 +1763,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -1909,7 +1909,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -1924,7 +1924,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -1934,7 +1934,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2085,7 +2085,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2100,7 +2100,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -2110,7 +2110,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2256,7 +2256,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2271,7 +2271,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -2281,7 +2281,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2436,7 +2436,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2451,7 +2451,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -2461,7 +2461,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2607,7 +2607,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2622,7 +2622,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -2632,7 +2632,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2778,7 +2778,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2793,7 +2793,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -2803,7 +2803,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -2949,7 +2949,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -2964,7 +2964,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -2974,7 +2974,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3120,7 +3120,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3135,7 +3135,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -3145,7 +3145,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3291,7 +3291,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3306,7 +3306,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -3316,7 +3316,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3462,7 +3462,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3477,7 +3477,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -3487,7 +3487,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3633,7 +3633,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3648,7 +3648,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -3658,7 +3658,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3804,7 +3804,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3819,7 +3819,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -3829,7 +3829,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -3975,7 +3975,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -3990,7 +3990,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -4000,7 +4000,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -4146,7 +4146,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -4161,7 +4161,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -4171,7 +4171,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -4322,7 +4322,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -4337,7 +4337,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -4347,7 +4347,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." @@ -4493,7 +4493,7 @@ }, "cliCommand": { "type": "string", - "description": "Databricks CLI command that lists resources. Must include placeholder." + "description": "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map." }, "selectField": { "type": "string", @@ -4508,7 +4508,7 @@ "type": "string" }, "shortcut": { - "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "description": "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", "type": "string" } }, @@ -4518,7 +4518,7 @@ "selectField" ], "additionalProperties": false, - "description": "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits." + "description": "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions." } ], "description": "Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command." diff --git a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts index d66402446..1a7371660 100644 --- a/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts +++ b/packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts @@ -768,6 +768,38 @@ describe("validate-manifest", () => { // Either a missing-discriminator error, or a "type" path issue. expect(formatted).toMatch(/type|discriminator/i); }); + + it("rejects cli variant with shell metacharacters in cliCommand", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + type: "cli", + cliCommand: + "databricks warehouses list --profile --output json; rm -rf /", + selectField: ".id", + }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("cliCommand"); + expect(formatted).toMatch(/shell metacharacters/i); + }); + + it("rejects cli variant with shell metacharacters in shortcut", () => { + const result = validateManifest( + buildManifestWithDiscovery("sql_warehouse", "CAN_USE", { + type: "cli", + cliCommand: + "databricks warehouses list --profile --output json", + selectField: ".id", + shortcut: + "databricks warehouses get --profile | tail -n1", + }), + ); + expect(result.valid).toBe(false); + const formatted = formatValidationErrors(result.errors ?? []); + expect(formatted).toContain("shortcut"); + expect(formatted).toMatch(/shell metacharacters/i); + }); }); describe("scaffolding rule item maxLength (Phase 6)", () => { diff --git a/packages/shared/src/schemas/manifest.ts b/packages/shared/src/schemas/manifest.ts index 7e55977fa..51cd5f9ca 100644 --- a/packages/shared/src/schemas/manifest.ts +++ b/packages/shared/src/schemas/manifest.ts @@ -174,6 +174,16 @@ export const kindDiscoveryDescriptorSchema = z "Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.", ); +/** + * Shell metacharacters rejected on free-form CLI command strings. Catches the + * common foot-guns (statement separators, pipes, redirects via shell, command + * substitution, newlines). Not a security boundary on its own — executors must + * always pass these via argv, never shell-exec the string. Angle brackets are + * permitted because `` (and future `<…>` placeholders) are part of + * the command-template convention. + */ +const SHELL_METACHAR_RE = /[;|&`$\n\r]/; + export const cliDiscoveryDescriptorSchema = z .object({ type: z @@ -184,7 +194,7 @@ export const cliDiscoveryDescriptorSchema = z cliCommand: z .string() .describe( - "Databricks CLI command that lists resources. Must include placeholder.", + "Databricks CLI command that lists resources. Must include . Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map.", ), selectField: z .string() @@ -207,7 +217,7 @@ export const cliDiscoveryDescriptorSchema = z .string() .optional() .describe( - "Single-value fast-path command that returns exactly one value, skipping interactive selection.", + "Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.", ), }) .strict() @@ -215,8 +225,22 @@ export const cliDiscoveryDescriptorSchema = z message: "must include placeholder", path: ["cliCommand"], }) + .refine((descriptor) => !SHELL_METACHAR_RE.test(descriptor.cliCommand), { + message: + "must not contain shell metacharacters (;|&`$ or newlines); use the `kind` variant for typed Databricks resources", + path: ["cliCommand"], + }) + .refine( + (descriptor) => + descriptor.shortcut === undefined || + !SHELL_METACHAR_RE.test(descriptor.shortcut), + { + message: "must not contain shell metacharacters (;|&`$ or newlines)", + path: ["shortcut"], + }, + ) .describe( - "Discovery via a free-form Databricks CLI command. Used when no well-known resourceKind fits.", + "Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions.", ); export const discoveryDescriptorSchema = z