Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Registry plugins built before 0.49.0 install and load again instead of failing with an invalid plugin bundle error.

## [0.49.0] - 2026-06-06

### Added
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ When adding a new method to the driver protocol: add to `PluginDatabaseDriver` (

**Additive changes are binary-compatible and need NO version bump**: adding a requirement to `DriverPlugin` / `PluginDatabaseDriver` that has a default implementation, reordering requirements, adding a field to a non-`@frozen` transfer struct, or removing a requirement that defaulted to `nil`.

**Adding a field to a transfer struct is additive ONLY if every existing public initializer keeps its exact signature.** Adding a parameter to an existing public init or function, even with a default value, replaces its mangled symbol and breaks every already-built plugin (this shipped in 0.49.0: `PluginQueryResult` gained `columnMeta:` on its init and every registry plugin failed to load with "Bundle failed to load executable"). Add a NEW overload for the new field and keep the old signature; mark the old overload `@_disfavoredOverload` so new code resolves to the full init while old binaries keep their symbol. A red PluginKit ABI Gate is a merge blocker, never something to merge over: either the diff is additive (verify no symbol disappeared, then add the `abi-additive` label) or it is breaking (bump and re-release).

**Bump `currentPluginKitVersion` (in `PluginManager.swift`) and `TableProPluginKitVersion` in every plugin `Info.plist` ONLY for a breaking change**: changing or removing an existing requirement's signature, adding a requirement without a default, adding a case to a `@frozen` enum, or changing a frozen type's layout. Mark a public enum `@frozen` only when an exhaustive switch over it forces it (the compiler flags the switch) and its case set is genuinely closed; leave the rest non-frozen so they can gain cases. `PluginCapability` stays non-frozen with `@unknown default` because it is a growing capability set, not a closed vocabulary. The driver protocols and transfer structs stay non-frozen so they can grow. The strict version gate in `validateBundleVersions` still rejects a stale plugin cleanly after a breaking bump (no `EXC_BAD_INSTRUCTION`).

**ABI gate**: `scripts/check-pluginkit-abi.sh [base-ref]` builds TableProPluginKit at the current tree and at the base ref with the same toolchain, then diffs their public interfaces. There is no committed baseline, so a Swift version difference between a dev machine and CI never produces a false diff. CI (`.github/workflows/pluginkit-abi.yml`) runs it on every PR that touches `Plugins/TableProPluginKit/**`, comparing against the PR base. A reported diff is a real ABI change: additive needs no bump; breaking needs the version bump above plus `release-all-plugins.sh`. After reviewing a diff as additive, add the `abi-additive` label to the PR; the gate then passes and records the decision. Remove the label if later commits add a breaking change. (Until Library Evolution is on the base too, the base emits no interface and the gate passes as a bootstrap.)
Expand Down
22 changes: 22 additions & 0 deletions Plugins/TableProPluginKit/PluginQueryResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ public struct PluginQueryResult: Codable, Sendable {
self.columnMeta = columnMeta
}

@_disfavoredOverload
public init(
columns: [String],
columnTypeNames: [String],
rows: [[PluginCellValue]],
rowsAffected: Int,
executionTime: TimeInterval,
isTruncated: Bool,
statusMessage: String?
) {
self.init(
columns: columns,
columnTypeNames: columnTypeNames,
rows: rows,
rowsAffected: rowsAffected,
executionTime: executionTime,
isTruncated: isTruncated,
statusMessage: statusMessage,
columnMeta: nil
)
}

public static let empty = PluginQueryResult(
columns: [],
columnTypeNames: [],
Expand Down
Loading