feat: new manifest format#261
Conversation
There was a problem hiding this comment.
Pull request overview
This PR upgrades the AppKit plugin manifest ecosystem to a v2.0 template manifest format to support smarter databricks apps init scaffolding, including field discovery metadata, post-scaffold user instructions, computed field origins, and semantic (cross-field) validation during plugin validate.
Changes:
- Bump template plugin manifest version to
2.0and add a top-levelscaffoldingdescriptor. - Extend plugin/template schemas with
discovery(CLI command-based value discovery) andpostScaffoldsteps; generate/propagate computedorigininto template manifests during sync. - Add semantic validation (dependsOn cycles/dangling refs,
<PROFILE>placeholder, discovery/origin coherence, postScaffold structure) and associated tests/docs updates.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| template/appkit.plugins.json | Updates template manifest to v2.0 and annotates built-in plugins with discovery/origin/postScaffold plus scaffolding descriptor. |
| packages/shared/src/schemas/template-plugins.schema.json | Expands template manifest schema to support v2.0 and scaffolding descriptor; inlines field/requirement defs for origin. |
| packages/shared/src/schemas/plugin-manifest.schema.json | Adds discovery to resource fields and postScaffold to plugin manifests. |
| packages/shared/src/schemas/plugin-manifest.generated.ts | Updates generated TS types to include discovery/postScaffold types. |
| packages/shared/src/plugin.ts | Re-exports new generated types (DiscoveryDescriptor, PostScaffoldStep). |
| packages/shared/src/cli/commands/plugin/validate/validate.ts | Runs semantic validation and formats semantic errors/warnings in CLI output. |
| packages/shared/src/cli/commands/plugin/validate/validate-manifest.ts | Implements semantic validation rules and issue formatting. |
| packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts | Adds test coverage for new semantic validation behavior. |
| packages/shared/src/cli/commands/plugin/sync/sync.ts | Computes/injects origin, bumps template manifest to v2.0, and adds scaffolding descriptor on write. |
| packages/shared/src/cli/commands/plugin/sync/sync.test.ts | Adds unit tests for origin computation. |
| packages/shared/src/cli/commands/plugin/manifest-types.ts | Adds scaffolding descriptor types and exports new manifest-related types. |
| packages/appkit/src/plugins/lakebase/manifest.json | Adds discovery descriptors and postScaffold steps to the lakebase built-in plugin manifest. |
| packages/appkit/src/plugins/genie/manifest.json | Adds schema reference, discovery descriptor, and postScaffold steps to genie plugin manifest. |
| packages/appkit/src/plugins/files/manifest.json | Adds discovery descriptor and postScaffold steps to files plugin manifest. |
| packages/appkit/src/plugins/analytics/manifest.json | Adds discovery descriptor and postScaffold steps to analytics plugin manifest. |
| docs/static/schemas/template-plugins.schema.json | Publishes updated template plugins schema for docs site. |
| docs/static/schemas/plugin-manifest.schema.json | Publishes updated plugin manifest schema for docs site. |
| docs/static/appkit-ui/styles.gen.css | Updates generated UI styles (tailwind output) used by docs UI. |
| docs/docs/api/appkit/Interface.ResourceFieldEntry.md | Documents the new discovery field on ResourceFieldEntry. |
| docs/docs/api/appkit/Interface.PluginManifest.md | Documents the new postScaffold field on PluginManifest. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@atilafassina this one looks ok and evolution, however little heavy code-wise (let me post comments from isaac about these) and mixing concepts little bit. What do you think of this: Alternatives worth weighing Zod as single source of truth (my default for a TS-first ecosystem):
TS-authored manifests compiled to JSON:
Split concerns into two files:
|
keugenek
left a comment
There was a problem hiding this comment.
Overall direction looks right — v2.0 versioning via if/then is correct and co-located manifest.json per plugin is the right locality. A few shape-level concerns worth addressing before or soon after this lands. Happy to chat on any of them.
f27eed4 to
a1c30e3
Compare
…tScaffold, and scaffolding Xavier loop: iteration 1 — Phase 1 (Schema Definitions & Type Generation) Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
…te manifest emission Xavier loop: iteration 2 — Phase 2 (Origin Computation & Sync Enrichment) Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
…, and postScaffold Xavier loop: iteration 3 — Phase 3 (Semantic Validation) Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
…ostScaffold steps Xavier loop: iteration 4 — Phase 4 (Core Plugin Manifest Annotations) Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
…t for origin support 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 <atila@fassina.eu>
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 <atila@fassina.eu>
… strict config 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 <catalog>.<schema>` → `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 <atila@fassina.eu>
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 <atila@fassina.eu>
The `cli` variant of discoveryDescriptor accepts a free-form Databricks
CLI command supplied by the plugin author. With no further constraint
beyond the existing `<PROFILE>` 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 `<PROFILE>` (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 <atila@fassina.eu>
postScaffoldStepSchema.instruction was bounded below (.min(1)) but had no upper bound, while scaffolding.rules.must[] / never[] are capped at 120 chars per phase 6. Same intent applies to postScaffold instructions — they are checklist items, not prose — so add .max(200) with a parse-time error message. 200 (vs 120 for rules.must/never) allows short imperative sentences with placeholders; the longest existing core-plugin instruction is ~120 chars, so all current instructions fit with headroom. Regenerated docs/static/schemas/*.schema.json carry the new maxLength entry on the postScaffold step's instruction field. Two boundary tests added next to the existing "rejects empty postScaffold instruction" test (rejects 201, accepts exactly 200). Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
# Conflicts: # .github/workflows/ci.yml # docs/docs/api/appkit/index.md # docs/docs/api/appkit/typedoc-sidebar.ts # docs/package.json # knip.json # packages/appkit/src/plugins/genie/manifest.json # template/appkit.plugins.json
Delete postScaffold from canonical Zod schema and add plugin-level
scaffolding.rules ({must?, should?, never?}, ≤120 chars each) gated by the
substitutability principle. Adds parity should[] on template-level
scaffoldingRules. Migrates analytics, files, genie, lakebase manifests off
postScaffold per the A5 mapping table.
xavier loop iteration 1 — phase 1 of plugin-manifest-refactor amendment.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Apply the A4 audit from the 2026-05-18 manifest amendment. All 4 prior must[] entries are substitutable (skill content, derivable from requiredByTemplate, derivable from field.env, or belongs on volume discovery descriptor — A6 candidate for a later phase). Net result: must=[], should=[] (parity), never keeps the 3 cross-cutting guardrails. Regression test pins the shape. xavier loop iteration 2 — phase 2 of plugin-manifest-refactor amendment. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Extend RESOURCE_KIND_COMMANDS value shape with parents?: readonly string[].
parents declares free-text prompts the runner must collect before invoking
a kind's listing command — each entry substitutes the matching {name}
placeholder in the command template. Unlike dependsOn (which references a
sibling field), parents covers transient inputs not persisted as fields.
Applies to volume.parents = ['catalog', 'schema'], replacing the prose rule
deleted in the A4 audit. Regression tests pin volume's parents shape and
confirm no other kind currently uses parents.
A6 decisions (b) and (c) accept-as-prose — see ~/.xavier/tasks/plugin-manifest-refactor.md.
xavier loop iteration 3 — phase 3 of plugin-manifest-refactor amendment.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…ecks audit-core-plugin: Step 3.6 — substitutability-gate patterns (permission duplication, existence tautology, inactionable --set, enum-or, length cap); discovery descriptor completeness; RESOURCE_KIND_COMMANDS.parents vs dependsOn consistency. All findings feed Category 1 (Manifest Design). review-core-plugin: Step 5.6 — same gate/discovery checks scoped to changed manifest files only. create-core-plugin: Step 4e.1 — post-scaffold enrichment pass that adds discovery descriptors to user-supplied fields and gates scaffolding.rules against the substitutability principle with 5 reject patterns + 3 legitimate categories. No changes to plugin-best-practices.md or plugin-review-guidance.md reference docs — additions are procedural workflow steps, not narrative rules. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
…tion The original three never entries surfaced false-positive conflicts with plugin setup instructions during integration testing against the databricks-apps skill (Track B PR databricks/databricks-agent-skills#79): - 'Modify files inside the template directory' caught every plugin must rule that edits scaffolded files (genie spaces config, lakebase migrations, analytics queries). The rule was either unreachable (if read as the SDK source-of-truth) or contradictory (if read as the scaffolded output, which the user owns post-init). Deleted. - 'Hardcode workspace-specific values in template files' conflated workspace IDs (which are correctly committed to bundle config) with credentials (which must not be). Replaced with a must/never pair that names the legitimate destinations (app.yaml, databricks.yml, .env) and the leak path (client bundle). - 'Skip resource configuration prompts' conflicted with the --set non-interactive path. Replaced with a should/never pair covering the actual decision-time failures: ask the user when uncertain; never guess when discovery returns zero or multiple options. Net: 0 must + 0 should + 3 never -> 1 must + 1 should + 2 never. All entries describe agent behaviors at decision points (substitutability gate passes), each under 120 chars, and the merged set is precedence- and phase-clean per the skill PR's protocol. Regression test in validate-manifest.test.ts updated to pin the new contents. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
- tools/generate-json-schema.ts, tools/generate-registry-types.ts: drop .ts extensions on relative imports — editors with TS LS configured without allowImportingTsExtensions flagged them as errors (per MarioCadenas on PR #261). tsx resolver accepts both forms; the rest of tools/ already imports without extensions. - lakebase/manifest.json: reword the migrations must rule. Previous text ('pnpm drizzle:migrate') referenced a script that doesn't exist in template/package.json AND a package manager (pnpm) the template doesn't use (template scripts use npm throughout). New text is ORM-agnostic: 'After init, run any database migrations for your chosen ORM before first request'. Preserves the don't-forget-migrations directive without presuming a tool the template doesn't ship. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Phase 5 deleted plugin-manifest.generated.ts (the json-schema-to-typescript output that carried JSDoc converted from Zod .describe() strings). The Omit<GeneratedPluginManifest, ...>-based PluginManifest interface in packages/shared/src/plugin.ts then inherited fields from z.infer<typeof pluginManifestSchema> — a computed type with no JSDoc to render. TypeDoc walks the TS surface (not the JSON Schema), so 10 inherited field descriptions silently vanished from docs/docs/api/appkit/Interface.PluginManifest.md. Redeclare the affected fields locally on PluginManifest with JSDoc copied verbatim from the Zod .describe() text. Restores the rendered descriptions in TypeDoc output. Drift risk acknowledged: if Zod .describe() changes, the JSDoc here stays stale until manually synced. A proper follow-up would either (a) re-emit JSDoc'd .d.ts from the regenerated JSON Schema as a codegen artifact pointed at by TypeDoc, or (b) adopt a Zod-aware TypeDoc plugin. Both out of scope for this PR. Addresses PR #261 review comment from MarioCadenas on docs/docs/api/appkit/Interface.PluginManifest.md. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Multi-line JSDoc comments produced multi-line description text in the TypeDoc-rendered markdown, which git diffed against the original single-line descriptions in main. Functionally identical; just whitespace. Collapsing onSetupMessage, hidden, and stability JSDoc to single-line matches the original markdown formatting verbatim. Net change vs main on docs/docs/api/appkit/Interface.PluginManifest.md is now just the See link swap (deleted plugin-manifest.generated.ts -> Zod source of truth pointer). Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
CI sync:template diff check caught two source/template drifts on 4ae1741: 1. lakebase manifest had been moved must -> should in the source, but the committed template still carried the rule under must. Resyncing now that sync:template is invoked as part of pnpm build. 2. TEMPLATE_SCAFFOLDING declared --template-dir and --config-dir as required: true, but the committed template had them as required: false (per MarioCadenas on PR #261 #6 — these flags aren't actually required by databricks apps init). Flipping the source constant to match; resync now produces consistent output. After this commit, source and template are in lockstep; CI sync:template diff check should pass. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Code comments should describe implementation details, not project-management state. Stripped references to phase numbers (Phase 1-6), the substitutability gate amendment, A4/A5/A6 audit candidates, and Track B PR cross-links from: - packages/shared/src/schemas/manifest.ts (header comment) - packages/shared/src/plugin.ts (re-export comment) - packages/shared/src/cli/commands/plugin/manifest-types.ts (shim header) - packages/shared/src/cli/commands/plugin/schema-resources.ts (header) - packages/shared/src/cli/commands/plugin/validate/validate-manifest.test.ts (describe/it names + inline comments) - tools/generate-json-schema.ts (header — dropped the phase narrative entirely) - tools/generate-registry-types.ts (header) Where the original prose carried an implementation fact worth keeping (transform overwrites input, parents replaces dependsOn for volume, strict object rejects postScaffold, etc.), the fact is preserved phase-free. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Replace stale --template-dir / --config-dir entries with the canonical
databricks apps init flag set, derived from cmd/apps/init.go:
--name (required, with kebab-case pattern), --template, --version,
--features (with no-whitespace pattern), --set, --output-dir,
--description, --run, --auto-approve, --profile
Excluded by design: --branch (niche GitHub-template only, mutually
exclusive with --version), --deploy (post-creation side effect to
shared workspace), --warehouse-id (deprecated), --plugins (hidden
alias for --features).
Addresses PR feedback on template/appkit.plugins.json scaffolding.flags
(thread r3282166233): --name is required by the scaffolder because
{{.projectName}} populates package.json, databricks.yml, and .env.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
scaffolding.flags in templates.md still showed the pre-curation 3-flag placeholder set (--template-dir, --config-dir, --profile). Replace with three representative real flags plus a reference table covering all 10 canonical flags shipped by TEMPLATE_SCAFFOLDING. lakebase scaffolding rules example in manifest.md still used a `must` with Drizzle-specific wording, but the merged lakebase manifest.json has no `must` and uses generic ORM language under `should`. Sync the JSON example and the substitutability-gate prose example to match. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
* docs: document v2.0 manifest contract Add docs/docs/plugins/manifest.md covering the v2.0 plugin manifest: resources, kind/cli discovery descriptors, field dependencies, transient prompts via `parents`, and plugin-level scaffolding rules. Update templates.md to cover the v2.0 template manifest: computed `origin` field, scaffolding descriptor + rules with substitutability gate, and scaffolding.rules propagation from plugin manifests. Update custom-plugins.md to recommend the manifest.json import pattern matching the core plugins. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu> * docs: clarify scaffolding requirement is CLI-enforced, not JSON-Schema Per Copilot review on #370: the published template-plugins.schema.json marks only `version` and `plugins` as top-level required. `scaffolding` is enforced via Zod superRefine (conditional on version=2.0), which JSON Schema cannot express. Reword both claims so readers validating against the published schema alone are not misled. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu> * docs: align manifest v2.0 docs with merged PR #261 schema scaffolding.flags in templates.md still showed the pre-curation 3-flag placeholder set (--template-dir, --config-dir, --profile). Replace with three representative real flags plus a reference table covering all 10 canonical flags shipped by TEMPLATE_SCAFFOLDING. lakebase scaffolding rules example in manifest.md still used a `must` with Drizzle-specific wording, but the merged lakebase manifest.json has no `must` and uses generic ORM language under `should`. Sync the JSON example and the substitutability-gate prose example to match. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu> --------- Signed-off-by: Atila Fassina <atila@fassina.eu>
Summary
Evolves the AppKit plugin manifest contract from v1.0 to v2.0 and swaps the canonical authoring surface from hand-written JSON Schema to Zod via the Standard Schema interface. AJV, the
json-schema-to-typescriptcodegen pipeline, and the standalone semantic-validator delete; refinement and.transform()on Zod schemas replace them.Mid-review (2026-05-18) the design picked up a substitutability-gate amendment that deleted
postScaffoldentirely in favor of a unified plugin-levelscaffolding.rulesblock, and audited every prose rule against the gate. See "2026-05-18 amendment" below.Important
~91% of insertions are auto-regenerated artifacts — two JSON Schema files (
docs/static/schemas/{plugin-manifest,template-plugins}.schema.json) account for+10,400lines..gitattributesmarks all generated artifactslinguist-generated=true. GitHub collapses them in the file list — substantive review surface is ~32 files / ~2,300 insertions.What changes vs
mainContract
discoveryDescriptoris a discriminated union:{ type: "kind", resourceKind, ... } | { type: "cli", cliCommand, ... }. Six known kinds:warehouse,genie_space,volume,postgres_project,postgres_branch,postgres_database.clivariant rejects shell metacharacters (;,|,&, backtick,$, newlines) oncliCommandandshortcut..describe()notes direct authors tokindfor first-party resources and flag theclishape as minimal-and-may-tighten.RESOURCE_KIND_COMMANDSmap next to the schema; commands verified against Databricks CLI v0.299.0.volumekind carriesparents: ["catalog", "schema"]— declares free-text prompts the runner must collect before invoking discovery (replaces the prior prose rule).originon template fields is a.transform()output (drift-impossible).rules.must/rules.should/rules.neveritems capped atmaxLength: 120. Per-bucket and cross-bucket dedup enforced via.superRefine().projectfield.Authoring surface
packages/shared/src/schemas/manifest.ts(Zod schemas +RESOURCE_KIND_COMMANDS+TEMPLATE_SCAFFOLDING).tools/generate-json-schema.ts(Zod → draft-07 JSON Schema, emits todocs/static/schemas/).packages/shared:@standard-schema/spec.Deletions
ajv,ajv-formats,json-schema-to-typescript.packages/shared/src/schemas/{plugin-manifest.schema.json,template-plugins.schema.json,plugin-manifest.generated.ts}.tools/generate-schema-types.ts,docs/scripts/copy-schemas.ts.validate-manifest.ts(498 → 177 lines): AJV plumbing +runSemanticValidation+ helpers.enrichFieldsWithOrigin,validateDiscoveryOrigin.postScaffoldStepSchemaandpostScaffoldfield (post-amendment — see below).Migrated to Zod-direct
schema-resources.tsandtools/generate-registry-types.ts(no more runtime JSON-schema reads).manifest-types.tsis now a re-export shim (z.infertypes +StandardSchemaV1).Plugin Rules (per core plugin,
scaffolding.rules)--set analytics.sql-warehouse.idis runningconfig/queries/has at least one.sqlfile before runningnpm run typegenWRITE_VOLUMEpermission'spaces'map in plugin config with alias-to-Space-ID mappings'pnpm drizzle:migrate'before first request'psql $PGHOST -c "select 1"'Template-level scaffolding.rules
{ "must": [ "Keep all secrets and credentials only in app.yaml, databricks.yml, and/or .env" ], "should": [ "ask user when in doubt of resource to use for plugin" ], "never": [ "guess resources when multiple or no options are available", "embed secrets in files that will go to the client-bundle" ] }Each entry passes the substitutability gate (describes a cross-cutting agent behavior at decision time, not derivable from structured manifest data). Each ≤120 chars. Conflict-detection-safe against the current plugin rule corpus (no
must↔neveroverlaps).2026-05-18 amendment — substitutability gate
The original v2.0 plan shipped
postScaffoldarrays ({ instruction, required }) at the plugin level. During v2.0 implementation we found the format couldn't participate in thedatabricks-appsagent skill's command-construction flow — prose doesn't compose across plugins, duplicates work the skill drives from structured resources, and{ instruction, required }adds zero parseable signal.The amendment introduces one design principle and applies it uniformly:
Changes that landed under the amendment:
postScaffolddeleted.postScaffoldStepSchemaand thepostScaffoldfield removed from bothpluginManifestSchemaand the template plugin schema. All four core plugins migrated to plugin-levelscaffolding.rules.scaffolding.ruleswithmust/should/neverarrays (≤120 chars each, no duplicates within a bucket, no overlap across buckets — all enforced via.superRefine()).scaffoldingRulesSchemagainsshould[]?for parity with plugin-level.mustentries pruned (every one was substitutable: skill content, derivable fromrequiredByTemplate, derivable fromfield.env, or a discovery-descriptor concern). Original 3neverentries replaced with 2 sharper rules covering credentials hygiene and resource-selection decision points; one entry deleted as unreachable. See "Template-level scaffolding.rules" above.RESOURCE_KIND_COMMANDS.volume.parents = ["catalog", "schema"]. Models transient query inputs as a kind-level property without conflating withdependsOn(which references sibling fields).--setderivation for genie spaces → ACCEPT AS PROSE pending Track B confirmation of the skill's consumption model..claude/commands/skill extensionsOutside the
packages/tree but relevant to reviewers — the in-repo plugin commands gained procedural v2.0 semantic checks:audit-core-plugin.md— Step 3.6 (3.6a substitutability-gate patterns, 3.6b discovery descriptor completeness, 3.6cparentsvsdependsOnconsistency). All findings feed Category 1 (Manifest Design).review-core-plugin.md— Step 5.6 with the same checks scoped to changed manifest files only.create-core-plugin.md— Step 4e.1 post-scaffold enrichment that walks each user-supplied field to add adiscoveryblock and gates anyscaffolding.rulesagainst 5 explicit reject patterns.No changes to
plugin-best-practices.mdorplugin-review-guidance.md— the additions are procedural workflow steps, not narrative rules.Track B — consumer skill (cross-repo)
The
databricks-appsskill indatabricks/databricks-agent-skills#79("docs(apps): update AppKit scaffolding setup for Manifest 2.0") adds a new "Scaffolding Rules Protocol" subsection toskills/databricks-apps/SKILL.md(skill version bump0.1.1 → 0.1.2). It consumes the rules emitted by this PR:--featuresplugins + every plugin withrequiredByTemplate: true+ template-levelBefore init/After initprefixesmust↔ templateneverstops the flowSequencing: Track A (this PR) ships first. Track B is opened, not blocking merge — plugin-level rules are advisory metadata in the transition state. Failure mode if Track B lands later: degraded (third-party plugin guardrails don't fire), not broken (resources still drive the build).
End-to-end integration runbook lives in
~/.xavier/research/manifest-2-skill-integration-test.md(vault note, not in this repo).Deferred to follow-up PRs
defineManifest({ ... }))manifest-docs)dependsOnchecksclivariant full hardeningdatabricks-agent-skills#79— opens for skill author review; not required to merge before Track ANote
Plugin authors continue to write
manifest.json. See the docs PR (#370) for the authoring guide.Note on the generated files diff
z.toJSONSchema()has its own key ordering, and that's what now writes the file.$ref: "#/$defs/resourceRequirement"got expanded into giantoneOf: [...]blocks per resource type — the schema's actual shape changed, not just its layout.Biome's JSON formatter only normalizes whitespace; it doesn't reorder keys. So the noise you're seeing is the cost of moving from hand-authored JSON Schema → Zod-generated JSON.