diff --git a/.github/macos-test-quarantine.txt b/.github/macos-test-quarantine.txt index 3988ba7bd..2c9670781 100644 --- a/.github/macos-test-quarantine.txt +++ b/.github/macos-test-quarantine.txt @@ -14,7 +14,6 @@ MCPBridgeIntegrationTests # host (only plugin metadata loads), so driver-backed SQL generation returns empty. TableOperationsPluginTests TableQueryBuilderFilteredQueryTests -FilterSQLGeneratorTests SQLCompletionProviderTests SQLStatementGeneratorPKRegressionTests SQLStatementGeneratorCompositePKTests @@ -31,7 +30,6 @@ ValidateDriverDescriptorTests PluginCapabilityTests FreeTDSClassifierTests DatabaseTypeCassandraTests -RowOperationsManagerBinaryCopyTests RowOperationsManagerTests StructureGridDelegateAddRowTests SaveCompletionTests @@ -39,7 +37,6 @@ ChangeReapplyVersionTests # --- Persistence-entangled: tests assume lowercase enum values but rawValues are # display-style ("Red"/"Private Key"); changing them needs a Codable migration. -ConnectionSharingTests ConnectionURLFormatterSSHProfileTests DatabaseConnectionExternalAccessTests DataGridSettingsDefaultSortDecoderTests @@ -50,8 +47,6 @@ MCPPairingServiceTests MCPBearerTokenAuthenticatorTests TablePlusImporterTests SequelAceImporterTests -DBeaverImporterTests -ForeignAppImporterRegistryTests HostKeyStoreTests KeychainHelperTests @@ -60,8 +55,6 @@ StructureChangeManagerUndoTests DataChangeManagerExtendedTests DataChangeManagerTests CoordinatorEditorLoadTests -CoordinatorColumnVisibilityTests CommandActionsDispatchTests -SidebarViewModelTests ChatToolSpecCopilotTests SQLStatementScannerLocatedTests diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e0d8cffe2..8b0cc4ad2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,54 +32,8 @@ jobs: test: name: macOS Tests - runs-on: macos-26 - timeout-minutes: 40 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '26.4.1' - - - name: Install xcbeautify - run: brew list xcbeautify &>/dev/null || brew install xcbeautify - - - name: Download static libraries - env: - GH_TOKEN: ${{ github.token }} - run: scripts/download-libs.sh --force - - - name: Create Secrets.xcconfig - env: - ANALYTICS_HMAC_SECRET: ${{ secrets.ANALYTICS_HMAC_SECRET }} - run: echo "ANALYTICS_HMAC_SECRET = ${ANALYTICS_HMAC_SECRET}" > Secrets.xcconfig - - - name: Run package tests - run: swift test --package-path Packages/TableProCore - - - name: Run app tests - run: | - set -o pipefail - SKIP_ARGS=() - while IFS= read -r line; do - suite="${line%%#*}" - suite="$(echo "$suite" | xargs)" - [ -z "$suite" ] && continue - SKIP_ARGS+=("-skip-testing:TableProTests/$suite") - done < .github/macos-test-quarantine.txt - xcodebuild test \ - -project "$XCODE_PROJECT" \ - -scheme "$XCODE_SCHEME" \ - -destination "platform=macOS" \ - -only-testing:TableProTests \ - "${SKIP_ARGS[@]}" \ - -parallel-testing-enabled NO \ - -skipPackagePluginValidation \ - CODE_SIGNING_ALLOWED=NO \ - | xcbeautify --renderer github-actions + uses: ./.github/workflows/macos-tests.yml + secrets: inherit build-arm64: name: Build ARM64 diff --git a/.github/workflows/contract-drift.yml b/.github/workflows/contract-drift.yml deleted file mode 100644 index fd23ccb57..000000000 --- a/.github/workflows/contract-drift.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Contract Drift - -on: - pull_request: - paths: - - "Plugins/TableProPluginKit/**" - - "Packages/TableProCore/Sources/TableProPluginKit/**" - - "Packages/TableProCore/Sources/TableProModels/**" - - "TablePro/Models/**" - - "TableProMobile/**" - - "scripts/audit-refactor-health.sh" - - ".github/duplicate-contract-baseline.txt" - - ".github/workflows/contract-drift.yml" - push: - branches: [main] - paths: - - "Plugins/TableProPluginKit/**" - - "Packages/TableProCore/Sources/TableProPluginKit/**" - - "Packages/TableProCore/Sources/TableProModels/**" - - "TablePro/Models/**" - - "TableProMobile/**" - - "scripts/audit-refactor-health.sh" - - ".github/duplicate-contract-baseline.txt" - - ".github/workflows/contract-drift.yml" - workflow_dispatch: - -concurrency: - group: contract-drift-${{ github.ref }} - cancel-in-progress: true - -jobs: - drift: - name: Shared contract drift - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v4 - - name: Fail on new duplicate-contract drift - run: scripts/audit-refactor-health.sh --check diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index 4c3bacae2..a6c3e82a1 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -21,6 +21,7 @@ on: - "Libs/**" - ".github/workflows/macos-tests.yml" workflow_dispatch: + workflow_call: # Only one run per PR/branch at a time; new pushes cancel pending older ones. concurrency: diff --git a/.github/workflows/pluginkit-abi.yml b/.github/workflows/pluginkit-abi.yml deleted file mode 100644 index 33ec9daa5..000000000 --- a/.github/workflows/pluginkit-abi.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: PluginKit ABI Gate - -on: - pull_request: - types: [opened, synchronize, reopened, labeled, unlabeled] - paths: - - "Plugins/TableProPluginKit/**" - - "scripts/check-pluginkit-abi.sh" - - ".github/workflows/pluginkit-abi.yml" - -jobs: - abi-gate: - name: PluginKit ABI Gate - runs-on: macos-26 - timeout-minutes: 20 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26.4.1" - - - name: Create Secrets.xcconfig - run: touch Secrets.xcconfig - - - name: Check PluginKit ABI vs base - env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - ABI_ACKNOWLEDGED_ADDITIVE: ${{ contains(github.event.pull_request.labels.*.name, 'abi-additive') && '1' || '' }} - run: scripts/check-pluginkit-abi.sh "$BASE_SHA" diff --git a/CLAUDE.md b/CLAUDE.md index e10e1ca06..ffb0822de 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -104,11 +104,11 @@ 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). +**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. Before any PluginKit change run `scripts/check-pluginkit-abi.sh` (see below) and act on the result: either the diff is additive (verify no symbol disappeared) 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.) +**ABI check** (manual): `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 machines never produces a false diff. Run it before merging any change under `Plugins/TableProPluginKit/**`, comparing against the merge base. A reported diff is a real ABI change: additive needs no bump; breaking needs the version bump above plus `release-all-plugins.sh`. (Until Library Evolution is on the base too, the base emits no interface and the check passes as a bootstrap.) **Post-ABI-bump checklist (mandatory, breaking bumps only)**: Bumps are now rare (only the breaking changes listed above). After one, every registry-published plugin must be rebuilt against the new ABI. Run `release-all-plugins.sh` for the new version BEFORE or WITH the app release, never after, or users on the new app hit `noCompatibleBinary` until the registry catches up. App auto-update reconciliation handles the user-facing recovery, but the registry has to carry binaries for the new PluginKit version first. diff --git a/TableProTests/Core/Plugins/PluginKitABIResilienceTests.swift b/TableProTests/Core/Plugins/PluginKitABIResilienceTests.swift index d0f63f6c1..06c8765ec 100644 --- a/TableProTests/Core/Plugins/PluginKitABIResilienceTests.swift +++ b/TableProTests/Core/Plugins/PluginKitABIResilienceTests.swift @@ -8,7 +8,7 @@ // documented value. If a requirement is ever added without a default, this file // (and FakeMSSQLPluginDriver) stops compiling, flagging a breaking change that // needs a currentPluginKitVersion bump. Cross-binary load compatibility itself -// is enforced by scripts/check-pluginkit-abi.sh in CI. +// is checked by scripts/check-pluginkit-abi.sh, run manually before merging. // import Foundation