Skip to content

Resolve bundle extends at build time - #258

Merged
coryodaniel merged 18 commits into
mainfrom
bundle-inheritance
Oct 19, 2026
Merged

coryodaniel merged 18 commits into
mainfrom
bundle-inheritance

Conversation

@coryodaniel

@coryodaniel coryodaniel commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Implements the CLI side of massdriver-cloud/massdriver#3516. Backend validation and the provides API field land in the paired massdriver PR; docs in massdriver-cloud/docs#235.

New functionality

  • provides: an optional top-level key listing what a bundle offers. runtime is the only accepted value; other values are deferred until their names are settled.
  • provides lint (mass bundle lint / publish):
    • unknown or duplicate values fail
    • a bundle cannot both provide runtime and set app.runtime
  • Runtimes and applications: an application sets app.runtime: <runtime>@<constraint>. build, lint, and publish resolve the newest matching runtime, pull it, check it, and merge the application onto it. The result publishes as an ordinary bundle.
  • Runtime check after pull: before merging, the pulled parent must provide runtime, not set app.runtime itself, and use dependencies/resources rather than legacy connections/artifacts.
  • Merge rules:
    • the runtime owns steps, params, ui, its resources, and the IaC
    • the application may set name, version (required, semver), description, source_url, schema, provides, dependencies, resources, steps, and app (runtime, envs, secrets only); name, description, source_url, and schema must be strings; any other key, including a null like params:, fails the build
    • application dependencies, app.envs, app.secrets, and resources are added to the runtime's; any name collision fails
    • the merged bundle carries the application's provides, never the runtime's
    • app.envs must reference params and dependencies in the merged bundle; reading an optional dependency warns
    • README.md, CHANGELOG.md, icon.svg, operator.md from the application replace the runtime's
  • Resources emitted by applications: an application declares resources and binds each to a runtime step with a jq template at steps[].resources.<name>.template. Templates stay in the published massdriver.yaml for the backend to render after the step applies.
  • Pinned runtime: the published massdriver.yaml pins app.runtime to the exact runtime version it was built on, e.g. lambda@~1.2 becomes lambda@1.2.7.
  • Build directory: build assembles into <application>/.massdriver/build/ (git-ignored); lint and publish use a temp dir. Bundles without app.runtime are untouched.
  • publish returns errors on lint failure instead of os.Exit(1), so the temp build directory is cleaned up.

Examples

A runtime:

name: lambda
version: 1.2.7
provides: [runtime]

app:
  envs:
    LOG_LEVEL: .params.log_level

An application that runs on it and emits its own resource:

name: billing-api
version: 0.1.0

dependencies:
  db:
    resource_type: postgres@~1
    required: true

resources:
  billing:
    resource_type: billing-api@1.0.0
    required: true

steps:
  - path: src
    resources:
      billing:
        template: |
          { url: .outputs.function_url, region: .params.region }

app:
  runtime: lambda@~1.2
  envs:
    DB_HOST: .dependencies.db.host
  secrets:
    STRIPE_KEY:
      required: true
$ mass bundle publish --development
Using runtime lambda@1.2.7 (~1.2, sha256:...)
...
 ✓ Provides check passed.

Published app.runtime:

app:
  runtime: lambda@1.2.7

Rejected:

$ mass bundle build   # app.runtime points at a bundle without provides: [runtime]
Error: lambda@1.2.7 does not provide a runtime. Add `runtime` to `provides` in its massdriver.yaml and republish

$ mass bundle lint    # provides: [compute]
provides: 'compute' is not one of runtime

$ mass bundle build   # application step sets more than resources
Error: a step in a bundle with 'app.runtime' may only set path and resources; remove: provisioner

Notes

  • The runtime is resolved with Bundles.Get("name@constraint"), the same resolver deploys use, rather than ociRepo.releaseChannels: the dev-channel SQL there orders version_pre_release ASC, so ~X+dev channels point at the oldest dev build.

🤖 Generated with Claude Code

A bundle can name a parent with extends: <bundle>@<constraint>. Build,
lint, and publish pull the newest matching parent, merge the child's
identity, dependencies, and app wiring over it, and publish one ordinary
bundle carrying the parent's IaC, so application developers keep a single
massdriver.yaml instead of cloning a bundle per app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread docs/generated/mass_bundle_build.md Outdated
mass bundle build [path] [flags]
```

## Extending a bundle

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extending shouldn't be the 'default' docs explanation. Include a regular build/publish example and we'll put the extending in docs.massdriver.cloud.

Comment thread docs/generated/mass_bundle_publish.md Resolved
Comment thread docs/helpdocs/bundle/build.md Resolved
Comment thread docs/helpdocs/bundle/publish.md Resolved
coryodaniel and others added 4 commits October 9, 2026 12:07
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
coryodaniel and others added 10 commits October 15, 2026 13:04
Leaves room for per-resource settings later without breaking published bundles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bundle opts in with provides_runtime: true and an app block; an
application names it with extends_runtime. Applications cannot run on
other applications, and a bundle cannot be both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
provides is a closed set the catalog can rely on; each value carries its
own publish rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The other values are deferred until their names are settled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runtimes are often leaf nodes with nothing to emit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chrisghill and others added 2 commits October 19, 2026 14:18
Scalar and null values for unlisted keys were copied over the runtime's,
so `params:` alone wiped the runtime's params.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coryodaniel
coryodaniel merged commit ca6c752 into main Oct 19, 2026
6 checks passed
@coryodaniel
coryodaniel deleted the bundle-inheritance branch October 19, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants