{"slug": "massdriver-platform-update-november-2026", "title": "Massdriver Platform Update: November 2026", "summary": "Massdriver's November 2026 platform release introduces runtimes, letting a platform team write the infrastructure-as-code for how applications run once while developers ship each application as a single massdriver.yaml file, with `mass bundle build`, `lint` and `publish` resolving the newest runtime in a declared version range and merging the application onto it. The release also adds OIDC cloud credentials for deployments, custom default tags, proposal review in the config form, cloud resource import from Claude Code and opencode, search and sort for SCIM-synced members and groups, inspectable and deletable CI service accounts, projects copied from an environment, and config forms that collapse long lists and validate input before deploy. The Platform Engineering Podcast also moved to massdriver.cloud/podcast, where all 52 episodes now live with show notes and full transcripts.", "body_md": "Upcoming Workshop: Citizen Developer Agentic Software Factory on AWS 11.4.26 @ 12PM PST\n\n[Register Here!](https://streamyard.com/watch/umMggUFkVMNu)\n\nThe Platform Engineering Podcast moves to massdriver.cloud, applications that run on runtimes your platform team owns, OIDC cloud credentials for deployments, custom default tags, proposal review in the config form, cloud resource import from Claude Code and opencode, search and sort for members and groups, service accounts you can inspect and delete, projects copied from an environment, and config forms that collapse long lists and check your input before you deploy.\n\nThe Platform Engineering Podcast has a new home at [massdriver.cloud/podcast](https://www.massdriver.cloud/podcast), with new artwork. All 52 episodes are there, with show notes and full transcripts. Links to platformengineeringpod.com open the same episode on the new site, and your podcast app subscription keeps working.\n\nNew episodes air soon. If you have an idea for a show or a guest, email [cory@massdriver.cloud](mailto:cory@massdriver.cloud).\n\nIf you like the show, please rate it on [Apple Podcasts](https://podcasts.apple.com/us/podcast/platform-engineering-podcast/id1729594542) and [Spotify](https://open.spotify.com/show/5VDRu9cn4flUpCaVWOZalW).\n\nOctober made seats follow your identity provider. This release adds runtimes: your platform team writes the IaC for how applications run once, and developers ship an application as one `massdriver.yaml`. It also makes large organizations easier to manage. You can search the people and groups that SCIM syncs in, see who belongs to what, and inspect and delete the CI service accounts you no longer use.\n\n**Write how applications run once. Ship each application as one file.**\n\nA **runtime** is a bundle that defines how applications run on a cloud, for example a Lambda function, a Kubernetes deployment, or an ECS service. Its IaC decides scheduling, networking, IAM, and logging. Developers cannot change it.\n\nAn **application** is a bundle that runs on a runtime. It has no IaC of its own. It adds only its name and version, the dependencies it needs, the environment variables and secrets it reads, and the resources it emits.\n\nA runtime declares `provides: [runtime]` and an `app` block that its IaC reads:\n\n```\nname: lambda\nversion: 1.2.7\nprovides: [runtime]\n\napp:\n  envs:\n    LOG_LEVEL: .params.log_level\n```\n\nAn application names the runtime with a version range:\n\n```\nname: billing-api\nversion: 0.1.0\n\ndependencies:\n  db:\n    resource_type: postgres@~1\n    required: true\n\nresources:\n  billing:\n    resource_type: billing-api@1.0.0\n    required: true\n\nsteps:\n  - path: src\n    resources:\n      billing:\n        template: |\n          { url: .outputs.function_url, region: .params.region }\n\napp:\n  runtime: lambda@~1.2\n  envs:\n    DB_HOST: .dependencies.db.host\n  secrets:\n    STRIPE_KEY:\n      required: true\n```\n\n`mass bundle build`, `lint`, and `publish` resolve the newest runtime in the range, pull it, and merge the application onto it. The result publishes as an ordinary bundle, so it deploys, versions, and upgrades the same as every other bundle.\n\n``` bash\n$ mass bundle publish --development\nUsing runtime lambda@1.2.7 (~1.2, sha256:...)\n...\n ✓ Provides check passed.\n```\n\nHow the merge works:\n\n`steps`, `params`, `ui`, its own `resources`, and the IaC.` dependencies`, `app.envs`, and `app.secrets` to the runtime's. If a name is used in both, the build fails.`template`. Massdriver renders the template after that step applies.` lambda@~1.2` is published as `lambda@1.2.7`, so a later runtime release does not change a published application. Publish the application again to move it to the newer runtime.\nPublish checks both sides:\n\n`provides` accepts only `runtime` today, and each value once`app` block`app.runtime`\n\n```\nlambda@1.2.7 does not provide a runtime. Add `runtime` to `provides` in its massdriver.yaml and republish.\n```\n\nOn the API, `Bundle.provides` returns `[RUNTIME]` for a runtime. Runtimes need CLI 2.5.0 or later. [Runtimes and applications](https://docs.massdriver.cloud/bundle-development/runtimes-and-applications) in the docs covers the full merge rules.\n\n**Deployments can sign in to your cloud with short-lived OIDC tokens.**\n\nMassdriver is now an OIDC identity provider for deployments. Each deployment gets four signed tokens, one for each level: organization, project, environment, and instance. Your cloud trusts Massdriver's public keys and gives a role to the tokens that match your rules. The provisioner gets cloud credentials for the deployment, and you do not store a long-lived key in Massdriver.\n\nEach level's subject adds to its parent's subject:\n\n```\norg:acme\norg:acme:project:ecomm\norg:acme:project:ecomm:env:prod\norg:acme:project:ecomm:env:prod:instance:db\n```\n\nYou pick the level in your trust rule. A project-level rule lets developers fork environments and make preview environments with no new cloud credential. An instance-level rule gives a role to one instance only.\n\nEvery token has `aud` set to `massdriver:<organization>`. Tokens also carry `level`, `organization`, `project`, `environment`, `instance`, and `deployment_id`, so platforms that match on any claim (GCP, Vault, Kubernetes) can use them.\n\nThe provisioner step gets each token as a file:\n\n| Environment variable | File | \n|---|---|\n| `MASSDRIVER_OIDC_ORGANIZATION_TOKEN_FILE` | `/massdriver/oidc/organization.jwt` | \n| `MASSDRIVER_OIDC_PROJECT_TOKEN_FILE` | `/massdriver/oidc/project.jwt` | \n| `MASSDRIVER_OIDC_ENVIRONMENT_TOKEN_FILE` | `/massdriver/oidc/environment.jwt` | \n| `MASSDRIVER_OIDC_INSTANCE_TOKEN_FILE` | `/massdriver/oidc/instance.jwt` | \n\nFor example, an AWS role that every deployment in the `ecomm` project can assume:\n\n```\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Federated\": \"arn:aws:iam::123456789012:oidc-provider/api.massdriver.example.com\"\n      },\n      \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"api.massdriver.example.com:aud\": \"massdriver:acme\",\n          \"api.massdriver.example.com:sub\": \"org:acme:project:ecomm\"\n        }\n      }\n    }\n  ]\n}\n```\n\nThe bundle's AWS provider reads the project token:\n\n```\nprovider \"aws\" {\n  region = \"us-east-1\"\n\n  assume_role_with_web_identity {\n    role_arn                = \"arn:aws:iam::123456789012:role/ecomm-deployer\"\n    web_identity_token_file = \"/massdriver/oidc/project.jwt\"\n  }\n}\n```\n\nSelf-hosted installs turn this on in the Helm chart with `provisioner.identityTokens.signingKey`, an RSA private key in PEM format, or with `existingSecret`. With no key, no tokens are issued and nothing changes.\n\n| Value | Default | Purpose | \n|---|---|---|\n| `existingSecret` | `\"\"` | A Secret you manage, with a `signingKey` key and an optional`previousPublicKeys` key | \n| `signingKey` | `\"\"` | RSA private key (PEM). When `existingSecret` is empty, the chart creates the Secret. | \n| `previousPublicKeys` | `\"\"` | Public keys to keep in the JWKS while you rotate keys | \n| `issuer` | `\"\"` | Token issuer. Defaults to the API URL. Set it when the discovery documents are on another host. | \n| `ttlMinutes` | `120` | Token lifetime in minutes | \n\nDiscovery is at `/.well-known/openid-configuration` and `/.well-known/jwks.json`.\n\nSubjects start with the organization identifier, so a deleted organization's identifier is now retired. No new organization can take it and match the old organization's trust rules.\n\n**Choose the attributes that Massdriver tags your cloud resources with.**\n\nEvery deployment gets a set of default tags in `md_metadata.default_tags`, and bundles apply them to the cloud resources they create. Until now, every organization got the same tags: `managed-by`, `md-project`, `md-target`, `md-manifest`, and `md-package`. Now your organization picks which attributes go in that set. Use system attributes such as `md-project` and `md-environment`, or your own custom attributes such as `cost_center`.\n\nAdmins choose the tags on the new **Default tags** tab in organization settings. `managed-by` and `md-instance` are always sent. Each other attribute has a switch. When you create a project, environment, or component attribute, you can select **Send as a default tag**.\n\nYou can also set the list with the API:\n\n```\nmutation {\n  updateOrganizationSettings(\n    organizationId: \"acme\"\n    input: {\n      defaultTags: [\n        \"md-project\"\n        \"md-environment\"\n        \"md-component\"\n        \"md-instance\"\n        \"cost_center\"\n      ]\n    }\n  ) {\n    result {\n      id\n    }\n  }\n}\n```\n\nThe value of each tag is the attribute's value on the instance, merged from the project, the environment, and the instance. An attribute with no value on an instance is left out of that instance's tags. Two tags are always sent:\n\n| Tag | Value | \n|---|---|\n| `managed-by` | `massdriver` | \n| `md-instance` | The instance identifier, for example `api-prod-database` | \n\nThe `effectiveAttributes` query lists the system and custom attributes that apply to a scope, such as `INSTANCE`, so you can see what you can select.\n\nSome rules:\n\n`_`, and `-`.\nNo organization's tags change until an admin sets `defaultTags`. The [Default tags](https://docs.massdriver.cloud/concepts/organization-settings#default-tags) docs have the full rules.\n\n**Costs and alarms follow `md-instance`.** Massdriver now finds the instance for cost data and alarms by the `md-instance` tag, and uses the old `md-package` tag when `md-instance` is not there. This covers AWS Cost and Usage Reports, Azure cost exports, CloudWatch, Google Cloud Monitoring, Azure Monitor, and Prometheus Alertmanager. Instances keep their cost history while their resources move from one tag to the other.\n\nBefore you switch to custom default tags, read what each integration needs:\n\n`md-instance` as a cost allocation tag in the payer account before you switch.`md-package` must filter on **Import existing cloud resources with `/massdriver:import`.**\n\nThe [Massdriver Claude Code plugin](https://github.com/massdriver-cloud/claude-plugins) has a new command for cloud resources that already exist: created by hand, by another IaC tool, or in another account.\n\n``` python\n/massdriver:import existing production RDS Postgres instance created by hand\n```\n\nThe command asks how you want to import before it starts:\n\nPaths 1 and 2 put the resource under Massdriver's IaC. The agent runs `tofu import` against the instance's Massdriver state backend. It does not use `import {}` blocks, because those put one resource ID into source that every instance shares. Plans run in the Massdriver provisioner, not on your machine. The agent imports, publishes, and plans again until the plan shows no changes. Then it proposes a deployment with those params. Nothing deploys until a person approves the proposal.\n\n**Massdriver for opencode.** The same skill, agents, and commands now run in [opencode](https://opencode.ai). Add the plugin to `opencode.json`:\n\n```\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"plugin\": [\"@massdriver/opencode\"]\n}\n```\n\nThe plugin registers the Massdriver MCP server and adds `/massdriver:develop`, `/massdriver:gen`, `/massdriver:test-upgrade`, `/massdriver:import`, and `/massdriver:architect`. It has the same safety guard as the Claude Code plugin:\n\n`--development`` update_instance`, secrets, environment defaults, and proposals\nAn environment is production when its ID matches `(prod|production)`. You can change the pattern in `.opencode/massdriver.local.md`. Setup, credentials, and options are in the [opencode plugin README](https://github.com/massdriver-cloud/opencode-plugin).\n\n**Search, sort, and page through members and groups on the server.**\n\nThe Members and Groups tables in organization settings used to show 20 rows with no search. One customer had to go to page 37 to find their Org Admins group.\n\nBoth tables now have:\n\nSearch, sort, and page size are kept in the URL, so a reload or a shared link opens the same view.\n\n**See a member's groups.** Click a member's email to open their detail view. It shows their source, whether they hold a seat, and every group they belong to in the organization. Admins who can manage groups can remove the member from a group on this page.\n\n**See your own groups.** The **Organizations** tab on your account page lists the groups you belong to in every organization. Click one or more organization cards to filter the table. Pending invitations show in a bar at the top of the tab.\n\nOn the API:\n\n`Organization.members` takes `filter: { search }` and `sort: { field: EMAIL | NAME, order }`` Organization.member(id)` returns one member`groups` takes `filter: { search, accountId, serviceAccountId }`` viewer.groups` lists your groups across organizations, with an `organizationId` filter`ServiceAccount.lastUsedAt` shows when a service account last authenticated a request, and service accounts sort by `LAST_USED_AT`\n\n```\nquery {\n  organization(organizationId: \"acme\") {\n    members(filter: { search: \"ada\" }, sort: { field: NAME, order: ASC }) {\n      items {\n        email\n        holdsSeat\n        source\n      }\n    }\n  }\n}\n```\n\n**Starter groups for new organizations.** New organizations get six groups with policies already set: Organization Viewers, Developers, Production Deployers, Platform Engineers, CI / Automation, and Billing. They have no members, and you can rename them, delete them, or change their policies. Developers can propose, and Production Deployers approve in environments where `md-environment` is `prod` or `production`. The [access control](https://docs.massdriver.cloud/platform-operations/security/access-control) docs list each group's policies.\n\n**The built-in organization viewer group is retired.** It had no policies, so its members could do nothing. Each existing viewer group is now a custom group with the same name and members, and it has an allow policy for `project:view`, `repo:view`, and `resource:view`. OIDC autojoin now adds new users to the admin group, and it does not change the groups of users who are already members. `ORGANIZATION_VIEWER` is removed from the `GroupRole` and `ViewerGroupType` enums.\n\n**See what a service account can do, and when it was last used.**\n\nClick a service account's name to open its detail page. It shows:\n\nYou can edit and delete the service account on the same page. The Service accounts table has a sortable **Last used** column. A warning icon marks accounts that have not been used in more than 90 days, so you can find and delete old CI credentials.\n\n**A service account that has deployed can now be deleted.**\n\nBefore this release, deleting a service account that had ever run a deployment failed with a server error. A service account exists to deploy from CI, so almost every service account in use could not be deleted.\n\nWhen you delete a service account now:\n\n| Record | Result | \n|---|---|\n| Deployments | Kept. The deployment still names the deployer. | \n| Audit logs | Kept. | \n| Pending environment deployments | Stopped and deleted. | \n| Access tokens | Deleted. | \n| Group memberships | Deleted. | \n\nDeleting a compromised service account stops the environment rollouts it started. A deployment that is already running finishes, because it uses its own deployment token.\n\nEach deployment now stores the name of the account or service account that started it, so `deployedBy` shows a name after the deployer is deleted. In the audit log, a deleted actor shows as `service_account:<id>` or `account:<id>`. Paste that value into the actor filter to find everything else it did.\n\nThe disable and reactivate actions for service accounts are removed. No API field used them.\n\n**Manage service accounts with Terraform.** Terraform provider 2.4.0 adds `massdriver_service_account` and `massdriver_service_account_group_membership`. The token is a sensitive attribute that is set only when the service account is created. To rotate it, replace the resource:\n\n```\nresource \"time_rotating\" \"ci_token\" {\n  rotation_days = 90\n}\n\nresource \"massdriver_service_account\" \"ci\" {\n  name                     = \"ci-deployer\"\n  token_expiration_minutes = 60 * 24 * 100\n\n  lifecycle {\n    replace_triggered_by = [time_rotating.ci_token]\n  }\n}\n\nresource \"massdriver_service_account_group_membership\" \"ci\" {\n  group_id           = massdriver_group.ci.id\n  service_account_id = massdriver_service_account.ci.id\n}\n```\n\n**Configure one environment, then copy it into as many new projects as you need.**\n\n`createProjectFromEnvironment` creates a new project with the source project's blueprint and one environment. Each new instance uses the same bundle version and release strategy as the source instance. Use it to give each developer their own project, or to start a new service from an environment that already works.\n\n`cloneProject` copies only the blueprint, so every instance in the new project started empty. `copyParams` controls which params the new instances get:\n\n`NONE`` CONFIGURED``LATEST`\n\n```\nmutation {\n  createProjectFromEnvironment(\n    organizationId: \"acme\"\n    sourceEnvironmentId: \"template-dev\"\n    input: {\n      id: \"payments\"\n      name: \"Payments\"\n      environment: { id: \"dev\", name: \"Development\" }\n      copyParams: LATEST\n    }\n  ) {\n    successful\n    result {\n      id\n    }\n  }\n}\n```\n\nFields marked `$md.copyable: false` are never copied, the same as fork and copy. Secrets, remote references, and environment defaults are not copied. If any step fails, nothing is created.\n\nYou need `project:view` on the source project, `project:create` for the new project's attributes, and `environment:create` for the new environment.\n\nIn the UI, the environment header has **Copy to New Project**. It opens a form with the new project's name, attributes, and environment.\n\n**Clone Project** on the project page now asks what to copy: the full project, or one environment.\n\n**Long lists collapse.** Every array in a config form shows each item as a header you can collapse. Each array has **Expand all** and **Collapse all**. A collapsed item with a validation error shows an error icon in its header, so you can find the item to fix without opening each one.\n\nThe header uses the item's `name`, `title`, `label`, or `id`, in that order. Bundle authors can set their own header with `itemTitle` in the UI schema:\n\n```\nui:\n  subnets:\n    ui:options:\n      itemTitle: '{{cidr}} ({{availability_zone}})'\n```\n\nWith that setting, each subnet header reads `10.0.0.0/24 (us-east-1a)`.\n\n**Input is checked before you deploy.** **Deploy** and **Propose** on the Config tab now run the form's checks first. An empty required field or a value that breaks a pattern shows on the field, and nothing is sent. If the server rejects a param, the error shows under that field instead of in a message at the bottom of the screen.\n\n**Review proposals in the config form.** When an instance has proposals and you have `instance:deploy`, the Config tab shows how many are waiting. **Review** opens a picker. The proposal you pick shows in the read-only config form, or as JSON, with **Plan**, **Reject**, and **Approve**. Before this, you could read a proposal only as a table of params in the History tab.\n\nA proposal runs at the bundle version it was proposed on. When that version is not the instance's version, the review shows both, for example `v1.2.0 → v1.3.0`, and the form uses the fields of the proposed version.\n\n`Bundle` type has `paramsSchema` and `uiSchema`. Before this, you could get a bundle's schemas only through an instance on that exact version. `$md.enum` options are filled in only on `Instance.paramsSchema`, because they need an instance.\n\n```\nquery {\n  bundle(organizationId: \"acme\", id: \"aws-rds-postgres@1.4.0\") {\n    paramsSchema\n    uiSchema\n  }\n}\n```\n\n`massdriver.yaml` can set an RJSF UI schema at `ui.schema`. The import dialog and the resource Settings tab use it to lay out the form, the same way bundle forms use \n\n```\nui:\n  schema:\n    ui:order: [project_id, client_email, private_key, '*']\n    private_key:\n      ui:widget: textarea\n      ui:options:\n        rows: 8\n```\n\nOn the API, `ResourceType.uiSchema` returns it, or `{}` when it is not set.\n\n`exports` can set `fileFormat` to any lowercase letters and digits, not only `yaml`. The UI uses the value as the file extension of the download. For example, a VM resource type can export a shell script that connects to the VM:\n\n```\nexports:\n  - downloadButtonText: SSH script\n    fileFormat: sh\n    templatePath: ./exports/connect.sh.liquid\n    templateLang: liquid\nbash\n#!/bin/sh\nssh {{artifact.payload.user}}@{{artifact.payload.host}}\n```\n\n`$md.list` is a dropdown of the groups, service accounts, or projects in your organization. The saved value is the ID. Each user sees only the options they can view. A value saved by someone else that you cannot view stays in the list as \"You cannot view this project\", and you can keep it, remove it, or replace it. A save fails if it adds a value that you cannot view.\n\n```\nproperties:\n  on_call_teams:\n    title: On-call Teams\n    description: Groups paged for this database's alarms\n    type: array\n    items:\n      type: string\n      $md.list:\n        resource: group\n```\n\nThe [Massdriver annotations](https://docs.massdriver.cloud/bundle-development/schema-design/massdriver-annotations) docs cover `$md.list` and its publish checks.\n\n`GET /v1/resources` with a deployment's token (`MASSDRIVER_TOKEN` in the provisioner) returns every resource the deployment's instance produced. It does not return resources from other instances or imported resources. Other callers get a 403.\n\n```\n[\n  {\n    \"id\": \"pep-dev-index.table\",\n    \"name\": \"Table clips\",\n    \"field\": \"table\",\n    \"resource_type\": \"document-table@0.0.0\",\n    \"version_constraint\": null,\n    \"available_upgrade\": null,\n    \"payload\": {\n      \"name\": \"pep-dev-index-8dwd-clips\",\n      \"partition_key\": \"id\",\n      \"region\": \"us-east-1\"\n    }\n  }\n]\n```\n\n`massdriver.sessionTtlHours` (default `720`, 30 days).\n| Component | Version | \n|---|---|\n| Helm chart | 0.3.0 | \n| Massdriver | 2.6.0 | \n| UI | 2.1.7 | \n| CLI | 2.5.0 | \n| Terraform provider | 2.4.0 | \n\nCLI 2.5.0 adds runtimes and applications to `mass bundle build`, `lint`, and `publish`. Terraform provider 2.4.0 adds service accounts and service account group memberships.\n\nSelf-hosted installs pick up the platform and UI images through the chart. Chart 0.3.0 adds the `provisioner.identityTokens` and `massdriver.sessionTtlHours` values.\n\nSeats follow your identity provider, custom attributes are safe to change, config can be loaded and promoted from any instance, and external resources show on the environment graph.\n\nResource types become versioned OCI artifacts, connections carry version ranges, and deployment approvals get separation of duty.\n\nVersion 1.3.0 of the Massdriver Platform featuring SCIM 2.0, Integrations Console, and Claude Code Plugin.", "url": "https://wpnews.pro/news/massdriver-platform-update-november-2026", "canonical_source": "https://www.massdriver.cloud/blogs/november-2026-changelog", "published_at": "2026-10-28 23:13:59.246883+00:00", "updated_at": "2026-10-28 23:14:01.665814+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": ["Massdriver", "Claude Code", "opencode", "AWS", "Platform Engineering Podcast", "Apple Podcasts", "Spotify"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/massdriver-platform-update-november-2026", "markdown": "https://wpnews.pro/news/massdriver-platform-update-november-2026.md", "text": "https://wpnews.pro/news/massdriver-platform-update-november-2026.txt", "jsonld": "https://wpnews.pro/news/massdriver-platform-update-november-2026.jsonld"}}