diff --git a/.gitattributes b/.gitattributes index 7fa725c..4b90228 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,4 @@ # Protocol provenance verifies original file bytes on every platform. Sources/CodexAdapter/Resources/Protocol/** -text -# Runtime notice provenance is bound to the original upstream bytes. -Vendor/SwiftWindowsRuntime/** -text # Every platform archive must match the manifest stored at its release tag. computer-mcp-plugin.toml -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c33d22b..33967fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,28 +4,13 @@ on: push: branches: [master] workflow_dispatch: - inputs: - windows_sdk_revision: - description: 'Expected locked swift-codex commit for focused Windows checks; empty runs native packaging on both platforms' - required: false - type: string - windows_validation_scope: - description: 'Windows candidate checks; all remains the complete adapter gate' - type: choice - options: [all, host-mcp, database, artifact] - default: all - windows_artifact_run: - description: 'Completed source audit run whose exact linked Windows binary should be checked' - required: false - type: string permissions: contents: read concurrency: - group: ci-${{ github.ref }}-${{ inputs.windows_validation_scope || 'all' }} + group: ci-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: package: - if: ${{ inputs.windows_sdk_revision == '' }} runs-on: macos-latest timeout-minutes: 30 steps: @@ -90,217 +75,5 @@ jobs: if-no-files-found: error retention-days: 14 - windows-package: - if: ${{ inputs.windows_sdk_revision == '' }} - runs-on: windows-2022 - timeout-minutes: 45 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 - with: - python-version: '3.13' - - uses: compnerd/gha-setup-swift@bbae8ce86bac5a3449f3992b3d88bc4ca25f2057 - with: - swift-version: swift-6.2.3-release - swift-build: 6.2.3-RELEASE - - name: Validate native package ownership and inputs - shell: pwsh - run: | - python Scripts/version.py check - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - foreach ($pattern in @('test_package*.py', 'test_windows*.py', 'test_protocol*.py', 'test_version.py')) { - python -m unittest discover -s Tests -p $pattern -v - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - } - swift package resolve - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - git diff --exit-code -- Package.resolved - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - name: Build the native archive and runtime receipt - shell: pwsh - run: | - python Scripts/package.py --output dist --configuration release - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - git diff --exit-code -- Package.resolved computer-mcp-plugin.toml - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: codex-plugin-windows-x86_64-${{ github.sha }} - path: | - dist/codex-plugin-windows-x86_64.zip - dist/receipt.json - if-no-files-found: error - retention-days: 14 - - windows-package-acceptance: - needs: windows-package - runs-on: windows-2022 - timeout-minutes: 15 - permissions: - actions: read - contents: read - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 - with: - python-version: '3.13' - - name: Download the exact package from this run - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - gh run download $env:GITHUB_RUN_ID --repo $env:GITHUB_REPOSITORY --name "codex-plugin-windows-x86_64-$env:GITHUB_SHA" --dir accepted-artifact - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - name: Check relocated package with app-local runtime - shell: pwsh - run: ./Scripts/check-windows-package.ps1 -ArtifactDirectory accepted-artifact -OutputDirectory .build/windows-package-acceptance - - name: Preserve exact archive acceptance - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: codex-plugin-windows-package-acceptance-${{ github.run_id }}-${{ github.run_attempt }} - path: .build/windows-package-acceptance/evidence/ - include-hidden-files: true - if-no-files-found: error - - windows-candidate: - if: ${{ inputs.windows_sdk_revision != '' && inputs.windows_validation_scope != 'host-mcp' && inputs.windows_validation_scope != 'artifact' }} - runs-on: windows-2022 - timeout-minutes: 45 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - uses: compnerd/gha-setup-swift@bbae8ce86bac5a3449f3992b3d88bc4ca25f2057 - with: - swift-version: swift-6.2.3-release - swift-build: 6.2.3-RELEASE - - name: Audit native dependencies and complete adapter source - shell: pwsh - env: - CODEX_CANDIDATE_REVISION: ${{ inputs.windows_sdk_revision }} - WINDOWS_VALIDATION_SCOPE: ${{ inputs.windows_validation_scope }} - run: | - $databaseOnly = $env:WINDOWS_VALIDATION_SCOPE -eq 'database' - ./Scripts/validate-windows-candidate.ps1 -SDKRevision $env:CODEX_CANDIDATE_REVISION -DatabaseOnly:$databaseOnly - - name: Preserve Windows source audit - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: codex-plugin-windows-source-${{ github.run_id }}-${{ github.run_attempt }} - path: .build/windows-candidate/evidence/ - include-hidden-files: true - if-no-files-found: error - - windows-process: - if: ${{ inputs.windows_sdk_revision != '' && (inputs.windows_validation_scope == '' || inputs.windows_validation_scope == 'all') }} - runs-on: windows-2022 - timeout-minutes: 30 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - name: Validate exact SDK input - shell: pwsh - env: - CODEX_CANDIDATE_REVISION: ${{ inputs.windows_sdk_revision }} - run: | - if ($env:CODEX_CANDIDATE_REVISION -cnotmatch '^[0-9a-f]{40}$') { - throw 'Windows process acceptance requires an exact SDK commit' - } - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - repository: swift-library/swift-codex - ref: ${{ inputs.windows_sdk_revision }} - path: .build/windows-process-sdk - persist-credentials: false - - uses: compnerd/gha-setup-swift@bbae8ce86bac5a3449f3992b3d88bc4ca25f2057 - with: - swift-version: swift-6.2.3-release - swift-build: 6.2.3-RELEASE - - name: Test exact production process sources in debug and release - shell: pwsh - run: ./Scripts/test-windows-process.ps1 -SDKPath .build/windows-process-sdk -OutputDirectory .build/windows-process - - name: Preserve native process acceptance - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: codex-plugin-windows-process-${{ github.run_id }}-${{ github.run_attempt }} - path: .build/windows-process/evidence/ - include-hidden-files: true - if-no-files-found: error - - windows-host-mcp: - if: ${{ inputs.windows_sdk_revision != '' && inputs.windows_validation_scope != 'database' && inputs.windows_validation_scope != 'artifact' }} - runs-on: windows-2022 - timeout-minutes: 20 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - name: Validate exact SDK input - shell: pwsh - env: - CODEX_CANDIDATE_REVISION: ${{ inputs.windows_sdk_revision }} - run: | - if ($env:CODEX_CANDIDATE_REVISION -cnotmatch '^[0-9a-f]{40}$') { - throw 'Host MCP acceptance requires an exact SDK commit' - } - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - repository: swift-library/swift-codex - ref: ${{ inputs.windows_sdk_revision }} - path: .build/windows-host-sdk - persist-credentials: false - - uses: compnerd/gha-setup-swift@bbae8ce86bac5a3449f3992b3d88bc4ca25f2057 - with: - swift-version: swift-6.2.3-release - swift-build: 6.2.3-RELEASE - - name: Test inherited host pipes with the shipping MCP dependency - shell: pwsh - run: ./Scripts/test-windows-host-mcp.ps1 -SDKPath .build/windows-host-sdk -OutputDirectory .build/windows-host-mcp - - name: Preserve native host MCP acceptance - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: codex-plugin-windows-host-mcp-${{ github.run_id }}-${{ github.run_attempt }} - path: .build/windows-host-mcp/evidence/ - include-hidden-files: true - if-no-files-found: error - - windows-artifact: - if: ${{ inputs.windows_validation_scope == 'artifact' && inputs.windows_sdk_revision != '' }} - runs-on: windows-2022 - timeout-minutes: 15 - permissions: - actions: read - contents: read - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - uses: compnerd/gha-setup-swift@bbae8ce86bac5a3449f3992b3d88bc4ca25f2057 - with: - swift-version: swift-6.2.3-release - swift-build: 6.2.3-RELEASE - - name: Check the exact linked native adapter - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - SOURCE_RUN: ${{ inputs.windows_artifact_run }} - SDK_REVISION: ${{ inputs.windows_sdk_revision }} - run: ./Scripts/check-windows-artifact.ps1 -SourceRun $env:SOURCE_RUN -SDKRevision $env:SDK_REVISION - - name: Preserve native artifact acceptance - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: codex-plugin-windows-artifact-${{ github.run_id }}-${{ github.run_attempt }} - path: .build/windows-artifact/evidence/ - include-hidden-files: true - if-no-files-found: error brand: uses: computer-mcp/.github/.github/workflows/brand-check.yml@f402911abcdfb87ef0c41a2759e8c51387b18935 # master diff --git a/CHANGELOG.md b/CHANGELOG.md index 1512c42..244cbac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ # Changelog ## Unreleased + +### Removed + +- Windows support; the plugin ships only for macOS. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2202bc2..05c10b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,8 +22,7 @@ python3 -m unittest discover -s Tests -p 'test_*.py' python3 Scripts/package.py --output OUTPUT_DIRECTORY --configuration release ``` -CI also packages and checks the Windows x86_64 archive on a Windows runner and -runs the organization brand check. Exercise help, valid input and invalid input +CI also runs the organization brand check. Exercise help, valid input and invalid input from outside the checkout. Keep command output contracts and generated resources covered by tests. diff --git a/Documentation/Architecture/Package.md b/Documentation/Architecture/Package.md index 42ab448..2592912 100644 --- a/Documentation/Architecture/Package.md +++ b/Documentation/Architecture/Package.md @@ -1,10 +1,8 @@ # Package -The package uses Swift tools 6.2 and supports macOS 14 and Windows x86_64. -Windows archives use the reviewed Swift 6.2.3 runtime and pinned SQLite static -build. `CodexMCPAdapter` owns argument -handling; `CodexAdapter` owns configuration, MCP projection and Codex domain -implementation. Runtime tests live in `CodexAdapterTests`; command parsing tests +The package uses Swift tools 6.2 and supports macOS 14 or newer. +`CodexMCPAdapter` owns argument handling; `CodexAdapter` owns configuration, +MCP projection and Codex domain implementation. Runtime tests live in `CodexAdapterTests`; command parsing tests live in `CodexMCPAdapterTests`. Computer MCP Core is not a dependency. @@ -12,12 +10,10 @@ dependency. | Package | Responsibility | | --- | --- | -| `computer-mcp/swift-sdk` transport fork | Standard northbound MCP transport, tools and results; native Windows stdio and complete POSIX frame writes | +| `computer-mcp/swift-sdk` transport fork | Standard northbound MCP transport, tools and results; stdio and complete POSIX frame writes | | swift-codex | App Server client and Exec client, each with its own lifecycle | -| swift-subprocess 0.4.0 | Child process launch and line I/O on Apple platforms | +| swift-subprocess 0.4.0 | Child process launch and line I/O | | swift-argument-parser 1.8.2 | Named options, schema-comparison subcommand, validation and generated CLI help | -| Apple Swift System 1.8.1 | Typed CRT descriptors for inherited Windows MCP pipe handles; already shared by the MCP dependency | -| Apple Swift Crypto 4.5.2 | SHA-256 on Windows; Apple platforms retain CryptoKit | | GRDB 7.11.1 | Codex approval, runtime/thread ownership, acceptance run, worktree lease and managed-worktree storage and transactions | `Scripts/verify-swift-codex-release-gate.sh` verifies that the manifest's exact @@ -171,10 +167,8 @@ through unchanged. An initial directory or Git worktree is not OS isolation. `CodexProcessEnvironment` preserves vendor state configuration and proxy behavior while removing parent Codex session and Computer MCP launch metadata. External Codex installation and user credentials remain user-owned. -Environment filtering uses native case-insensitive name comparison on Windows. -Proxy settings preserve the existing spelling and do not create case aliases; -ambiguous caller-supplied names remain subject to native launch validation. -Apple platforms retain distinct uppercase/lowercase proxy variables. +Environment names are case-sensitive. Proxy settings mirror a single inherited +uppercase or lowercase value while preserving explicitly configured distinct values. App Server and Exec resolve the configured executable using the same launch environment and workspace: absolute paths are direct, relative paths @@ -276,17 +270,10 @@ acceptance before promoting an artifact to a public release. ## Inherited host channel -The host binds callbacks to its own immutable caller/workspace scope. macOS uses -an inherited connected Unix socket. The Windows transport accepts a paired read -and write byte-pipe handle through `COMPUTER_MCP_HOST_READ_HANDLE` and -`COMPUTER_MCP_HOST_WRITE_HANDLE`, alongside the host context. Handles must be -canonical decimal values, distinct, inherited, and separate from standard I/O. -Ambiguous environment keys, partial pairs, files and message pipes are rejected. - -Admission clears inheritance and bridges owned duplicates through Swift System's -CRT descriptor type. Once the MCP transport has duplicated the handles for native -I/O, the adapter closes the inherited originals and CRT intermediates. The owning -MCP transport supplies framing, bounded queues, cancellation and native I/O joins; -the adapter retains one connection/close task. Callback metadata is removed from -vendor environments. Windows native transport evidence is separate from a complete -host launch, adapter artifact or authenticated model acceptance. +The host binds callbacks to its own immutable caller/workspace scope over an +inherited connected Unix socket. `COMPUTER_MCP_HOST_FD` selects a canonical +descriptor from 3 through 9 alongside the host context. The transport requires +a connected stream socket, sets close-on-exec and nonblocking mode, and bounds +message sizes, queued data and write deadlines. Disconnect closes the owned +descriptor and joins active I/O. Callback metadata is removed from vendor +environments. diff --git a/Documentation/Architecture/VersioningAndRelease.md b/Documentation/Architecture/VersioningAndRelease.md index 4c41216..1c61389 100644 --- a/Documentation/Architecture/VersioningAndRelease.md +++ b/Documentation/Architecture/VersioningAndRelease.md @@ -39,8 +39,8 @@ Accept the complete host/plugin/SDK combination before delivery. Create a formal signed tag only for the accepted commit; `release.yml` promotes the already-built artifact from its verified source run into the matching draft. The upload verifies every platform archive's inventory and manifest against the -formal tag before uploading any asset. It promotes both declared native archives -and their receipts from the same successful source run without rebuilding. +formal tag before uploading any asset. It promotes the declared macOS archive +and its receipt from the successful source run without rebuilding. An already uploaded asset must have the same digest; conflicting bytes fail. Candidate retries keep the intended product version and use a new run identity. A public tag and archive remain immutable. Only changed components are released. diff --git a/Documentation/Reference/Installation.md b/Documentation/Reference/Installation.md index 0476fbc..0b75aad 100644 --- a/Documentation/Reference/Installation.md +++ b/Documentation/Reference/Installation.md @@ -1,17 +1,8 @@ # Installation and recovery -On macOS the package owns `bin/codex-mcp-adapter` and its adjacent -`codex-plugin_CodexAdapter.bundle`. On Windows it owns -`bin/codex-mcp-adapter.exe`, `codex-plugin_CodexAdapter.resources` and the required -Swift/open-source runtime DLLs in `bin/`. Codex itself remains an external dependency. - -Before launching on Windows x86_64, install the latest supported -[Microsoft Visual C++ v14 x64 Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170) -from Microsoft. The plugin does not include Microsoft runtime DLLs or install -the redistributable. Its release receipt records the minimum tested version for -each imported Microsoft DLL under `windows_runtime.external_prerequisites`. -If Windows reports a missing `VCRUNTIME140.dll`, `VCRUNTIME140_1.dll` or -`MSVCP140.dll`, install or update that official x64 package before retrying. +The macOS package owns `bin/codex-mcp-adapter` and its adjacent +`codex-plugin_CodexAdapter.bundle`. Codex itself remains an external dependency. + Installing this package never installs, updates or removes Codex, changes global PATH, or grants a profile access to tools. @@ -27,46 +18,30 @@ python3 Scripts/package.py --output /absolute/new/artifact-directory The default build is release; `--configuration debug` is available for development. The destination must not exist. The script builds using the pinned dependency resolution, copies the executable and resources, collects upstream license and -notice files, and emits the native archive and `receipt.json`. macOS binaries -receive an ad-hoc signature; Windows binaries are unsigned. The receipt records +notice files, and emits the native archive and `receipt.json`. The executable +receives an ad-hoc signature. The receipt records each file and archive SHA-256, platform, architecture and build configuration. It is not an official-source or publisher signature. No artifact is uploaded or installed automatically. -The repository manifest declares `codex-plugin-macos-arm64.zip` and -`codex-plugin-windows-x86_64.zip`. Packaging requires the built adapter to match -exactly one declared platform and architecture combination. Both archives carry -the same manifest bytes; its `platform_paths` selects the native executable. -The manifest's `minimum_host` declares the required Computer MCP version. -Official GitHub installation verifies the archive manifest against its release tag. - -On Windows, run the packaging command with Python 3.11 or newer, PowerShell, -Swift 6.2.3, `clang-cl`, `llvm-lib` and `llvm-readobj` available. The packager -builds SQLite from the checksummed source in `Scripts/windows-sqlite.json`, -verifies its required features, and passes the resulting headers and static -library to SwiftPM. It copies the recursively inspected Swift runtime DLLs -beside the adapter and verifies their architecture and notice coverage. The -Windows receipt records each bundled DLL's source digest, the imported Microsoft -runtime version floors and the SQLite build identity. +The repository manifest declares `codex-plugin-macos-arm64.zip`. Packaging +requires the built adapter to match the declared platform and architecture. +The archive carries the repository's exact manifest bytes; its `platform_paths` +selects the executable. The manifest's `minimum_host` declares the required +Computer MCP version. Official GitHub installation verifies the archive +manifest against its release tag. See [third-party components](../../THIRD_PARTY_NOTICES.md) for distribution terms. Package inputs must be regular files and directories. Symbolic links and special -files, including Windows reparse points, are rejected before signing or running -the staged adapter. The output is +files are rejected before signing or running the staged adapter. The output is published atomically without replacing any existing destination, including an empty directory created while the build is running. Failure removes only the packager's temporary staging directory; existing outputs remain unchanged. The repository's CI workflow (`ci.yml`) runs on pull requests, `master` pushes and manual dispatch. It checks formatting, tests and the dependency lock, then -retains both native ZIPs and receipts as downloadable workflow artifacts. A -separate Windows job installs no Swift toolchain, relocates the exact ZIP and -runs the adapter with system-only child PATH. It records actual loaded module -paths and digests, verifies the system-installed Microsoft runtime architecture -and versions against the receipt, and checks MCP discovery, reconnect and joined -native process cleanup. -Hosted runners can contain preinstalled software; this gate does not claim a -pristine Windows installation or authenticated model execution. These outputs +retains the macOS ZIP and receipt as a downloadable workflow artifact. CI verifies +and relocates the exact archive with `Scripts/check-package.py`. These outputs are validation builds, not published or verified official releases. Check the receipt's architecture before installation. Public distribution needs separate publisher authorization and its signing/provenance review. diff --git a/Documentation/Reference/Release.md b/Documentation/Reference/Release.md index 4cadd33..61fab94 100644 --- a/Documentation/Reference/Release.md +++ b/Documentation/Reference/Release.md @@ -8,8 +8,7 @@ owns the version, acceptance and immutability rules. 1. Confirm that `version` in `computer-mcp-plugin.toml` has not been released and that the reviewed `master` commit has a successful CI run. -2. Accept that run's `codex-plugin-macos-arm64-` and - `codex-plugin-windows-x86_64-` artifacts as described in +2. Accept that run's `codex-plugin-macos-arm64-` artifact as described in [Versioning and Release](../Architecture/VersioningAndRelease.md), including the host checks against the exact archive bytes. 3. Create a signed annotated tag `vX.Y.Z` on the accepted commit and push it. @@ -27,8 +26,7 @@ owns the version, acceptance and immutability rules. rebuilds and never publishes the draft. 6. Review the draft's assets and notes, then publish it. -Each receipt records the archive digest, file inventory and build architecture; -the Windows receipt also records the required Microsoft runtime versions. +The receipt records the archive digest, file inventory and build architecture. ## Catalog notification diff --git a/Package.swift b/Package.swift index b0cf75b..807baff 100644 --- a/Package.swift +++ b/Package.swift @@ -7,8 +7,6 @@ let package = Package( platforms: [.macOS(.v14)], products: [.executable(name: "codex-mcp-adapter", targets: ["CodexMCPAdapter"])], dependencies: [ - .package(url: "https://github.com/apple/swift-crypto.git", exact: "4.5.2"), - .package(url: "https://github.com/apple/swift-system.git", exact: "1.8.1"), .package(url: "https://github.com/groue/GRDB.swift.git", exact: "7.11.1"), .package(url: "https://github.com/apple/swift-argument-parser", exact: "1.8.2"), .package(url: "https://github.com/computer-mcp/swift-sdk.git", exact: "0.13.1-computer-mcp.1"), @@ -20,16 +18,10 @@ let package = Package( name: "CodexAdapter", dependencies: [ .product(name: "GRDB", package: "GRDB.swift"), - .product(name: "Crypto", package: "swift-crypto", condition: .when(platforms: [.windows])), .product(name: "MCP", package: "swift-sdk"), - .product( - name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.windows])), .product(name: "CodexAppServerClient", package: "swift-codex"), .product(name: "CodexAppServerProtocol", package: "swift-codex"), .product(name: "CodexAppServerRuntime", package: "swift-codex"), - .product( - name: "CodexAppServerStdio", package: "swift-codex", - condition: .when(platforms: [.windows])), .product(name: "CodexExec", package: "swift-codex"), .product(name: "Subprocess", package: "swift-subprocess"), ], diff --git a/README.md b/README.md index 09ca186..aa59b57 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,8 @@ large histories can exceed transport, output or timeout limits. See the ## Build and run -On macOS, building requires macOS 14 or newer and Swift 6.2 or newer. -Run `swift build` and `swift test`. Windows x86_64 packaging uses Swift 6.2.3, -PowerShell and the pinned SQLite build described in -[Installation](Documentation/Reference/Installation.md). The Windows adapter -serves standard MCP over stdio and requires the user-installed official -Microsoft Visual C++ v14 x64 runtime. It does not provide a Windows host GUI. Launch +Building requires macOS 14 or newer and Swift 6.2 or newer. +Run `swift build` and `swift test`. Launch `.build/debug/codex-mcp-adapter` through an MCP stdio client, never as an unbounded unattended shell command. `--help` prints usage without serving. diff --git a/Scripts/build-windows-sqlite.ps1 b/Scripts/build-windows-sqlite.ps1 deleted file mode 100644 index 5546b6a..0000000 --- a/Scripts/build-windows-sqlite.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -param([Parameter(Mandatory = $true)][string]$OutputDirectory, [switch]$AsJSON) - -$ErrorActionPreference = 'Stop' -if (-not $IsWindows) { throw 'SQLite Windows input requires a native Windows toolchain' } -if (Test-Path $OutputDirectory) { throw 'SQLite build requires a fresh output directory' } -$root = (New-Item -ItemType Directory -Path $OutputDirectory).FullName -$metadataPath = Join-Path $PSScriptRoot 'windows-sqlite.json' -$metadata = Get-Content $metadataPath -Raw | ConvertFrom-Json -Copy-Item $metadataPath (Join-Path $root 'source.json') -$archive = Join-Path $root 'source.zip' -Invoke-WebRequest -Uri $metadata.url -OutFile $archive -TimeoutSec 120 -MaximumRetryCount 2 -RetryIntervalSec 2 -if ((Get-Item $archive).Length -ne $metadata.size -or - (Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $metadata.sha256) { - throw 'SQLite source archive does not match the pinned size and checksum' -} -Expand-Archive -Path $archive -DestinationPath $root -$include = Join-Path $root $metadata.directory -$compiler = (Get-Command clang-cl -ErrorAction Stop).Source -$librarian = (Get-Command llvm-lib -ErrorAction Stop).Source -& $compiler --version *> (Join-Path $root 'compiler.txt') -if ($LASTEXITCODE -ne 0) { throw 'SQLite compiler unavailable' } -$object = Join-Path $root 'sqlite3.obj' -$library = Join-Path $root 'sqlite3.lib' -$defines = @($metadata.compileDefinitions | ForEach-Object { "/D$_" }) -$compileArguments = @('/nologo', '/c', '/O2', '/MD') + $defines + @((Join-Path $include 'sqlite3.c'), "/Fo$object") -& $compiler @compileArguments *> (Join-Path $root 'compile.log') -if ($LASTEXITCODE -ne 0) { throw 'SQLite static compilation failed; see compile.log' } -& $librarian /nologo "/out:$library" $object *> (Join-Path $root 'library.log') -if ($LASTEXITCODE -ne 0) { throw 'SQLite static archive failed; see library.log' } - -$probe = Join-Path $PSScriptRoot '../Tests/WindowsSQLite/verify.c' -$executable = Join-Path $root 'verify.exe' -$probeObject = Join-Path $root 'verify.obj' -$probeArguments = @('/nologo', '/c', '/O2', '/MD', "/I$include", $probe, "/Fo$probeObject") -& $compiler @probeArguments *> (Join-Path $root 'verify-compile.log') -if ($LASTEXITCODE -ne 0) { throw 'SQLite verification compilation failed; see verify-compile.log' } -$linkArguments = @('/nologo', '/MD', $probeObject, $library, "/Fe$executable") -& $compiler @linkArguments *> (Join-Path $root 'verify-link.log') -if ($LASTEXITCODE -ne 0) { throw 'SQLite verification linking failed; see verify-link.log' } -& $executable (Join-Path $root 'verify.sqlite') $metadata.version *> (Join-Path $root 'verify.json') -if ($LASTEXITCODE -ne 0) { throw 'SQLite native verification failed; see verify.json' } - -$receipt = [pscustomobject]@{ - source = $metadata - compiler = $compiler - librarian = $librarian - compileArguments = $compileArguments - verificationArguments = $probeArguments - verificationLinkArguments = $linkArguments - librarySHA256 = (Get-FileHash $library -Algorithm SHA256).Hash.ToLowerInvariant() - headerSHA256 = (Get-FileHash (Join-Path $include 'sqlite3.h') -Algorithm SHA256).Hash.ToLowerInvariant() - probeSHA256 = (Get-FileHash $probe -Algorithm SHA256).Hash.ToLowerInvariant() - verification = (Get-Content (Join-Path $root 'verify.json') -Raw | ConvertFrom-Json) -} -$receipt | ConvertTo-Json -Depth 8 | Set-Content (Join-Path $root 'receipt.json') -$result = [pscustomobject]@{ includeDirectory = $include; libraryDirectory = $root; receipt = $receipt } -if ($AsJSON) { $result | ConvertTo-Json -Depth 10 } else { $result } diff --git a/Scripts/check-package.py b/Scripts/check-package.py index 1828d9d..2d0521d 100644 --- a/Scripts/check-package.py +++ b/Scripts/check-package.py @@ -43,10 +43,6 @@ def verify(archive, receipt_path, manifest, destination=None): raise ValueError("Archive receipt must declare its file inventory") for name, value in inventory.items(): safe_path(name) - if receipt.get("platform") == "windows": - from windows_runtime import is_msvc_runtime - if is_msvc_runtime(PurePosixPath(name).name): - raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled") if not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{64}", value): raise ValueError("Archive inventory requires exact SHA-256 digests") expected_name = validate_architectures(manifest, receipt["architectures"], receipt["platform"]) @@ -110,8 +106,7 @@ def verify(archive, receipt_path, manifest, destination=None): output.parent.mkdir(parents=True, exist_ok=True) with zipped.open(entry) as source, output.open("xb") as target: shutil.copyfileobj(source, target, length=1024 * 1024) - if os.name != "nt": - output.chmod((entry.external_attr >> 16) & 0o777 or 0o644) + output.chmod((entry.external_attr >> 16) & 0o777 or 0o644) publish_directory(stage, destination) stage = None finally: diff --git a/Scripts/check-windows-artifact.ps1 b/Scripts/check-windows-artifact.ps1 deleted file mode 100644 index c7fc3e8..0000000 --- a/Scripts/check-windows-artifact.ps1 +++ /dev/null @@ -1,140 +0,0 @@ -param( - [Parameter(Mandatory = $true)][string]$SourceRun, - [Parameter(Mandatory = $true)][string]$SDKRevision -) -$ErrorActionPreference = 'Stop' -if ($SourceRun -cnotmatch '^[0-9]+$' -or $SDKRevision -cnotmatch '^[0-9a-f]{40}$') { - throw 'Native artifact acceptance requires a run ID and exact SDK revision' -} -$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path -Set-Location $repository -$root = Join-Path $repository '.build/windows-artifact' -if (Test-Path $root) { throw 'Artifact acceptance requires a new output directory' } -$evidence = Join-Path $root 'evidence' -New-Item -ItemType Directory -Path $evidence -Force | Out-Null -$runJSON = gh api "repos/$env:GITHUB_REPOSITORY/actions/runs/$SourceRun" -if ($LASTEXITCODE -ne 0) { throw 'Cannot inspect source run' } -$run = $runJSON | ConvertFrom-Json -if ($run.status -ne 'completed' -or $run.path -ne '.github/workflows/ci.yml' -or - $run.head_repository.full_name -ne $env:GITHUB_REPOSITORY -or $run.head_sha -cnotmatch '^[0-9a-f]{40}$') { - throw 'Source must be a completed validation run from this repository' -} -$runJSON | Set-Content (Join-Path $evidence 'source-run.json') -$name = "codex-plugin-windows-source-$SourceRun-$($run.run_attempt)" -$download = Join-Path $root 'source-artifact' -gh run download $SourceRun --repo $env:GITHUB_REPOSITORY --name $name --dir $download -if ($LASTEXITCODE -ne 0) { throw 'Cannot download selected source artifact' } -$inputs = Get-Content (Join-Path $download 'inputs.json') -Raw | ConvertFrom-Json -$link = Get-Content (Join-Path $download 'linked-product-results.json') -Raw | ConvertFrom-Json -if ($inputs.adapterRevision -ne $run.head_sha -or $inputs.sdkRevision -ne $SDKRevision -or - !$inputs.completeAdapter -or $link.linkExitCode -ne 0) { - throw 'Artifact does not bind the requested complete linked adapter and SDK' -} -$product = (Resolve-Path (Join-Path $download 'linked-product')).Path -$files = @(Get-Content (Join-Path $download 'linked-product-files.json') -Raw | ConvertFrom-Json) -$seen = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) -foreach ($file in $files) { - if ([IO.Path]::IsPathRooted($file.path) -or $file.path.Contains(':') -or - @($file.path -split '[/\\]' | Where-Object { $_ -in @('', '.', '..') }).Count -gt 0 -or - !$seen.Add($file.path)) { throw 'Invalid artifact inventory path' } - $path = Join-Path $product $file.path - $item = Get-Item $path - if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or - $item.Length -ne $file.bytes -or - (Get-FileHash $path -Algorithm SHA256).Hash.ToLowerInvariant() -ne $file.sha256) { - throw 'Linked payload differs from its source inventory' - } -} -if (@(Get-ChildItem $product -Recurse -File).Count -ne $files.Count -or - @(Get-ChildItem $product -Recurse | Where-Object { ($_.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 }).Count -gt 0) { - throw 'Linked payload contains undeclared or linked files' -} -Copy-Item (Join-Path $download 'inputs.json') $evidence -Copy-Item (Join-Path $download 'linked-product-files.json') $evidence -swift --version | Set-Content (Join-Path $evidence 'toolchain.txt') -if ($LASTEXITCODE -ne 0) { throw 'Swift runtime environment unavailable' } -$runtimeDirectories = @($env:PATH -split ';' | Where-Object { - $_ -and (Test-Path (Join-Path $_ 'swiftCore.dll')) -} | Select-Object -Unique) -if ($runtimeDirectories.Count -eq 0) { throw 'Cannot locate selected Swift runtime' } -$auditArguments = @('--executable', (Join-Path $product 'codex-mcp-adapter.exe'), - '--system-directory', (Join-Path $env:SystemRoot 'System32'), - '--output', (Join-Path $evidence 'runtime-dependencies.json')) -foreach ($directory in $runtimeDirectories) { $auditArguments += @('--runtime-directory', $directory) } -python (Join-Path $PSScriptRoot 'windows_runtime.py') @auditArguments -if ($LASTEXITCODE -ne 0) { throw 'Recursive native runtime dependency audit failed' } -$swift = (Get-Command swift).Source -$swiftInstallation = Split-Path (Split-Path (Split-Path (Split-Path (Split-Path $swift)))) -$notices = @(Get-ChildItem $swiftInstallation -Recurse -File | Where-Object { - $_.Name -match '(LICENSE|NOTICE|COPYING|COPYRIGHT|ThirdParty)' -and $_.Length -lt 1048576 -} | ForEach-Object { - [pscustomobject]@{ path = $_.FullName; bytes = $_.Length - sha256 = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() } -}) -ConvertTo-Json -InputObject $notices -Depth 4 | Set-Content (Join-Path $evidence 'runtime-notice-inventory.json') -$noticeOutput = Join-Path $evidence 'runtime-notices' -foreach ($notice in $notices) { - $destination = Join-Path $noticeOutput ([IO.Path]::GetRelativePath($swiftInstallation, $notice.path)) - New-Item -ItemType Directory -Path (Split-Path $destination) -Force | Out-Null - Copy-Item $notice.path $destination -} -$definitionRevision = git rev-parse HEAD -if ($LASTEXITCODE -ne 0) { throw 'Cannot identify check definition' } -[pscustomobject]@{ - sourceRun = $SourceRun - sourceArtifact = $name - adapterRevision = $inputs.adapterRevision - sdkRevision = $SDKRevision - checkRevision = $definitionRevision - linkedFilesVerified = $files.Count - sourceRebuilt = $false - cleanMachineRelocation = $false - authenticatedModel = $false -} | ConvertTo-Json | Set-Content (Join-Path $evidence 'acceptance-inputs.json') -$fixture = Join-Path $repository 'Tests/WindowsAdapter' -$metadata = Get-Content (Join-Path $fixture 'codex-binary.json') -Raw | ConvertFrom-Json -Copy-Item (Join-Path $fixture 'codex-binary.json') $evidence -$codex = Join-Path $root 'codex' -New-Item -ItemType Directory -Path $codex | Out-Null -$archive = Join-Path $codex 'codex.zip' -Invoke-WebRequest -Uri $metadata.archiveURL -OutFile $archive -TimeoutSec 120 -MaximumRetryCount 2 -if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $metadata.archiveSHA256) { - throw 'Native Codex archive checksum mismatch' -} -Expand-Archive -Path $archive -DestinationPath $codex -$codexBinary = Join-Path $codex $metadata.executable -if ((Get-FileHash $codexBinary -Algorithm SHA256).Hash.ToLowerInvariant() -ne $metadata.executableSHA256) { - throw 'Native Codex executable checksum mismatch' -} -python (Join-Path $fixture 'ProtocolCheck.py') --adapter (Join-Path $product 'codex-mcp-adapter.exe') ` - --codex $codexBinary --evidence-directory (Join-Path $evidence 'protocol') ` - *> (Join-Path $evidence 'protocol.log') -$code = $LASTEXITCODE -Get-Content (Join-Path $evidence 'protocol.log') -Tail 80 -if ($code -ne 0) { exit $code } - -# The relocated candidate contains only inventoried product files and selected runtime DLLs. -$relocated = Join-Path $root 'relocated' -Copy-Item $product $relocated -Recurse -$closure = Get-Content (Join-Path $evidence 'runtime-dependencies.json') -Raw | ConvertFrom-Json -if (@($closure.libraries | Where-Object { $_.role -eq 'external-msvc-runtime' }).Count -ne 0) { - throw 'App-local acceptance requires redistributable runtime inputs outside System32' -} -foreach ($library in @($closure.libraries | Where-Object { $_.role -eq 'runtime' })) { - $destination = Join-Path $relocated ([IO.Path]::GetFileName($library.path)) - if (Test-Path $destination) { throw 'Runtime DLL collides with the product payload' } - Copy-Item $library.path $destination - if ((Get-FileHash $destination -Algorithm SHA256).Hash.ToLowerInvariant() -ne $library.sha256) { - throw 'Relocated runtime DLL differs from its inspected source' - } -} -@(Get-ChildItem $relocated -Recurse -File | ForEach-Object { - [pscustomobject]@{ path = [IO.Path]::GetRelativePath($relocated, $_.FullName); bytes = $_.Length - sha256 = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() } -}) | ConvertTo-Json -Depth 4 | Set-Content (Join-Path $evidence 'relocated-files.json') -python (Join-Path $fixture 'ProtocolCheck.py') --adapter (Join-Path $relocated 'codex-mcp-adapter.exe') ` - --codex $codexBinary --evidence-directory (Join-Path $evidence 'app-local-protocol') --app-local-runtime ` - *> (Join-Path $evidence 'app-local-protocol.log') -$relocatedCode = $LASTEXITCODE -Get-Content (Join-Path $evidence 'app-local-protocol.log') -Tail 80 -exit $relocatedCode diff --git a/Scripts/check-windows-package.ps1 b/Scripts/check-windows-package.ps1 deleted file mode 100644 index 24a71d4..0000000 --- a/Scripts/check-windows-package.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -param( - [Parameter(Mandatory = $true)][string]$ArtifactDirectory, - [Parameter(Mandatory = $true)][string]$OutputDirectory -) -$ErrorActionPreference = 'Stop' -$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path -$artifact = (Resolve-Path $ArtifactDirectory).Path -$root = [IO.Path]::GetFullPath($OutputDirectory) -if (Test-Path $root) { throw 'Package acceptance requires a new output directory' } -$evidence = Join-Path $root 'evidence' -New-Item -ItemType Directory -Path $evidence | Out-Null -$receiptPath = Join-Path $artifact 'receipt.json' -$receipt = Get-Content $receiptPath -Raw | ConvertFrom-Json -if ($receipt.platform -ne 'windows' -or $receipt.configuration -ne 'release' -or - $receipt.archive -cnotmatch '^[A-Za-z0-9][A-Za-z0-9_.-]*\.zip$') { - throw 'Expected a native Windows release package receipt' -} -$revision = git -C $repository rev-parse HEAD -if ($LASTEXITCODE -ne 0) { throw 'Cannot identify acceptance source' } -[pscustomobject]@{ - sourceRevision = $revision - runnerImage = $env:ImageOS - runnerImageVersion = $env:ImageVersion - runnerOS = $env:RUNNER_OS - runnerArchitecture = $env:RUNNER_ARCH - existingSwiftCommands = @((Get-Command swift -All -ErrorAction SilentlyContinue).Source) - swiftToolchainInstalledByThisJob = $false - microsoftRuntime = 'system-installed prerequisite; native versions and module paths verified against the package receipt' - pristineWindowsImage = $false - sourceRebuilt = $false - authenticatedModel = $false -} | ConvertTo-Json -Depth 4 | Set-Content (Join-Path $evidence 'inputs.json') -Copy-Item $receiptPath (Join-Path $evidence 'package-receipt.json') -$relocated = Join-Path $root 'relocated package 汉字' -python (Join-Path $PSScriptRoot 'check-package.py') --archive (Join-Path $artifact $receipt.archive) ` - --receipt $receiptPath --destination $relocated *> (Join-Path $evidence 'archive-check.json') -if ($LASTEXITCODE -ne 0) { throw 'Archive inventory, manifest or relocation verification failed' } - -# This CLI is a private validation input and never becomes part of the plugin archive. -$fixture = Join-Path $repository 'Tests/WindowsAdapter' -$metadata = Get-Content (Join-Path $fixture 'codex-binary.json') -Raw | ConvertFrom-Json -Copy-Item (Join-Path $fixture 'codex-binary.json') $evidence -$codex = Join-Path $root 'codex' -New-Item -ItemType Directory -Path $codex | Out-Null -$archive = Join-Path $codex 'codex.zip' -Invoke-WebRequest -Uri $metadata.archiveURL -OutFile $archive -TimeoutSec 120 -MaximumRetryCount 2 -if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $metadata.archiveSHA256) { - throw 'Native Codex archive checksum mismatch' -} -Expand-Archive -Path $archive -DestinationPath $codex -$codexBinary = Join-Path $codex $metadata.executable -if ((Get-FileHash $codexBinary -Algorithm SHA256).Hash.ToLowerInvariant() -ne $metadata.executableSHA256) { - throw 'Native Codex executable checksum mismatch' -} -python (Join-Path $fixture 'ProtocolCheck.py') --adapter (Join-Path $relocated 'bin/codex-mcp-adapter.exe') ` - --codex $codexBinary --evidence-directory (Join-Path $evidence 'protocol') --app-local-runtime --package-receipt $receiptPath ` - *> (Join-Path $evidence 'protocol.log') -$code = $LASTEXITCODE -Get-Content (Join-Path $evidence 'protocol.log') -Tail 80 -exit $code diff --git a/Scripts/package.py b/Scripts/package.py index 6bb142a..b63f193 100644 --- a/Scripts/package.py +++ b/Scripts/package.py @@ -27,16 +27,11 @@ def command(arguments, cwd): def current_platform(): if sys.platform == "darwin": return "macos" - if sys.platform == "win32": - return "windows" - raise ValueError("Native packaging requires macOS or Windows") + raise ValueError("Native packaging requires macOS") def input_kind(path): - metadata = path.lstat() - if getattr(metadata, "st_file_attributes", 0) & 0x400: - raise ValueError(f"Package input is a reparse point: {path}") - return metadata.st_mode + return path.lstat().st_mode def copy_file(source, destination): @@ -111,10 +106,6 @@ def validate_architectures(manifest, architectures, platform="macos"): def publish_directory(source, destination): - if sys.platform == "win32": - # Windows rename atomically refuses every existing destination. - os.rename(source, destination) - return # Darwin RENAME_EXCL atomically refuses an existing destination, including an empty directory. rename = ctypes.CDLL(None, use_errno=True).renamex_np rename.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint] @@ -133,23 +124,11 @@ def package(output, configuration): stage = Path(tempfile.mkdtemp(prefix="codex-package-", dir=output.parent)) try: platform = current_platform() - swift = "/usr/bin/swift" if platform == "macos" else shutil.which("swift") - if swift is None: - raise ValueError("Swift must be available to build the native archive") - build_arguments = [] - sqlite = None - if platform == "windows": - shell = shutil.which("pwsh") - if shell is None: - raise ValueError("PowerShell is required for the native SQLite build") - sqlite = json.loads(command([shell, "-NoProfile", "-NonInteractive", "-File", - str(repo / "Scripts/build-windows-sqlite.ps1"), "-OutputDirectory", str(stage / "sqlite"), "-AsJSON"], repo)) - build_arguments = ["-Xcc", "-I" + sqlite["includeDirectory"], - "-Xlinker", "/LIBPATH:" + sqlite["libraryDirectory"]] - command([swift, "build", "-c", configuration, "--disable-automatic-resolution", *build_arguments], repo) + swift = "/usr/bin/swift" + command([swift, "build", "-c", configuration, "--disable-automatic-resolution"], repo) built = Path(command([swift, "build", "-c", configuration, "--show-bin-path"], repo)) - executable = built / ("codex-mcp-adapter.exe" if platform == "windows" else "codex-mcp-adapter") - bundle = built / ("codex-plugin_CodexAdapter.resources" if platform == "windows" else "codex-plugin_CodexAdapter.bundle") + executable = built / "codex-mcp-adapter" + bundle = built / "codex-plugin_CodexAdapter.bundle" if not executable.is_file() or not bundle.is_dir(): raise ValueError("The executable and its SwiftPM resource bundle must both be built") payload = stage / "package" @@ -180,10 +159,6 @@ def package(output, configuration): for name in ["LICENSE", "NOTICE"]: copy_file(repo / ".build/checkouts/swift-codex/Vendor/CodexAppServerProtocolSchema" / name, schema_notices / name) - runtime_receipt = None - if platform == "windows": - from windows_package import stage_runtime - runtime_receipt = stage_runtime(repo, binary_directory, notices, sqlite, swift, command, copy_file, copy_tree) validate_payload(payload) with (payload / "ThirdPartyNotices.txt").open("wb") as aggregate: for entry in sorted(notices.rglob("*")): @@ -192,12 +167,9 @@ def package(output, configuration): aggregate.write(entry.read_bytes()) aggregate.write(b"\n") binary = binary_directory / executable.name - if platform == "macos": - command(["/usr/bin/codesign", "--force", "--sign", "-", str(binary)], stage) - command(["/usr/bin/codesign", "--verify", "--strict", str(binary)], stage) - architectures = command(["/usr/bin/lipo", "-archs", str(binary)], stage).split() - else: - architectures = [runtime_receipt["architecture"]] + command(["/usr/bin/codesign", "--force", "--sign", "-", str(binary)], stage) + command(["/usr/bin/codesign", "--verify", "--strict", str(binary)], stage) + architectures = command(["/usr/bin/lipo", "-archs", str(binary)], stage).split() manifest = payload / "computer-mcp-plugin.toml" archive_name = validate_architectures(manifest, architectures, platform) declaration = tomllib.loads(manifest.read_text(encoding="utf-8")) @@ -227,12 +199,10 @@ def package(output, configuration): for entry in sorted(payload.rglob("*")): package_zip.write(entry, entry.relative_to(payload).as_posix()) receipt = {"plugin_id": "codex", "configuration": configuration, "architectures": architectures, - "platform": platform, "signature": "ad-hoc" if platform == "macos" else "unsigned", + "platform": platform, "signature": "ad-hoc", "publisher_verified": False, "published": False, "archive": archive_name, "archive_sha256": hashlib.sha256(archive.read_bytes()).hexdigest(), "archive_bytes": archive.stat().st_size, "files": inventory} - if runtime_receipt is not None: - receipt["windows_runtime"] = runtime_receipt (stage / "receipt.json").write_text(json.dumps(receipt, indent=2) + "\n") publish_directory(stage, output) return {"directory": str(output), "archive": str(output / archive.name), diff --git a/Scripts/test-windows-host-mcp.ps1 b/Scripts/test-windows-host-mcp.ps1 deleted file mode 100644 index 44649cd..0000000 --- a/Scripts/test-windows-host-mcp.ps1 +++ /dev/null @@ -1,125 +0,0 @@ -param( - [Parameter(Mandatory = $true)][string]$SDKPath, - [Parameter(Mandatory = $true)][string]$OutputDirectory -) - -$ErrorActionPreference = 'Stop' -$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path -$sdk = (Resolve-Path $SDKPath).Path -$sdkRevision = git -C $sdk rev-parse HEAD -if ($LASTEXITCODE -ne 0 -or $sdkRevision -cnotmatch '^[0-9a-f]{40}$') { throw 'Cannot identify SDK source' } -git -C $sdk diff --quiet HEAD -if ($LASTEXITCODE -ne 0) { throw 'Host MCP tests require an unchanged committed SDK candidate' } -$adapterRevision = git -C $repository rev-parse HEAD -if ($LASTEXITCODE -ne 0) { throw 'Cannot identify adapter source' } -$shippingPath = Join-Path $repository 'Package.resolved' -$shippingHash = (Get-FileHash $shippingPath -Algorithm SHA256).Hash -$shippingLock = Get-Content $shippingPath -Raw | ConvertFrom-Json -$sdkPin = @($shippingLock.pins | Where-Object { $_.identity -eq 'swift-codex' }) -$mcpPin = @($shippingLock.pins | Where-Object { $_.identity -eq 'swift-sdk' }) -if ($sdkPin.Count -ne 1 -or $sdkPin[0].state.revision -ne $sdkRevision -or - $mcpPin.Count -ne 1 -or $mcpPin[0].location -ne 'https://github.com/computer-mcp/swift-sdk.git' -or - $mcpPin[0].state.version -cnotmatch '^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$') { - throw 'Host MCP checks require the shipping dependency identities' -} -if (Test-Path $OutputDirectory) { throw 'Host MCP tests require a fresh output directory' } -New-Item -ItemType Directory -Path $OutputDirectory | Out-Null -$root = (Resolve-Path $OutputDirectory).Path -$evidence = Join-Path $root 'evidence' -$consumer = Join-Path $root 'consumer' -$sources = Join-Path $consumer 'Sources/HostPipe' -$tests = Join-Path $consumer 'Tests/HostPipeTests' -$childSources = Join-Path $consumer 'Sources/HostPipeFixture' -New-Item -ItemType Directory -Path $evidence, $sources, $tests, $childSources | Out-Null -$fixture = Join-Path $repository 'Tests/WindowsHostMCP' -$manifest = (Get-Content (Join-Path $fixture 'Package.swift.template') -Raw).Replace( - '__MCP_URL__', $mcpPin[0].location).Replace('__MCP_VERSION__', $mcpPin[0].state.version) -$manifest | Set-Content (Join-Path $consumer 'Package.swift') -Copy-Item $shippingPath (Join-Path $consumer 'Package.resolved') -Copy-Item $shippingPath (Join-Path $evidence 'shipping-Package.resolved') -swift package --package-path $consumer resolve *> (Join-Path $evidence 'resolve.log') -if ($LASTEXITCODE -ne 0) { throw 'Host MCP consumer resolution failed' } -$consumerLock = Get-Content (Join-Path $consumer 'Package.resolved') -Raw | ConvertFrom-Json -foreach ($pin in $consumerLock.pins) { - $expected = @($shippingLock.pins | Where-Object { $_.identity -eq $pin.identity }) - if ($expected.Count -ne 1 -or $pin.location -ne $expected[0].location -or - $pin.state.revision -ne $expected[0].state.revision -or $pin.state.version -ne $expected[0].state.version) { - throw "Host MCP consumer differs from shipping dependency: $($pin.identity)" - } -} -if (@($consumerLock.pins | Where-Object { $_.identity -eq 'swift-sdk' }).Count -ne 1) { - throw 'Host MCP consumer did not resolve the shipping MCP dependency' -} -Copy-Item (Join-Path $fixture 'HostPipeTests.swift') $tests -Copy-Item (Join-Path $fixture 'HostPipeFixture.swift') $childSources -Copy-Item (Join-Path $fixture 'HostProcess') (Join-Path $consumer 'Sources/HostProcess') -Recurse -Copy-Item (Join-Path $consumer 'Package.swift') $evidence -$source = Join-Path $repository 'Sources/CodexAdapter/MCPInheritedPipeTransport.swift' -$copy = Join-Path $sources 'MCPInheritedPipeTransport.swift' -Copy-Item $source $copy -Copy-Item $source $childSources -$sourceHash = (Get-FileHash $source -Algorithm SHA256).Hash.ToLowerInvariant() -if ((Get-FileHash $copy -Algorithm SHA256).Hash.ToLowerInvariant() -ne $sourceHash -or - (Get-FileHash (Join-Path $childSources 'MCPInheritedPipeTransport.swift') -Algorithm SHA256).Hash.ToLowerInvariant() -ne $sourceHash) { - throw 'Host transport source copy changed' -} -[pscustomobject]@{ - adapterRevision = $adapterRevision - sdkRevision = $sdkRevision - mcpDependency = $mcpPin[0] - shippingLockSHA256 = $shippingHash.ToLowerInvariant() - source = 'Sources/CodexAdapter/MCPInheritedPipeTransport.swift' - sourceSHA256 = $sourceHash - evidenceClass = 'native-inherited-host-mcp-transport' - independentChild = $true - completeAdapter = $false - authenticatedModel = $false -} | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $evidence 'inputs.json') - -$versionOutput = swift --version -if ($LASTEXITCODE -ne 0) { throw 'Swift unavailable' } -$versionOutput | Set-Content (Join-Path $evidence 'toolchain.txt') -$version = (($versionOutput -join "`n") -split 'Swift version ')[1].Split(' ')[0].Trim() -if ($version -notmatch '^\d+\.\d+\.\d+$') { throw 'Cannot identify Swift testing runtime version' } -$developer = Split-Path (Split-Path $env:SDKROOT.TrimEnd([char[]]'\/')) -$testing = Join-Path $developer "Library/Testing-$version/usr/bin64" -$xctest = Join-Path $developer "Library/XCTest-$version/usr/bin64" -if (!(Test-Path (Join-Path $testing 'Testing.dll')) -or !(Test-Path (Join-Path $xctest 'XCTest.dll'))) { - throw 'Missing selected SDK testing runtimes' -} -$originalPath = $env:PATH -$originalFixture = $env:HOST_PIPE_FIXTURE_PATH -$results = @() -try { - $env:PATH = "$testing;$xctest;$originalPath" - foreach ($configuration in @('debug', 'release')) { - $buildLog = Join-Path $evidence "$configuration-fixture.log" - swift build --package-path $consumer --product HostPipeFixture -c $configuration --disable-automatic-resolution *> $buildLog - $fixtureCode = $LASTEXITCODE - if ($fixtureCode -ne 0) { - Get-Content $buildLog -Tail 60 - $results += [pscustomobject]@{ configuration = $configuration; fixtureExitCode = $fixtureCode; testExitCode = $fixtureCode } - $results | ConvertTo-Json | Set-Content (Join-Path $evidence 'results.json') - continue - } - $binaryPath = swift build --package-path $consumer --show-bin-path -c $configuration - if ($LASTEXITCODE -ne 0) { throw 'Cannot locate native child executable' } - $env:HOST_PIPE_FIXTURE_PATH = Join-Path ($binaryPath | Select-Object -Last 1) 'HostPipeFixture.exe' - if (!(Test-Path $env:HOST_PIPE_FIXTURE_PATH -PathType Leaf)) { throw 'Native child executable is missing' } - $log = Join-Path $evidence "$configuration-tests.log" - swift test --package-path $consumer --no-parallel -c $configuration --disable-automatic-resolution -Xswiftc -enable-testing *> $log - $code = $LASTEXITCODE - Get-Content $log -Tail 60 - $results += [pscustomobject]@{ configuration = $configuration; fixtureExitCode = $fixtureCode; testExitCode = $code } - $results | ConvertTo-Json | Set-Content (Join-Path $evidence 'results.json') - } -} finally { - $env:PATH = $originalPath - $env:HOST_PIPE_FIXTURE_PATH = $originalFixture - $lock = Join-Path $consumer 'Package.resolved' - if (Test-Path $lock) { Copy-Item $lock $evidence } -} -if ($results.Where({ $_.testExitCode -ne 0 }).Count -gt 0) { exit 1 } -if ((Get-FileHash $shippingPath -Algorithm SHA256).Hash -ne $shippingHash) { - throw 'Shipping dependency lock changed during host MCP acceptance' -} diff --git a/Scripts/test-windows-process.ps1 b/Scripts/test-windows-process.ps1 deleted file mode 100644 index 443d1a2..0000000 --- a/Scripts/test-windows-process.ps1 +++ /dev/null @@ -1,119 +0,0 @@ -param( - [Parameter(Mandatory = $true)][string]$SDKPath, - [Parameter(Mandatory = $true)][string]$OutputDirectory -) - -$ErrorActionPreference = 'Stop' -$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path -$sdk = (Resolve-Path $SDKPath).Path -$sdkRevision = git -C $sdk rev-parse HEAD -if ($LASTEXITCODE -ne 0 -or $sdkRevision -cnotmatch '^[0-9a-f]{40}$') { throw 'Cannot identify SDK source' } -git -C $sdk diff --quiet HEAD -if ($LASTEXITCODE -ne 0) { throw 'Native process tests require an unchanged committed SDK candidate' } -$shippingLock = Get-Content (Join-Path $repository 'Package.resolved') -Raw | ConvertFrom-Json -$sdkPin = @($shippingLock.pins | Where-Object { $_.identity -eq 'swift-codex' }) -if ($sdkPin.Count -ne 1 -or $sdkPin[0].state.revision -ne $sdkRevision -or - $sdkPin[0].location -ne 'https://github.com/swift-library/swift-codex.git') { - throw 'Native process tests require the shipping SDK revision' -} -$adapterRevision = git -C $repository rev-parse HEAD -if ($LASTEXITCODE -ne 0) { throw 'Cannot identify adapter source' } -if (Test-Path $OutputDirectory) { throw 'Native process tests require a fresh output directory' } -New-Item -ItemType Directory -Path $OutputDirectory | Out-Null -$root = (Resolve-Path $OutputDirectory).Path -$evidence = Join-Path $root 'evidence' -$consumer = Join-Path $root 'consumer' -$sources = Join-Path $consumer 'Sources/ManagedProcess' -$tests = Join-Path $consumer 'Tests/ManagedProcessTests' -New-Item -ItemType Directory -Path $evidence, $sources, $tests | Out-Null -$fixture = Join-Path $repository 'Tests/WindowsProcess' -$manifest = (Get-Content (Join-Path $fixture 'Package.swift.template') -Raw).Replace( - '__SDK_PATH__', $sdk.Replace('\', '/').Replace('"', '\"')) -$manifest | Set-Content (Join-Path $consumer 'Package.swift') -Copy-Item (Join-Path $fixture 'ManagedProcessTests.swift') $tests -Copy-Item (Join-Path $fixture 'CommandRunnerTests.swift') $tests -Copy-Item (Join-Path $fixture 'PrivateDirectoryTests.swift') $tests -Copy-Item (Join-Path $fixture 'WorktreeFileSystemTests.swift') $tests -Copy-Item (Join-Path $consumer 'Package.swift') $evidence -$sourceRecords = @() -foreach ($name in @( - 'ManagedLineProcess.swift', 'ManagedLineProcess+Windows.swift', - 'CommandRunner.swift', 'CommandRunner+Windows.swift', 'WindowsCommandProcess.swift', - 'WindowsProcessJob.swift', 'WindowsFilePath.swift', 'WindowsExecutable.swift', - 'WindowsProcessEnvironment.swift', 'CodexConfig.swift', 'JSONValue.swift', - 'WindowsPrivateDirectory.swift', 'CodexWorktreeFileSystem.swift', 'CodexProcessEnvironment.swift' -)) { - $source = Join-Path $repository "Sources/CodexAdapter/$name" - Copy-Item $source $sources - $sourceRecords += [pscustomobject]@{ - source = "Sources/CodexAdapter/$name" - sha256 = (Get-FileHash $source -Algorithm SHA256).Hash.ToLowerInvariant() - } -} -[pscustomobject]@{ - sdkRevision = $sdkRevision - adapterRevision = $adapterRevision - sourceFiles = $sourceRecords - evidenceClass = 'native-adapter-lifecycle-using-complete-sdk-source' - authenticatedModel = $false - completeAdapter = $false -} | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $evidence 'inputs.json') - -$versionOutput = swift --version -if ($LASTEXITCODE -ne 0) { throw 'Swift unavailable' } -$versionOutput | Set-Content (Join-Path $evidence 'toolchain.txt') -$version = (($versionOutput -join "`n") -split 'Swift version ')[1].Split(' ')[0].Trim() -if ($version -notmatch '^\d+\.\d+\.\d+$') { throw 'Cannot identify Swift testing runtime version' } -$developer = Split-Path (Split-Path $env:SDKROOT.TrimEnd([char[]]'\/')) -$testing = Join-Path $developer "Library/Testing-$version/usr/bin64" -$xctest = Join-Path $developer "Library/XCTest-$version/usr/bin64" -if (!(Test-Path (Join-Path $testing 'Testing.dll')) -or !(Test-Path (Join-Path $xctest 'XCTest.dll'))) { - throw 'Missing selected SDK testing runtimes' -} -# These owners depend only on Foundation and WinSDK. Diagnose native API imports -# before building the complete SDK fixture and running both runtime configurations. -$filesystemSources = @('WindowsFilePath.swift', 'WindowsPrivateDirectory.swift', 'CodexWorktreeFileSystem.swift') | - ForEach-Object { Join-Path $sources $_ } -swiftc -typecheck -swift-version 6 -strict-concurrency=complete -module-name CodexNativeFileSystem @filesystemSources *> (Join-Path $evidence 'filesystem-typecheck.log') -$filesystemExit = $LASTEXITCODE -[pscustomobject]@{ exitCode = $filesystemExit } | ConvertTo-Json | Set-Content (Join-Path $evidence 'filesystem-typecheck.json') -if ($filesystemExit -ne 0) { - Get-Content (Join-Path $evidence 'filesystem-typecheck.log') - exit 1 -} - -$originalPath = $env:PATH -$originalFixture = $env:CODEX_WINDOWS_FIXTURE -$results = @() -try { - $env:PATH = "$testing;$xctest;$originalPath" - foreach ($configuration in @('debug', 'release')) { - $fixturePackage = Join-Path $sdk 'Tests/WindowsIntegration' - swift build --package-path $fixturePackage --product CodexProcessFixture -c $configuration *> (Join-Path $evidence "$configuration-fixture.log") - $fixtureExit = $LASTEXITCODE - $testExit = $null - if ($fixtureExit -eq 0) { - $binaryDirectory = swift build --package-path $fixturePackage -c $configuration --show-bin-path - if ($LASTEXITCODE -ne 0) { throw 'Cannot locate native fixture' } - $env:CODEX_WINDOWS_FIXTURE = Join-Path $binaryDirectory 'CodexProcessFixture.exe' - if (!(Test-Path $env:CODEX_WINDOWS_FIXTURE)) { throw 'Native fixture missing' } - swift test --package-path $consumer --no-parallel -c $configuration *> (Join-Path $evidence "$configuration-tests.log") - $testExit = $LASTEXITCODE - Get-Content (Join-Path $evidence "$configuration-tests.log") -Tail 55 - } else { - Get-Content (Join-Path $evidence "$configuration-fixture.log") -Tail 55 - } - $results += [pscustomobject]@{ - configuration = $configuration - fixtureExitCode = $fixtureExit - testExitCode = $testExit - } - $results | ConvertTo-Json | Set-Content (Join-Path $evidence 'results.json') - } -} finally { - $env:PATH = $originalPath - $env:CODEX_WINDOWS_FIXTURE = $originalFixture -} -if ($results.Where({ $_.fixtureExitCode -ne 0 -or $null -eq $_.testExitCode -or $_.testExitCode -ne 0 }).Count -gt 0) { - exit 1 -} diff --git a/Scripts/validate-windows-candidate.ps1 b/Scripts/validate-windows-candidate.ps1 deleted file mode 100644 index 9807e96..0000000 --- a/Scripts/validate-windows-candidate.ps1 +++ /dev/null @@ -1,203 +0,0 @@ -param( - [Parameter(Mandatory = $true)][string]$SDKRevision, - [switch]$DatabaseOnly -) - -$ErrorActionPreference = 'Stop' -if ($SDKRevision -cnotmatch '^[0-9a-f]{40}$') { throw 'Expected SDK must be an exact commit SHA' } -$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path -Set-Location $repository -$root = Join-Path $repository '.build/windows-candidate' -if (Test-Path $root) { throw 'Windows source audit requires a fresh candidate directory' } -$evidence = Join-Path $root 'evidence' -New-Item -ItemType Directory -Path $evidence | Out-Null -swift --version | Out-File (Join-Path $evidence 'toolchain.txt') -if ($LASTEXITCODE -ne 0) { throw 'Swift toolchain unavailable' } - -$shippingHash = (Get-FileHash Package.resolved -Algorithm SHA256).Hash -$shippingLock = Get-Content Package.resolved -Raw | ConvertFrom-Json -$sdkPin = @($shippingLock.pins | Where-Object { $_.identity -eq 'swift-codex' }) -$mcpPin = @($shippingLock.pins | Where-Object { $_.identity -eq 'swift-sdk' }) -if ($sdkPin.Count -ne 1 -or $sdkPin[0].state.revision -ne $SDKRevision -or - $sdkPin[0].location -ne 'https://github.com/swift-library/swift-codex.git' -or - $mcpPin.Count -ne 1 -or $mcpPin[0].location -ne 'https://github.com/computer-mcp/swift-sdk.git') { - throw 'Windows acceptance requires the declared shipping SDK and MCP graph' -} -Copy-Item Package.resolved (Join-Path $evidence 'shipping-Package.resolved') -swift package resolve *> (Join-Path $evidence 'resolve.log') -if ($LASTEXITCODE -ne 0) { throw 'Shipping dependency resolution failed' } -if ((Get-FileHash Package.resolved -Algorithm SHA256).Hash -ne $shippingHash) { - throw 'Shipping dependency lock changed' -} -foreach ($pin in @($sdkPin[0], $mcpPin[0])) { - $checkout = Join-Path $repository ".build/checkouts/$($pin.identity)" - $revision = git -C $checkout rev-parse HEAD - if ($LASTEXITCODE -ne 0 -or $revision -ne $pin.state.revision) { - throw "Resolved dependency revision differs: $($pin.identity)" - } - git -C $checkout diff --quiet HEAD - if ($LASTEXITCODE -ne 0) { throw "Resolved dependency source changed: $($pin.identity)" } -} -$adapterRevision = git rev-parse HEAD -[pscustomobject]@{ - adapterRevision = $adapterRevision - sdkRevision = $sdkPin[0].state.revision - mcpDependency = $mcpPin[0] - shippingLockSHA256 = $shippingHash.ToLowerInvariant() - evidenceClass = if ($DatabaseOnly) { 'native-database-lifetime' } else { 'native-shipping-dependency-build' } - completeAdapter = -not $DatabaseOnly.IsPresent - shippingDependenciesChanged = $false -} | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $evidence 'inputs.json') - -$sqlite = & (Join-Path $PSScriptRoot 'build-windows-sqlite.ps1') -OutputDirectory (Join-Path $evidence 'sqlite') -$buildArguments = @('--disable-automatic-resolution', '-Xcc', "-I$($sqlite.includeDirectory)", '-Xlinker', "/LIBPATH:$($sqlite.libraryDirectory)") -$results = @() -$targets = if ($DatabaseOnly) { @('GRDB') } else { - @('GRDB', 'Subprocess', 'ArgumentParser', 'MCP', 'CodexAppServerClient', 'CodexExec', 'CodexAdapter', 'CodexMCPAdapter') -} -foreach ($target in $targets) { - $log = Join-Path $evidence "$target.log" - swift build --target $target @buildArguments *> $log - $code = $LASTEXITCODE - Get-Content $log -Tail 50 - $results += [pscustomobject]@{ target = $target; exitCode = $code } - $results | ConvertTo-Json | Set-Content (Join-Path $evidence 'results.json') -} -Copy-Item Package.resolved (Join-Path $evidence 'candidate-Package.resolved') - -$runtimeExit = $null -if (!$DatabaseOnly -and $results.Where({ $_.target -eq 'CodexMCPAdapter' -and $_.exitCode -eq 0 }).Count -eq 1) { - # Target compilation does not link an executable or prove resource lookup. - swift build --product codex-mcp-adapter @buildArguments *> (Join-Path $evidence 'adapter-link.log') - $linkExit = $LASTEXITCODE - $runtimeExit = $linkExit - [pscustomobject]@{ configuration = 'debug'; linkExitCode = $linkExit } | - ConvertTo-Json | Set-Content (Join-Path $evidence 'linked-product-results.json') - Get-Content (Join-Path $evidence 'adapter-link.log') -Tail 50 - if ($linkExit -eq 0) { - $built = swift build --show-bin-path - if ($LASTEXITCODE -ne 0) { throw 'Cannot locate linked adapter' } - $product = Join-Path $evidence 'linked-product' - New-Item -ItemType Directory -Path $product | Out-Null - $binary = Join-Path $product 'codex-mcp-adapter.exe' - Copy-Item (Join-Path $built 'codex-mcp-adapter.exe') $binary - $resources = @(Get-ChildItem $built -Directory | Where-Object { $_.Name -match '\.(resources|bundle)$' }) - if ($resources.Count -eq 0) { throw 'Missing SwiftPM resource directory' } - foreach ($resource in $resources) { Copy-Item $resource.FullName $product -Recurse } - $inventory = @(Get-ChildItem $product -File -Recurse | ForEach-Object { - [pscustomobject]@{ - path = [System.IO.Path]::GetRelativePath($product, $_.FullName) - bytes = $_.Length - sha256 = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() - } - }) - $inventory | ConvertTo-Json | Set-Content (Join-Path $evidence 'linked-product-files.json') - $inspector = Get-Command llvm-readobj -ErrorAction SilentlyContinue - if ($inspector) { - & $inspector.Source --coff-imports $binary *> (Join-Path $evidence 'adapter-imports.txt') - if ($LASTEXITCODE -ne 0) { throw 'Native import inspection failed' } - } - & $binary --help *> (Join-Path $evidence 'adapter-help.txt') - if ($LASTEXITCODE -ne 0) { throw 'Relocated native adapter help failed' } - & $binary --version *> (Join-Path $evidence 'adapter-version.txt') - if ($LASTEXITCODE -ne 0) { throw 'Relocated native adapter version failed' } - - $fixture = Join-Path $repository 'Tests/WindowsAdapter' - $codexMetadata = Get-Content (Join-Path $fixture 'codex-binary.json') -Raw | ConvertFrom-Json - Copy-Item (Join-Path $fixture 'codex-binary.json') $evidence - $codexDirectory = Join-Path $root 'codex-binary' - New-Item -ItemType Directory -Path $codexDirectory | Out-Null - $codexArchive = Join-Path $codexDirectory 'codex.zip' - Invoke-WebRequest -Uri $codexMetadata.archiveURL -OutFile $codexArchive -TimeoutSec 120 -MaximumRetryCount 2 - if ((Get-FileHash $codexArchive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $codexMetadata.archiveSHA256) { - throw 'Native Codex archive checksum mismatch' - } - Expand-Archive -Path $codexArchive -DestinationPath $codexDirectory - $codexBinary = Join-Path $codexDirectory $codexMetadata.executable - if ((Get-FileHash $codexBinary -Algorithm SHA256).Hash.ToLowerInvariant() -ne $codexMetadata.executableSHA256) { - throw 'Native Codex executable checksum mismatch' - } - $codexVersion = & $codexBinary --version - if ($LASTEXITCODE -ne 0 -or $codexVersion -ne "codex-cli $($codexMetadata.version)") { - throw 'Unexpected native Codex version' - } - [pscustomobject]@{ - version = $codexVersion - executableSHA256 = (Get-FileHash $codexBinary -Algorithm SHA256).Hash.ToLowerInvariant() - installed = $false - bundled = $false - } | ConvertTo-Json | Set-Content (Join-Path $evidence 'codex-binary-receipt.json') - python (Join-Path $fixture 'ProtocolCheck.py') --adapter $binary --codex $codexBinary ` - --evidence-directory (Join-Path $evidence 'adapter-protocol') ` - *> (Join-Path $evidence 'adapter-protocol.log') - $runtimeExit = $LASTEXITCODE - Get-Content (Join-Path $evidence 'adapter-protocol.log') -Tail 60 - [pscustomobject]@{ - configuration = 'debug' - linkExitCode = $linkExit - protocolExitCode = $runtimeExit - runtimeEnvironment = 'selected-toolchain' - authenticatedModel = $false - cleanMachineRelocation = $false - } | ConvertTo-Json | Set-Content (Join-Path $evidence 'linked-product-results.json') - } -} - -$databaseExit = $null -if ($results.Where({ $_.target -eq 'GRDB' -and $_.exitCode -eq 0 }).Count -eq 1) { - $consumer = Join-Path $root 'database-consumer' - $tests = Join-Path $consumer 'Tests/DatabaseTests' - New-Item -ItemType Directory -Path $tests | Out-Null - $grdb = (Resolve-Path '.build/checkouts/GRDB.swift').Path - $grdbRevision = git -C $grdb rev-parse HEAD - if ($LASTEXITCODE -ne 0) { throw 'Cannot identify resolved GRDB source' } - $lock = Get-Content Package.resolved -Raw | ConvertFrom-Json - $grdbPin = @($lock.pins | Where-Object { $_.identity -eq 'grdb.swift' }) - if ($grdbPin.Count -ne 1 -or $grdbPin[0].state.revision -ne $grdbRevision) { - throw 'GRDB consumer source differs from the resolved lock' - } - $fixture = Join-Path $repository 'Tests/WindowsSQLite' - $manifest = (Get-Content (Join-Path $fixture 'Package.swift.template') -Raw).Replace( - '__GRDB_PATH__', $grdb.Replace('\', '/')) - $manifest | Set-Content (Join-Path $consumer 'Package.swift') - Copy-Item (Join-Path $fixture 'GRDBTests.swift') $tests - Copy-Item (Join-Path $fixture 'DirectoryLifetimeTests.swift') $tests - $directorySources = @() - foreach ($name in @('WindowsPrivateDirectory.swift', 'WindowsFilePath.swift')) { - $source = Join-Path $repository "Sources/CodexAdapter/$name" - Copy-Item $source $tests - $directorySources += [pscustomobject]@{ - source = "Sources/CodexAdapter/$name" - sha256 = (Get-FileHash $source -Algorithm SHA256).Hash.ToLowerInvariant() - } - } - $directorySources | ConvertTo-Json | Set-Content (Join-Path $evidence 'database-directory-sources.json') - $grdbRevision | Set-Content (Join-Path $evidence 'grdb-revision.txt') - Copy-Item (Join-Path $consumer 'Package.swift') (Join-Path $evidence 'database-Package.swift') - - $version = ((Get-Content (Join-Path $evidence 'toolchain.txt') -Raw) -split 'Swift version ')[1].Split(' ')[0].Trim() - if ($version -notmatch '^\d+\.\d+\.\d+$') { throw 'Cannot identify Swift testing runtime version' } - $developer = Split-Path (Split-Path $env:SDKROOT.TrimEnd([char[]]'\/')) - $testing = Join-Path $developer "Library/Testing-$version/usr/bin64" - $xctest = Join-Path $developer "Library/XCTest-$version/usr/bin64" - if (!(Test-Path (Join-Path $testing 'Testing.dll')) -or !(Test-Path (Join-Path $xctest 'XCTest.dll'))) { - throw 'Missing selected SDK testing runtimes' - } - $originalPath = $env:PATH - try { - $env:PATH = "$testing;$xctest;$originalPath" - swift package --package-path $consumer resolve *> (Join-Path $evidence 'grdb-resolve.log') - if ($LASTEXITCODE -ne 0) { throw 'GRDB consumer resolution failed' } - swift test --package-path $consumer --no-parallel @buildArguments *> (Join-Path $evidence 'grdb-tests.log') - $databaseExit = $LASTEXITCODE - Get-Content (Join-Path $evidence 'grdb-tests.log') -Tail 50 - } finally { - $env:PATH = $originalPath - } -} -[pscustomobject]@{ grdbTestExitCode = $databaseExit } | ConvertTo-Json | Set-Content (Join-Path $evidence 'database-results.json') -if ((Get-FileHash Package.resolved -Algorithm SHA256).Hash -ne $shippingHash) { - throw 'Shipping dependency lock changed during native acceptance' -} -if ($results.Where({ $_.exitCode -ne 0 }).Count -gt 0 -or $databaseExit -ne 0 -or - (!$DatabaseOnly -and ($null -eq $runtimeExit -or $runtimeExit -ne 0))) { exit 1 } diff --git a/Scripts/windows-sqlite.json b/Scripts/windows-sqlite.json deleted file mode 100644 index 970da8e..0000000 --- a/Scripts/windows-sqlite.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "3.53.4", - "url": "https://www.sqlite.org/2026/sqlite-amalgamation-3530400.zip", - "size": 2946650, - "sha256": "1e71ddf93849c6a6ecf58b827c0692073d2dd7ee40196158068f7b29f422e87d", - "sha3_256": "628a44cfe82c66aed1ccbbe85a562d2e33ebe64b3288981ed76285612227934e", - "directory": "sqlite-amalgamation-3530400", - "compileDefinitions": [ - "SQLITE_THREADSAFE=1", - "SQLITE_ENABLE_FTS5", - "SQLITE_ENABLE_SNAPSHOT", - "SQLITE_ENABLE_COLUMN_METADATA" - ] -} diff --git a/Scripts/windows_package.py b/Scripts/windows_package.py deleted file mode 100644 index 2379521..0000000 --- a/Scripts/windows_package.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Stage inspected Windows runtime inputs and their original source notices.""" - -import json -import os -from pathlib import Path, PurePosixPath -import re -import shutil - -import windows_runtime - - -def notice_sources(root): - metadata = json.loads((root / "sources.json").read_text(encoding="utf-8")) - if metadata.get("schema_version") != 1 or not metadata.get("sources"): - raise ValueError("Missing Windows runtime notice provenance") - names = set() - for source in metadata["sources"]: - name = source["file"] - if not isinstance(name, str): - raise ValueError("Invalid Windows runtime notice path") - path = PurePosixPath(name) - if (path.is_absolute() or path.as_posix() != name - or any(part in {"", ".", ".."} for part in name.split("/")) - or ":" in name or "\\" in name or name.casefold() in names): - raise ValueError("Invalid Windows runtime notice path") - names.add(name.casefold()) - local = root.joinpath(*path.parts) - windows_runtime.regular_file(local) - if local.stat().st_size != source["bytes"] or windows_runtime.digest(local) != source["sha256"]: - raise ValueError(f"Windows runtime notice differs from its upstream source: {name}") - coverage = metadata.get("runtime_libraries", {}) - if not coverage: - raise ValueError("Missing runtime library notice mapping") - for name, declaration in coverage.items(): - if not re.fullmatch(r"[a-z0-9_.-]+\.dll", name): - raise ValueError("Invalid runtime library notice name") - scope = declaration.get("license_scope") - sources = declaration.get("sources") - if (not isinstance(sources, list) - or any(not isinstance(source, str) or source.casefold() not in names for source in sources) - or scope not in {"open-source", "Microsoft Visual C++ Redistributable"} - or (scope == "open-source" and not sources)): - raise ValueError("Runtime library has incomplete notice coverage") - return metadata - - -def stage_runtime(repo, binary_directory, notices, sqlite, swift, command, copy_file, copy_tree): - notice_root = repo / "Vendor/SwiftWindowsRuntime" - metadata = notice_sources(notice_root) - version = re.search(r"Swift version (\d+\.\d+\.\d+)", command([swift, "--version"], repo)) - if version is None or version[1] != metadata["swift_release"]: - raise ValueError("Swift runtime version must match the reviewed notice provenance") - runtime_directories = [] - for entry in os.environ.get("PATH", "").split(os.pathsep): - directory = Path(entry) - if entry and (directory / "swiftCore.dll").is_file() and directory not in runtime_directories: - runtime_directories.append(directory) - inspector = shutil.which("llvm-readobj") - if not runtime_directories or inspector is None: - raise ValueError("Selected Swift runtime directories and llvm-readobj are required") - report = windows_runtime.audit(binary_directory / "codex-mcp-adapter.exe", runtime_directories, - Path(os.environ["SystemRoot"]) / "System32", Path(inspector)) - coverage = metadata["runtime_libraries"] - copied = [] - prerequisites = [] - for row in report["libraries"]: - if row["role"] == "external-msvc-runtime": - windows_runtime.version_tuple(row["version"]) - prerequisites.append({"name": row["name"], "minimum_version": row["version"], - "build_input_sha256": row["sha256"], "architecture": row["architecture"]}) - continue - if row["role"] != "runtime": - continue - name = row["name"].casefold() - declaration = coverage.get(name) - if declaration is None: - raise ValueError(f"Runtime library lacks reviewed notice coverage: {row['name']}") - if declaration["license_scope"] != "open-source" or windows_runtime.is_msvc_runtime(name): - raise ValueError(f"Runtime library is an external prerequisite: {row['name']}") - source = Path(row["path"]) - target = binary_directory / source.name - if os.path.lexists(target): - raise ValueError("Runtime DLL collides with the product payload") - copy_file(source, target) - if windows_runtime.digest(target) != row["sha256"]: - raise ValueError("Copied runtime DLL differs from its inspected source") - copied.append({"file": target.name, "sha256": row["sha256"], "bytes": row["bytes"], - "notice_coverage": declaration}) - if not any(row["file"].casefold() == "swiftcore.dll" for row in copied): - raise ValueError("The native adapter must include its Swift runtime") - if any(windows_runtime.is_msvc_runtime(name) for name in windows_runtime.directory_files(binary_directory)): - raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled") - if not prerequisites: - raise ValueError("Native runtime closure must declare its Microsoft prerequisites") - copy_tree(notice_root, notices / "windows-runtime") - # The original SQLite header retains its public-domain statement verbatim. - sqlite_notices = notices / "sqlite" - sqlite_notices.mkdir() - header = Path(sqlite["includeDirectory"]) / "sqlite3.h" - if windows_runtime.digest(header) != sqlite["receipt"]["headerSHA256"]: - raise ValueError("SQLite notice input differs from the compiled source") - copy_file(header, sqlite_notices / header.name) - copy_file(repo / "Scripts/windows-sqlite.json", sqlite_notices / "source.json") - return {"architecture": {"aarch64": "arm64", "x86_64": "x86_64"}[report["architecture"]], - "swift_version": version[1], "libraries": copied, - "external_prerequisites": prerequisites, - "sqlite_source": sqlite["receipt"]["source"], - "sqlite_library_sha256": sqlite["receipt"]["librarySHA256"], - "notice_metadata_sha256": windows_runtime.digest(notice_root / "sources.json"), - "system_imports": [row["name"] for row in report["libraries"] - if row["role"] in {"windows-system", "windows-api-set"}]} diff --git a/Scripts/windows_runtime.py b/Scripts/windows_runtime.py deleted file mode 100644 index 90ac88e..0000000 --- a/Scripts/windows_runtime.py +++ /dev/null @@ -1,294 +0,0 @@ -#!/usr/bin/env python3 -"""Inspect a Windows executable's recursive runtime imports without loading it.""" - -import argparse -import ctypes -import hashlib -import json -import os -from pathlib import Path -import re -import shutil -import stat -import struct -import subprocess - - -def digest(path): - with path.open("rb") as source: - return hashlib.file_digest(source, "sha256").hexdigest() - - -def regular_file(path): - metadata = path.lstat() - if (not stat.S_ISREG(metadata.st_mode) - or getattr(metadata, "st_file_attributes", 0) & 0x400): - raise ValueError(f"Runtime input is a link or special file: {path}") - - -def is_msvc_runtime(name): - return re.fullmatch(r"(?:vcruntime|msvcp|concrt|vccorlib)\d[a-z0-9_]*\.dll", name.casefold()) is not None - - -def pe_architecture(path): - regular_file(path) - with path.open("rb") as source: - header = source.read(64) - if len(header) != 64 or header[:2] != b"MZ": - raise ValueError(f"Missing PE header: {path}") - source.seek(struct.unpack_from(" 1024 * 1024: - raise ValueError(f"Missing or oversized file version resource: {path}") - buffer = ctypes.create_string_buffer(size) - if not version.GetFileVersionInfoW(str(path), 0, size, buffer): - raise ctypes.WinError(ctypes.get_last_error()) - pointer, length = wintypes.LPVOID(), wintypes.UINT() - if not version.VerQueryValueW(buffer, "\\", ctypes.byref(pointer), ctypes.byref(length)) or length.value < 52: - raise ValueError(f"Missing fixed file version: {path}") - fields = ctypes.cast(pointer, ctypes.POINTER(wintypes.DWORD)) - if fields[0] != 0xFEEF04BD: - raise ValueError(f"Invalid fixed file version signature: {path}") - return ".".join(map(str, (fields[2] >> 16, fields[2] & 0xFFFF, fields[3] >> 16, fields[3] & 0xFFFF))) - - -def version_tuple(value): - if not isinstance(value, str) or not re.fullmatch(r"\d{1,5}(?:\.\d{1,5}){3}", value): - raise ValueError("Runtime version must have four numeric components") - result = tuple(map(int, value.split("."))) - if max(result) > 65535: - raise ValueError("Runtime version component exceeds its native bound") - return result - - -def imports(path, inspector): - regular_file(path) - result = subprocess.run([str(inspector), "--file-headers", "--coff-imports", str(path)], - capture_output=True, text=True, check=True, timeout=60) - architecture = re.search(r"^Arch: (\S+)$", result.stdout, re.MULTILINE) - if architecture is None or architecture[1] not in {"x86_64", "aarch64"}: - raise ValueError(f"Unsupported or missing PE architecture: {path}") - names = re.findall(r"^ Name: (.+)$", result.stdout, re.MULTILINE) - for name in names: - if not re.fullmatch(r"[A-Za-z0-9_.-]+\.dll", name, re.IGNORECASE): - raise ValueError(f"Invalid DLL import: {name!r}") - return architecture[1], sorted(set(names), key=str.casefold) - - -def directory_files(directory): - metadata = directory.lstat() - if (not stat.S_ISDIR(metadata.st_mode) - or getattr(metadata, "st_file_attributes", 0) & 0x400): - raise ValueError(f"Runtime directory is a link or special file: {directory}") - result = {} - for path in directory.iterdir(): - if path.suffix.lower() == ".dll": - key = path.name.casefold() - if key in result: - raise ValueError(f"Case-colliding DLL names in {directory}: {path.name}") - result[key] = path - return result - - -def resolve(name, runtime_files, system_files): - key = name.casefold() - # API-set contracts are resolved by Windows, not independent redistributable files. - if key.startswith(("api-ms-win-", "ext-ms-win-")): - return "windows-api-set", None - candidates = [files[key] for files in runtime_files if key in files] - if candidates: - for path in candidates: - regular_file(path) - if len({digest(path) for path in candidates}) != 1: - raise ValueError(f"Conflicting runtime DLL sources for {name}: {candidates}") - return ("external-msvc-runtime" if is_msvc_runtime(name) else "runtime"), candidates[0] - if key in system_files: - # Visual C++ redistributables are not Windows OS components, even in System32. - role = ("external-msvc-runtime" if is_msvc_runtime(name) - else "windows-system") - regular_file(system_files[key]) - return role, system_files[key] - raise ValueError(f"Unresolved DLL import: {name}") - - -def audit(executable, runtime_directories, system_directory, inspector): - runtime_files = [directory_files(path) for path in runtime_directories] - system_files = directory_files(system_directory) - architecture, direct = imports(executable, inspector) - queue = [(name, executable.name) for name in direct] - libraries = {} - while queue: - name, owner = queue.pop(0) - key = name.casefold() - if key in libraries: - if owner not in libraries[key]["imported_by"]: - libraries[key]["imported_by"].append(owner) - continue - role, path = resolve(name, runtime_files, system_files) - entry = {"name": name, "role": role, "imported_by": [owner]} - libraries[key] = entry - if path is not None: - entry.update(path=str(path), bytes=path.stat().st_size, sha256=digest(path)) - if role in {"runtime", "external-msvc-runtime"}: - native_arch, dependencies = imports(path, inspector) - if native_arch != architecture: - raise ValueError(f"Runtime architecture mismatch: {path}: {native_arch} != {architecture}") - entry.update(architecture=native_arch, imports=dependencies) - if role == "external-msvc-runtime": - entry["version"] = file_version(path) - queue.extend((dependency, name) for dependency in dependencies) - return {"executable": str(executable), "sha256": digest(executable), - "architecture": architecture, "runtime_directories": list(map(str, runtime_directories)), - "system_directory": str(system_directory), "inspector": str(inspector), - "imports": direct, "libraries": sorted(libraries.values(), key=lambda row: row["name"].casefold()), - "evidence_class": "static-native-import-closure", "relocation_verified": False, - "dynamic_loads_verified": False, "distribution_notices_verified": False} - - -def loaded_modules(pid): - """Observe the live process through a retained handle, independent of PATH.""" - if os.name != "nt": - raise ValueError("Native module observation requires Windows") - from ctypes import wintypes - kernel = ctypes.WinDLL("kernel32", use_last_error=True) - kernel.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] - kernel.OpenProcess.restype = wintypes.HANDLE - kernel.CloseHandle.argtypes = [wintypes.HANDLE] - kernel.CloseHandle.restype = wintypes.BOOL - kernel.K32EnumProcessModulesEx.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.HMODULE), - wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), wintypes.DWORD] - kernel.K32EnumProcessModulesEx.restype = wintypes.BOOL - kernel.K32GetModuleFileNameExW.argtypes = [wintypes.HANDLE, wintypes.HMODULE, - wintypes.LPWSTR, wintypes.DWORD] - kernel.K32GetModuleFileNameExW.restype = wintypes.DWORD - handle = kernel.OpenProcess(0x0400 | 0x0010, False, pid) # QUERY_INFORMATION | VM_READ - if not handle: - raise ctypes.WinError(ctypes.get_last_error()) - try: - count = 128 - for _ in range(4): - modules = (wintypes.HMODULE * count)() - needed = wintypes.DWORD() - if not kernel.K32EnumProcessModulesEx(handle, modules, ctypes.sizeof(modules), - ctypes.byref(needed), 3): - raise ctypes.WinError(ctypes.get_last_error()) - if needed.value <= ctypes.sizeof(modules): - break - count = needed.value // ctypes.sizeof(wintypes.HMODULE) + 32 - if count > 4096: - raise ValueError("Native module inventory exceeds the observation bound") - else: - raise ValueError("Native module inventory changed during observation") - paths = [] - for module in modules[:needed.value // ctypes.sizeof(wintypes.HMODULE)]: - buffer = ctypes.create_unicode_buffer(32768) - length = kernel.K32GetModuleFileNameExW(handle, module, buffer, len(buffer)) - if not length: - raise ctypes.WinError(ctypes.get_last_error()) - if length >= len(buffer): - raise ValueError("Native module path was truncated") - paths.append(Path(buffer.value)) - return paths - finally: - kernel.CloseHandle(handle) - - -def verify_prerequisites(executable, system_directory, requirements): - local = directory_files(executable.parent) - if any(is_msvc_runtime(name) for name in local): - raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled") - architecture = pe_architecture(executable) - system = directory_files(system_directory) - result, seen = [], set() - if not requirements: - raise ValueError("Missing Microsoft runtime prerequisite declarations") - for requirement in requirements: - name = requirement["name"].casefold() - if not is_msvc_runtime(name) or name in seen: - raise ValueError("Invalid or duplicate Microsoft runtime prerequisite") - seen.add(name) - minimum = version_tuple(requirement["minimum_version"]) - path = system.get(name) - if path is None: - raise ValueError(f"Install the official Microsoft Visual C++ runtime: missing {name}") - if pe_architecture(path) != architecture: - raise ValueError(f"Microsoft runtime architecture mismatch: {path}") - current = file_version(path) - if version_tuple(current) < minimum: - raise ValueError(f"Update the official Microsoft Visual C++ runtime: {name} {current} < {requirement['minimum_version']}") - result.append({"name": name, "path": str(path), "version": current, - "minimum_version": requirement["minimum_version"], "architecture": architecture, - "sha256": digest(path), "role": "external-msvc-runtime"}) - return result - - -def verify_app_local_modules(modules, executable, system_directory, prerequisites=()): - local = directory_files(executable.parent) - if any(is_msvc_runtime(name) for name in local): - raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled") - external = {row["name"].casefold(): row for row in prerequisites} - observed = set() - result = [] - for path in modules: - name = path.name.casefold() - regular_file(path) - if is_msvc_runtime(name): - expected = external.get(name) - if expected is None or not path.samefile(Path(expected["path"])) or digest(path) != expected["sha256"]: - raise ValueError(f"Process loaded an unverified Microsoft runtime: {path}") - role = "external-msvc-runtime" - elif name in local: - if not path.samefile(local[name]): - raise ValueError(f"Packaged runtime was loaded from outside the package: {path}") - role = "app-local-runtime" - observed.add(name) - elif path.samefile(executable): - role = "adapter" - elif path.parent.samefile(system_directory): - role = "windows-system" - else: - raise ValueError(f"Process loaded an undeclared external module: {path}") - result.append({"path": str(path), "role": role, "sha256": digest(path)}) - if "swiftcore.dll" not in observed: - raise ValueError("No app-local Swift runtime was observed") - return result - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--executable", required=True, type=Path) - parser.add_argument("--runtime-directory", action="append", required=True, type=Path) - parser.add_argument("--system-directory", required=True, type=Path) - parser.add_argument("--inspector", type=Path, default=shutil.which("llvm-readobj")) - parser.add_argument("--output", required=True, type=Path) - args = parser.parse_args() - if args.inspector is None: - parser.error("llvm-readobj is required") - report = audit(args.executable, args.runtime_directory, args.system_directory, args.inspector) - with args.output.open("x", encoding="utf-8") as output: - json.dump(report, output, indent=2) - output.write("\n") - print(json.dumps({"architecture": report["architecture"], "libraries": len(report["libraries"]), - "report": str(args.output)})) diff --git a/Sources/CodexAdapter/AppServerSchema.swift b/Sources/CodexAdapter/AppServerSchema.swift index 4070701..92f30ee 100644 --- a/Sources/CodexAdapter/AppServerSchema.swift +++ b/Sources/CodexAdapter/AppServerSchema.swift @@ -1,12 +1,7 @@ import CodexAppServerRuntime +import CryptoKit import Foundation -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - typealias AppServerJSON = CodexAppServerConnectionFoundation.JSONValue enum SchemaError: Error, Equatable, LocalizedError { diff --git a/Sources/CodexAdapter/CodexAppServerRuntime.swift b/Sources/CodexAdapter/CodexAppServerRuntime.swift index 65058de..1b9b3d5 100644 --- a/Sources/CodexAdapter/CodexAppServerRuntime.swift +++ b/Sources/CodexAdapter/CodexAppServerRuntime.swift @@ -1,13 +1,8 @@ import CodexAppServerClient import CodexAppServerProtocol +import CryptoKit import Foundation -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - struct CodexRuntimeOwner: Codable, Equatable, Sendable { let workspaceID: String? let profileID: String? diff --git a/Sources/CodexAdapter/CodexConfig.swift b/Sources/CodexAdapter/CodexConfig.swift index 57cb6e7..6db1a47 100644 --- a/Sources/CodexAdapter/CodexConfig.swift +++ b/Sources/CodexAdapter/CodexConfig.swift @@ -141,45 +141,36 @@ package struct CodexConfig: Codable, Equatable, Sendable { } func resolvedExecutableURL(workspaceURL: URL, environment: [String: String]) throws -> URL { - #if os(Windows) - do { - return try WindowsExecutable.resolve( - executable, workspace: workspaceURL, environment: environment) - } catch { - throw ConfigurationError.invalid(error.localizedDescription) + let candidates: [URL] + if executable.contains("/") { + candidates = [ + executable.hasPrefix("/") + ? URL(fileURLWithPath: executable) + : workspaceURL.appendingPathComponent(executable) + ] + } else if let path = environment["PATH"] { + candidates = path.split(separator: ":", omittingEmptySubsequences: false).map { entry in + let directory = + entry.hasPrefix("/") + ? URL(fileURLWithPath: String(entry), isDirectory: true) + : workspaceURL.appendingPathComponent(String(entry), isDirectory: true) + return directory.appendingPathComponent(executable) } - #else - let candidates: [URL] - if executable.contains("/") { - candidates = [ - executable.hasPrefix("/") - ? URL(fileURLWithPath: executable) - : workspaceURL.appendingPathComponent(executable) - ] - } else if let path = environment["PATH"] { - candidates = path.split(separator: ":", omittingEmptySubsequences: false).map { entry in - let directory = - entry.hasPrefix("/") - ? URL(fileURLWithPath: String(entry), isDirectory: true) - : workspaceURL.appendingPathComponent(String(entry), isDirectory: true) - return directory.appendingPathComponent(executable) - } - } else { - candidates = [] - } - for candidate in candidates { - let url = candidate.standardizedFileURL - var isDirectory: ObjCBool = false - if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory), - !isDirectory.boolValue, FileManager.default.isExecutableFile(atPath: url.path) - { - return url - } + } else { + candidates = [] + } + for candidate in candidates { + let url = candidate.standardizedFileURL + var isDirectory: ObjCBool = false + if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory), + !isDirectory.boolValue, FileManager.default.isExecutableFile(atPath: url.path) + { + return url } - throw ConfigurationError.invalid( - "Cannot resolve configured Codex executable '\(executable)' in the launch workspace and PATH." - ) - #endif + } + throw ConfigurationError.invalid( + "Cannot resolve configured Codex executable '\(executable)' in the launch workspace and PATH." + ) } } diff --git a/Sources/CodexAdapter/CodexDatabase.swift b/Sources/CodexAdapter/CodexDatabase.swift index 6f4fe1f..99fc86d 100644 --- a/Sources/CodexAdapter/CodexDatabase.swift +++ b/Sources/CodexAdapter/CodexDatabase.swift @@ -12,15 +12,6 @@ final class CodexDatabase: @unchecked Sendable { fileURL = URL(fileURLWithPath: path).standardizedFileURL var configuration = Configuration() configuration.busyMode = .timeout(5) - #if os(Windows) - let directories = try ([path] + [worktreeLeasePath].compactMap { $0 }).map { - try WindowsPrivateDirectory(URL(fileURLWithPath: $0).deletingLastPathComponent()) - } - // GRDB retains configuration through connection close, keeping ancestry handles alive. - configuration.prepareDatabase { [directories] _ in - for directory in directories { try directory.validate() } - } - #endif writer = try DatabaseQueue(path: path, configuration: configuration) sharesWorktreeLeases = worktreeLeasePath != nil if let worktreeLeasePath { diff --git a/Sources/CodexAdapter/CodexHostMCPClient.swift b/Sources/CodexAdapter/CodexHostMCPClient.swift index 981c2d1..f6d79fe 100644 --- a/Sources/CodexAdapter/CodexHostMCPClient.swift +++ b/Sources/CodexAdapter/CodexHostMCPClient.swift @@ -1,20 +1,7 @@ +import CryptoKit import Foundation import MCP -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - -#if os(Windows) - typealias CodexHostMCPTransport = MCPInheritedPipeTransport - typealias CodexHostMCPEndpoint = MCPInheritedPipeEndpoint -#else - typealias CodexHostMCPTransport = MCPInheritedSocketTransport - typealias CodexHostMCPEndpoint = FileHandle -#endif - /// Host tools use standard MCP and the existing gateway policy/ticket tool surface. /// Caller-provided tool arguments cannot select this connection or its authority. actor CodexHostMCPClient: CodexHostTools, CodexManagedWorkspaceHost, @@ -24,7 +11,7 @@ actor CodexHostMCPClient: CodexHostTools, CodexManagedWorkspaceHost, private let owner: CodexRuntimeOwner private let workspaceID: String private let client = MCP.Client(name: "codex-host-tools", version: CodexAdapterBuildInfo.version) - private let transport: CodexHostMCPTransport + private let transport: MCPInheritedSocketTransport private let requestTimeout: Duration private var startup: Task? private var connected = false @@ -39,24 +26,17 @@ actor CodexHostMCPClient: CodexHostTools, CodexManagedWorkspaceHost, static func inherited(environment: [String: String], context: CodexLaunchContext) throws -> Self? { - #if os(Windows) - guard let endpoint = try MCPInheritedPipeEndpoint.inherited(environment: environment) else { - return nil - } - return try Self(takingOwnershipOf: endpoint, owner: context.owner) - #else - guard let text = environment[descriptorEnvironmentKey] else { return nil } - guard environment["COMPUTER_MCP_HOST_CONTEXT"] != nil, - let descriptor = Int32(text), (3...9).contains(descriptor), String(descriptor) == text - else { throw ConfigurationError.invalid("Invalid inherited host MCP descriptor.") } - return try Self( - takingOwnershipOf: FileHandle(fileDescriptor: descriptor, closeOnDealloc: true), - owner: context.owner) - #endif + guard let text = environment[descriptorEnvironmentKey] else { return nil } + guard environment["COMPUTER_MCP_HOST_CONTEXT"] != nil, + let descriptor = Int32(text), (3...9).contains(descriptor), String(descriptor) == text + else { throw ConfigurationError.invalid("Invalid inherited host MCP descriptor.") } + return try Self( + takingOwnershipOf: FileHandle(fileDescriptor: descriptor, closeOnDealloc: true), + owner: context.owner) } init( - takingOwnershipOf handle: CodexHostMCPEndpoint, owner: CodexRuntimeOwner, + takingOwnershipOf handle: FileHandle, owner: CodexRuntimeOwner, requestTimeout: Duration = .seconds(30) ) throws { guard requestTimeout > .zero else { @@ -68,7 +48,7 @@ actor CodexHostMCPClient: CodexHostTools, CodexManagedWorkspaceHost, self.owner = owner self.workspaceID = workspaceID self.requestTimeout = requestTimeout - transport = try CodexHostMCPTransport(takingOwnershipOf: handle) + transport = try MCPInheritedSocketTransport(takingOwnershipOf: handle) } func risk(named name: String, arguments: JSONValue, requestID: String, workspaceID: String?) @@ -217,7 +197,7 @@ actor CodexHostMCPClient: CodexHostTools, CodexManagedWorkspaceHost, /// Disconnecting on timeout/cancellation resolves SDK waiters before the task group joins. /// A cancelled deadline after normal completion must not close a reusable connection. private static func bounded( - client: MCP.Client, transport: CodexHostMCPTransport, timeout: Duration, + client: MCP.Client, transport: MCPInheritedSocketTransport, timeout: Duration, operation: @escaping @Sendable () async throws -> T ) async throws -> T { let state = Completion() diff --git a/Sources/CodexAdapter/CodexLaunchContext.swift b/Sources/CodexAdapter/CodexLaunchContext.swift index 249a607..914165c 100644 --- a/Sources/CodexAdapter/CodexLaunchContext.swift +++ b/Sources/CodexAdapter/CodexLaunchContext.swift @@ -1,11 +1,6 @@ +import CryptoKit import Foundation -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - /// Host launch metadata binds task ownership; it never grants gateway administration. struct CodexLaunchContext: Sendable { let workspaceURL: URL @@ -94,11 +89,9 @@ struct CodexLaunchContext: Sendable { let leaseScope = SHA256.hash(data: leaseIdentity).map { String(format: "%02x", $0) }.joined() let leaseDirectory = stateDirectory.appendingPathComponent( "worktree-leases", isDirectory: true) - #if !os(Windows) - try FileManager.default.createDirectory( - at: leaseDirectory, withIntermediateDirectories: true, - attributes: [.posixPermissions: 0o700]) - #endif + try FileManager.default.createDirectory( + at: leaseDirectory, withIntermediateDirectories: true, + attributes: [.posixPermissions: 0o700]) worktreeLeasePath = leaseDirectory.appendingPathComponent(leaseScope + ".sqlite").path threadOwnerIndex = try makeThreadOwnerIndex(stateDirectory: stateDirectory) } else { @@ -106,11 +99,9 @@ struct CodexLaunchContext: Sendable { worktreeLeasePath = nil threadOwnerIndex = nil } - #if !os(Windows) - try FileManager.default.createDirectory( - at: storageDirectory, withIntermediateDirectories: true, - attributes: [.posixPermissions: 0o700]) - #endif + try FileManager.default.createDirectory( + at: storageDirectory, withIntermediateDirectories: true, + attributes: [.posixPermissions: 0o700]) let database = try CodexDatabase( path: storageDirectory.appendingPathComponent("codex.sqlite").path, worktreeLeasePath: worktreeLeasePath) @@ -136,11 +127,9 @@ struct CodexLaunchContext: Sendable { principalID, owner.profileID ?? "", owner.workspaceID ?? "", ]) let scope = SHA256.hash(data: identity).map { String(format: "%02x", $0) }.joined() - #if !os(Windows) - try FileManager.default.createDirectory( - at: stateDirectory, withIntermediateDirectories: true, - attributes: [.posixPermissions: 0o700]) - #endif + try FileManager.default.createDirectory( + at: stateDirectory, withIntermediateDirectories: true, + attributes: [.posixPermissions: 0o700]) let codexHome = ProcessInfo.processInfo.environment["CODEX_HOME"].map { URL(fileURLWithPath: $0) } ?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".codex") @@ -150,11 +139,7 @@ struct CodexLaunchContext: Sendable { } private static func isAbsolutePath(_ path: String) -> Bool { - #if os(Windows) - WindowsFilePath.isValid(path) && WindowsFilePath.isAbsolute(path) - #else - path.hasPrefix("/") - #endif + path.hasPrefix("/") } private struct Host: Decodable { diff --git a/Sources/CodexAdapter/CodexManagedWorktree.swift b/Sources/CodexAdapter/CodexManagedWorktree.swift index 4b4cd33..e1f0134 100644 --- a/Sources/CodexAdapter/CodexManagedWorktree.swift +++ b/Sources/CodexAdapter/CodexManagedWorktree.swift @@ -1,11 +1,6 @@ +import CryptoKit import Foundation -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - enum CodexManagedWorktreeState: String, Codable, Equatable, Sendable { case planned case provisioning @@ -335,12 +330,9 @@ enum CodexManagedWorktreeManager { } var createdLease: CodexWorktreeLease? var createdGitWorktree = false - var protections: [FileSystem.Protection] = [] - defer { withExtendedLifetime(protections) {} } do { - protections.append(try FileSystem.prepareRoot(expectedRoot)) - protections.append( - try FileSystem.prepareParent(target.deletingLastPathComponent(), containedIn: expectedRoot)) + try FileSystem.prepareRoot(expectedRoot) + try FileSystem.prepareParent(target.deletingLastPathComponent(), containedIn: expectedRoot) _ = try git( ["worktree", "add", "-b", record.branch, target.path, record.headOID], workingDirectory: sourceRoot, @@ -474,12 +466,6 @@ enum CodexManagedWorktreeManager { else { throw CodexManagedWorktreeError.unknown(managedWorktreeID) } - #if os(Windows) - let protection = try FileSystem.protectDirectory( - URL(fileURLWithPath: record.path).deletingLastPathComponent(), - containedIn: managedRootURL(database: database, override: managedRoot)) - defer { withExtendedLifetime(protection) {} } - #endif if record.state == .removing { try validateRemovedWorktree( record, database: database, liveRuntimeStatus: liveRuntimeStatus, @@ -554,12 +540,6 @@ enum CodexManagedWorktreeManager { cleanupOnly || (record.state == .removalPlanned && record.planExpiresAt.map({ $0 > now }) == true) else { throw CodexManagedWorktreeError.state("the removal plan is not current") } - #if os(Windows) - let protection = try FileSystem.protectDirectory( - URL(fileURLWithPath: record.path).deletingLastPathComponent(), - containedIn: managedRootURL(database: database, override: managedRoot)) - defer { withExtendedLifetime(protection) {} } - #endif if cleanupOnly { try validateRemovedWorktree( record, database: database, liveRuntimeStatus: liveRuntimeStatus, diff --git a/Sources/CodexAdapter/CodexOrchestration.swift b/Sources/CodexAdapter/CodexOrchestration.swift index 20c57c0..1796a3c 100644 --- a/Sources/CodexAdapter/CodexOrchestration.swift +++ b/Sources/CodexAdapter/CodexOrchestration.swift @@ -1,11 +1,6 @@ +import CryptoKit import Foundation -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - enum CodexOrchestrationRunState: String, Codable, Equatable, Sendable { case active case paused diff --git a/Sources/CodexAdapter/CodexProcessEnvironment.swift b/Sources/CodexAdapter/CodexProcessEnvironment.swift index 5244311..333afe4 100644 --- a/Sources/CodexAdapter/CodexProcessEnvironment.swift +++ b/Sources/CodexAdapter/CodexProcessEnvironment.swift @@ -1,8 +1,5 @@ import Foundation - -#if canImport(SystemConfiguration) - import SystemConfiguration -#endif +import SystemConfiguration internal struct SystemNetworkProxySettings: Equatable, Sendable { internal var httpProxy: String? @@ -23,14 +20,10 @@ internal struct SystemNetworkProxySettings: Equatable, Sendable { } internal static func current() -> Self { - #if canImport(SystemConfiguration) - guard let settings = SCDynamicStoreCopyProxies(nil) as? [String: Any] else { - return Self() - } - return resolved(from: settings) - #else + guard let settings = SCDynamicStoreCopyProxies(nil) as? [String: Any] else { return Self() - #endif + } + return resolved(from: settings) } internal static func resolved(from settings: [String: Any]) -> Self { @@ -107,12 +100,12 @@ internal enum CodexProcessEnvironment { systemProxy: SystemNetworkProxySettings = .current() ) -> [String: String] { var environment = base - for key in base.keys where parentSessionKeys.contains(where: { namesMatch(key, $0) }) { + for key in base.keys where parentSessionKeys.contains(key) { environment.removeValue(forKey: key) } let proxyKeys = httpKeys + httpsKeys + allKeys let hasInheritedProxy = environment.keys.contains { key in - proxyKeys.contains { namesMatch(key, $0) } + proxyKeys.contains(key) } if hasInheritedProxy { @@ -126,10 +119,10 @@ internal enum CodexProcessEnvironment { } let hasEffectiveProxy = environment.contains { key, value in - !value.isEmpty && proxyKeys.contains { namesMatch(key, $0) } + !value.isEmpty && proxyKeys.contains(key) } if hasEffectiveProxy { - if environment.keys.contains(where: { key in noProxyKeys.contains { namesMatch(key, $0) } }) { + if environment.keys.contains(where: { key in noProxyKeys.contains(key) }) { mirrorExistingValue(for: noProxyKeys, in: &environment) } else { let noProxy = normalizedBypassHosts(systemProxy.bypassHosts).joined(separator: ",") @@ -143,15 +136,13 @@ internal enum CodexProcessEnvironment { for keys: [String], in environment: inout [String: String] ) { - #if !os(Windows) - let existingValues = keys.compactMap { key in - environment[key].map { (key: key, value: $0) } - } - guard existingValues.count == 1, let existingValue = existingValues.first?.value else { - return - } - install(existingValue, for: keys, in: &environment) - #endif + let existingValues = keys.compactMap { key in + environment[key].map { (key: key, value: $0) } + } + guard existingValues.count == 1, let existingValue = existingValues.first?.value else { + return + } + install(existingValue, for: keys, in: &environment) } private static func install( @@ -160,24 +151,9 @@ internal enum CodexProcessEnvironment { in environment: inout [String: String] ) { guard let value else { return } - #if os(Windows) - // Windows already resolves names case-insensitively; aliases would be duplicates. - for key in keys.prefix(1) { - environment[key] = value - } - #else - for key in keys { - environment[key] = value - } - #endif - } - - static func namesMatch(_ lhs: String, _ rhs: String) -> Bool { - #if os(Windows) - return WindowsProcessEnvironment.namesMatch(lhs, rhs) - #else - return lhs == rhs - #endif + for key in keys { + environment[key] = value + } } private static func normalizedBypassHosts(_ hosts: [String]) -> [String] { diff --git a/Sources/CodexAdapter/CodexRecentThreadReader.swift b/Sources/CodexAdapter/CodexRecentThreadReader.swift index f91af08..85203a7 100644 --- a/Sources/CodexAdapter/CodexRecentThreadReader.swift +++ b/Sources/CodexAdapter/CodexRecentThreadReader.swift @@ -1,12 +1,7 @@ +import CryptoKit import Foundation import GRDB -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - struct CodexPersistedThreadMetadata: Equatable, Sendable { let id: String let rolloutURL: URL diff --git a/Sources/CodexAdapter/CodexRuntimeLease.swift b/Sources/CodexAdapter/CodexRuntimeLease.swift index a580702..2b64939 100644 --- a/Sources/CodexAdapter/CodexRuntimeLease.swift +++ b/Sources/CodexAdapter/CodexRuntimeLease.swift @@ -1,11 +1,6 @@ +import Darwin import Foundation -#if os(Windows) - import WinSDK -#else - import Darwin -#endif - struct CodexRuntimeRequestFailure: Codable, Equatable, Sendable { let kind: String let message: String @@ -156,16 +151,7 @@ enum CodexRuntimeMaintenance { /// Read-only evidence for persisted receipts. Unknown access and reused IDs retain ownership; /// a numeric ID never grants authority to signal the process or confirm descendant cleanup. func codexRuntimeProcessMayExist(_ processID: Int32?) -> Bool { - #if os(Windows) - guard let processID, DWORD(bitPattern: processID) > 1 else { return false } - guard let handle = OpenProcess(DWORD(SYNCHRONIZE), false, DWORD(bitPattern: processID)) else { - return GetLastError() != DWORD(ERROR_INVALID_PARAMETER) - } - defer { CloseHandle(handle) } - return WaitForSingleObject(handle, 0) != DWORD(WAIT_OBJECT_0) - #else - guard let processID, processID > 1 else { return false } - errno = 0 - return Darwin.kill(processID, 0) == 0 || errno != ESRCH - #endif + guard let processID, processID > 1 else { return false } + errno = 0 + return Darwin.kill(processID, 0) == 0 || errno != ESRCH } diff --git a/Sources/CodexAdapter/CodexStateMigration.swift b/Sources/CodexAdapter/CodexStateMigration.swift index d54c1f2..eb8cd7c 100644 --- a/Sources/CodexAdapter/CodexStateMigration.swift +++ b/Sources/CodexAdapter/CodexStateMigration.swift @@ -1,12 +1,7 @@ +import CryptoKit import Foundation import GRDB -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - /// Transfers only adapter-owned domain records. This is an explicit offline /// administration operation, not an MCP tool or a host-authority channel. enum CodexStateMigration { diff --git a/Sources/CodexAdapter/CodexThreadOwnerIndex.swift b/Sources/CodexAdapter/CodexThreadOwnerIndex.swift index 3c7cdc7..6b7ccc8 100644 --- a/Sources/CodexAdapter/CodexThreadOwnerIndex.swift +++ b/Sources/CodexAdapter/CodexThreadOwnerIndex.swift @@ -1,12 +1,7 @@ +import CryptoKit import Foundation import GRDB -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - /// Cross-subject affinity for native threads; all execution records remain in their subject database. final class CodexThreadOwnerIndex: @unchecked Sendable { private let database: DatabaseQueue @@ -21,12 +16,6 @@ final class CodexThreadOwnerIndex: @unchecked Sendable { .map { String(format: "%02x", $0) }.joined() var configuration = Configuration() configuration.busyMode = .timeout(5) - #if os(Windows) - let directory = try WindowsPrivateDirectory( - URL(fileURLWithPath: path).deletingLastPathComponent()) - // The connection owns this validation closure and its directory handles through close. - configuration.prepareDatabase { [directory] _ in try directory.validate() } - #endif database = try DatabaseQueue(path: path, configuration: configuration) try database.write { db in try db.execute( diff --git a/Sources/CodexAdapter/CodexThreadOwnershipReconciliation.swift b/Sources/CodexAdapter/CodexThreadOwnershipReconciliation.swift index 1cf6458..dfee8f5 100644 --- a/Sources/CodexAdapter/CodexThreadOwnershipReconciliation.swift +++ b/Sources/CodexAdapter/CodexThreadOwnershipReconciliation.swift @@ -1,11 +1,6 @@ +import CryptoKit import Foundation -#if canImport(CryptoKit) - import CryptoKit -#else - import Crypto -#endif - struct CodexThreadOwnershipReconciliationCandidate: Codable, Equatable, Sendable { let threadID: String let workspaceID: String? diff --git a/Sources/CodexAdapter/CodexWorktreeFileSystem.swift b/Sources/CodexAdapter/CodexWorktreeFileSystem.swift index 8b043cc..9a9ff71 100644 --- a/Sources/CodexAdapter/CodexWorktreeFileSystem.swift +++ b/Sources/CodexAdapter/CodexWorktreeFileSystem.swift @@ -1,224 +1,93 @@ +import Darwin import Foundation -#if os(Windows) - import WinSDK -#else - import Darwin -#endif - /// Filesystem ownership for managed Git operations; host authorization remains with the manager. enum CodexWorktreeFileSystem { - #if os(Windows) - typealias Protection = WindowsPrivateDirectory - static let gitExecutable = "git" - #else - struct Protection: Sendable {} - static let gitExecutable = "/usr/bin/git" - #endif - - static func prepareRoot(_ root: URL) throws -> Protection { - #if os(Windows) - return try WindowsPrivateDirectory(root) - #else - let fileManager = FileManager.default - if fileManager.fileExists(atPath: root.path) { - let values = try root.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey]) - guard values.isDirectory == true, values.isSymbolicLink != true else { - throw CodexManagedWorktreeError.invalid( - "the managed worktree root must be a real directory" - ) - } - let attributes = try fileManager.attributesOfItem(atPath: root.path) - if let owner = attributes[.ownerAccountID] as? NSNumber, owner.uint32Value != getuid() { - throw CodexManagedWorktreeError.invalid( - "the managed worktree root is owned by another user" - ) - } - } else { - try fileManager.createDirectory( - at: root, - withIntermediateDirectories: true, - attributes: [.posixPermissions: NSNumber(value: Int16(0o700))] - ) - } - try fileManager.setAttributes( - [.posixPermissions: NSNumber(value: Int16(0o700))], - ofItemAtPath: root.path - ) - - return Protection() - #endif - } + static let gitExecutable = "/usr/bin/git" - static func prepareParent(_ parent: URL, containedIn root: URL) throws -> Protection { - #if os(Windows) - return try WindowsPrivateDirectory(creatingDirectory: parent, containedIn: root) - #else - try FileManager.default.createDirectory( - at: parent, withIntermediateDirectories: true, - attributes: [.posixPermissions: NSNumber(value: Int16(0o700))]) - return try protectDirectory(parent, containedIn: root) - #endif - } - - /// Inspection never creates a missing directory or changes an existing directory's permissions. - static func protectDirectory(_ directory: URL, containedIn root: URL) throws -> Protection { - #if os(Windows) - return try WindowsPrivateDirectory(existingDirectory: directory, containedIn: root) - #else - let values = try directory.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey]) + static func prepareRoot(_ root: URL) throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: root.path) { + let values = try root.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey]) guard values.isDirectory == true, values.isSymbolicLink != true else { throw CodexManagedWorktreeError.invalid( - "managed worktree paths must be real directories, not symbolic links" + "the managed worktree root must be a real directory" ) } - let resolvedDirectory = directory.standardizedFileURL.resolvingSymlinksInPath() - let resolvedRoot = root.standardizedFileURL.resolvingSymlinksInPath() - guard - resolvedDirectory == resolvedRoot - || resolvedDirectory.path.hasPrefix(resolvedRoot.path + "/") - else { + let attributes = try fileManager.attributesOfItem(atPath: root.path) + if let owner = attributes[.ownerAccountID] as? NSNumber, owner.uint32Value != getuid() { throw CodexManagedWorktreeError.invalid( - "managed worktree path escaped the canonical managed root" + "the managed worktree root is owned by another user" ) } + } else { + try fileManager.createDirectory( + at: root, + withIntermediateDirectories: true, + attributes: [.posixPermissions: NSNumber(value: Int16(0o700))] + ) + } + try fileManager.setAttributes( + [.posixPermissions: NSNumber(value: Int16(0o700))], + ofItemAtPath: root.path + ) + } - return Protection() - #endif + static func prepareParent(_ parent: URL, containedIn root: URL) throws { + try FileManager.default.createDirectory( + at: parent, withIntermediateDirectories: true, + attributes: [.posixPermissions: NSNumber(value: Int16(0o700))]) + try validateDirectory(parent, containedIn: root) } + /// Inspection never creates a missing directory or changes an existing directory's permissions. static func validateDirectory(_ directory: URL, containedIn root: URL) throws { - let protection = try protectDirectory(directory, containedIn: root) - withExtendedLifetime(protection) {} + let values = try directory.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey]) + guard values.isDirectory == true, values.isSymbolicLink != true else { + throw CodexManagedWorktreeError.invalid( + "managed worktree paths must be real directories, not symbolic links" + ) + } + let resolvedDirectory = directory.standardizedFileURL.resolvingSymlinksInPath() + let resolvedRoot = root.standardizedFileURL.resolvingSymlinksInPath() + guard + resolvedDirectory == resolvedRoot + || resolvedDirectory.path.hasPrefix(resolvedRoot.path + "/") + else { + throw CodexManagedWorktreeError.invalid( + "managed worktree path escaped the canonical managed root" + ) + } } - /// A lexical precheck only. Existing directory containment also requires retained native ancestry. + /// A lexical precheck only. Existing directory containment requires canonical path validation. static func isDescendant(_ directory: URL, of root: URL) throws -> Bool { - #if os(Windows) - let path = try nativePath(directory) - let parent = try nativePath(root) - let prefix = parent + (parent.hasSuffix("\\") ? "" : "\\") - let units = Array(path.utf16) - let prefixUnits = Array(prefix.utf16) - guard units.count > prefixUnits.count else { return false } - return CompareStringOrdinal( - units, Int32(prefixUnits.count), prefixUnits, Int32(prefixUnits.count), true) == CSTR_EQUAL - #else - return directory.standardizedFileURL.path.hasPrefix(root.standardizedFileURL.path + "/") - #endif + return directory.standardizedFileURL.path.hasPrefix(root.standardizedFileURL.path + "/") } static func samePath(_ lhs: URL, _ rhs: URL) throws -> Bool { - #if os(Windows) - let left = Array(try nativePath(lhs).utf16) - let right = Array(try nativePath(rhs).utf16) - return CompareStringOrdinal( - left, Int32(left.count), right, Int32(right.count), true) == CSTR_EQUAL - #else - return lhs.standardizedFileURL.path == rhs.standardizedFileURL.path - #endif + return lhs.standardizedFileURL.path == rhs.standardizedFileURL.path } static func sameDirectory(_ lhs: URL, _ rhs: URL) throws -> Bool { - #if os(Windows) - let left = try openDirectory(lhs) - defer { CloseHandle(left) } - let right = try openDirectory(rhs) - defer { CloseHandle(right) } - return try WindowsDirectoryIdentity(left) == WindowsDirectoryIdentity(right) - #else - return lhs.path == rhs.path - #endif + return lhs.path == rhs.path } static func canonicalDirectory(_ path: String, relativeTo base: URL) throws -> URL { - #if os(Windows) - guard let absolute = WindowsFilePath.absolute(path, cwd: try nativePath(base)) else { - throw WindowsPrivateDirectoryError.invalid("Git returned an invalid native directory path.") - } - let handle = try openDirectory(URL(fileURLWithPath: absolute, isDirectory: true)) - defer { CloseHandle(handle) } - var output = [WCHAR](repeating: 0, count: 32_768) - let count = GetFinalPathNameByHandleW(handle, &output, DWORD(output.count), 0) - guard count > 0, count < output.count else { - throw WindowsPrivateDirectoryError.native("Resolve Git directory", GetLastError()) - } - var result = String(decoding: output.prefix(Int(count)), as: UTF16.self) - if result.hasPrefix("\\\\?\\UNC\\") { - result = "\\\\" + result.dropFirst(8) - } else if result.hasPrefix("\\\\?\\") { - result = String(result.dropFirst(4)) - } - guard WindowsFilePath.isValid(result), WindowsFilePath.isAbsolute(result) else { - throw WindowsPrivateDirectoryError.invalid( - "Git directory is outside the native path namespace.") - } - return URL(fileURLWithPath: result, isDirectory: true) - #else - let url = - path.hasPrefix("/") - ? URL(fileURLWithPath: path, isDirectory: true) - : base.appendingPathComponent(path, isDirectory: true) - return url.standardizedFileURL.resolvingSymlinksInPath() - #endif + let url = + path.hasPrefix("/") + ? URL(fileURLWithPath: path, isDirectory: true) + : base.appendingPathComponent(path, isDirectory: true) + return url.standardizedFileURL.resolvingSymlinksInPath() } static func isAbsent(_ url: URL) throws -> Bool { - #if os(Windows) - let handle = CreateFileW( - Array(try nativePath(url).utf16) + [0], DWORD(FILE_READ_ATTRIBUTES), - DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil, DWORD(OPEN_EXISTING), - DWORD(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT), nil) - if let handle, handle != INVALID_HANDLE_VALUE { - CloseHandle(handle) - return false - } - let code = GetLastError() - guard code == DWORD(ERROR_FILE_NOT_FOUND) || code == DWORD(ERROR_PATH_NOT_FOUND) else { - throw WindowsPrivateDirectoryError.native("Verify managed path absence", code) - } - return true - #else - var info = stat() - return lstat(url.path, &info) != 0 && errno == ENOENT - #endif + var info = stat() + return lstat(url.path, &info) != 0 && errno == ENOENT } static func provisionPathIsAvailable(_ url: URL) throws -> Bool { - #if os(Windows) - return try isAbsent(url) - #else - return !FileManager.default.fileExists(atPath: url.path) - #endif + return !FileManager.default.fileExists(atPath: url.path) } - #if os(Windows) - private static func nativePath(_ url: URL) throws -> String { - guard let native = WindowsFilePath.native(url), WindowsFilePath.isAbsolute(native), - let absolute = WindowsFilePath.absolute(native, cwd: native) - else { - throw WindowsPrivateDirectoryError.invalid("Managed paths must be native absolute paths.") - } - return absolute - } - - private static func openDirectory(_ url: URL) throws -> HANDLE { - guard - let handle = CreateFileW( - Array(try nativePath(url).utf16) + [0], DWORD(FILE_READ_ATTRIBUTES), - DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil, DWORD(OPEN_EXISTING), - DWORD(FILE_FLAG_BACKUP_SEMANTICS), nil), handle != INVALID_HANDLE_VALUE - else { throw WindowsPrivateDirectoryError.native("Open Git directory", GetLastError()) } - var information = BY_HANDLE_FILE_INFORMATION() - guard GetFileType(handle) == DWORD(FILE_TYPE_DISK), - GetFileInformationByHandle(handle, &information), - information.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) != 0 - else { - let code = GetLastError() - CloseHandle(handle) - throw WindowsPrivateDirectoryError.native("Inspect Git directory", code) - } - return handle - } - #endif } diff --git a/Sources/CodexAdapter/CommandRunner+Darwin.swift b/Sources/CodexAdapter/CommandRunner+Darwin.swift index 1e11805..09bda49 100644 --- a/Sources/CodexAdapter/CommandRunner+Darwin.swift +++ b/Sources/CodexAdapter/CommandRunner+Darwin.swift @@ -1,169 +1,167 @@ -#if !os(Windows) - import Foundation - - final class ProcessCommandRunner: CommandRunning, @unchecked Sendable { - init() {} - - func run( - executable: String, - arguments: [String], - workingDirectory: URL?, - environment: [String: String], - timeoutMilliseconds: Int, - maxOutputBytes: Int - ) throws -> CommandResult { - let result = try runData( - executable: executable, - arguments: arguments, - workingDirectory: workingDirectory, - environment: environment, - timeoutMilliseconds: timeoutMilliseconds, - maxOutputBytes: maxOutputBytes - ) - return CommandResult( - executable: result.executable, - arguments: result.arguments, - exitCode: result.exitCode, - timedOut: result.timedOut, - stdout: result.stdoutString, - stderr: result.stderrString, - stdoutTruncated: result.stdoutTruncated, - stderrTruncated: result.stderrTruncated - ) +import Foundation + +final class ProcessCommandRunner: CommandRunning, @unchecked Sendable { + init() {} + + func run( + executable: String, + arguments: [String], + workingDirectory: URL?, + environment: [String: String], + timeoutMilliseconds: Int, + maxOutputBytes: Int + ) throws -> CommandResult { + let result = try runData( + executable: executable, + arguments: arguments, + workingDirectory: workingDirectory, + environment: environment, + timeoutMilliseconds: timeoutMilliseconds, + maxOutputBytes: maxOutputBytes + ) + return CommandResult( + executable: result.executable, + arguments: result.arguments, + exitCode: result.exitCode, + timedOut: result.timedOut, + stdout: result.stdoutString, + stderr: result.stderrString, + stdoutTruncated: result.stdoutTruncated, + stderrTruncated: result.stderrTruncated + ) + } + + func runData( + executable: String, + arguments: [String], + workingDirectory: URL?, + environment: [String: String], + timeoutMilliseconds: Int, + maxOutputBytes: Int + ) throws -> CommandDataResult { + let process = Process() + configure(process: process, executable: executable, arguments: arguments) + process.currentDirectoryURL = workingDirectory + process.environment = ProcessInfo.processInfo.environment.merging(environment) { _, new in new } - func runData( - executable: String, - arguments: [String], - workingDirectory: URL?, - environment: [String: String], - timeoutMilliseconds: Int, - maxOutputBytes: Int - ) throws -> CommandDataResult { - let process = Process() - configure(process: process, executable: executable, arguments: arguments) - process.currentDirectoryURL = workingDirectory - process.environment = ProcessInfo.processInfo.environment.merging(environment) { _, new in new - } + let perStreamLimit = max(1, maxOutputBytes) + let stdout = OutputCollector(limit: perStreamLimit) + let stderr = OutputCollector(limit: perStreamLimit) + process.standardOutput = stdout.pipe + process.standardError = stderr.pipe - let perStreamLimit = max(1, maxOutputBytes) - let stdout = OutputCollector(limit: perStreamLimit) - let stderr = OutputCollector(limit: perStreamLimit) - process.standardOutput = stdout.pipe - process.standardError = stderr.pipe + let termination = DispatchSemaphore(value: 0) + process.terminationHandler = { _ in termination.signal() } - let termination = DispatchSemaphore(value: 0) - process.terminationHandler = { _ in termination.signal() } + do { + try process.run() + } catch { + throw CommandRunnerError.launchFailed(error.localizedDescription) + } - do { - try process.run() - } catch { - throw CommandRunnerError.launchFailed(error.localizedDescription) + let waitResult = termination.wait(timeout: .now() + .milliseconds(timeoutMilliseconds)) + var timedOut = false + if waitResult == .timedOut { + timedOut = true + process.terminate() + _ = termination.wait(timeout: .now() + .seconds(2)) + if process.isRunning { + process.interrupt() } + } - let waitResult = termination.wait(timeout: .now() + .milliseconds(timeoutMilliseconds)) - var timedOut = false - if waitResult == .timedOut { - timedOut = true - process.terminate() - _ = termination.wait(timeout: .now() + .seconds(2)) - if process.isRunning { - process.interrupt() - } + let processHasExited = !process.isRunning + stdout.stop(processHasExited: processHasExited) + stderr.stop(processHasExited: processHasExited) + + return CommandDataResult( + executable: executable, + arguments: arguments, + exitCode: process.isRunning ? nil : process.terminationStatus, + timedOut: timedOut, + stdout: stdout.dataValue, + stderr: stderr.dataValue, + stdoutTruncated: stdout.truncated, + stderrTruncated: stderr.truncated + ) + } +} + +func configure(process: Process, executable: String, arguments: [String]) { + if executable.contains("/") { + process.executableURL = URL(fileURLWithPath: executable) + process.arguments = arguments + } else { + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = [executable] + arguments + } +} + +final class OutputCollector: @unchecked Sendable { + let pipe = Pipe() + private let limit: Int + private let lock = NSLock() + private let endOfFile = DispatchSemaphore(value: 0) + private var data = Data() + private(set) var truncated = false + + init(limit: Int) { + self.limit = limit + pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in + let chunk = handle.availableData + guard !chunk.isEmpty else { + self?.endOfFile.signal() + return } - - let processHasExited = !process.isRunning - stdout.stop(processHasExited: processHasExited) - stderr.stop(processHasExited: processHasExited) - - return CommandDataResult( - executable: executable, - arguments: arguments, - exitCode: process.isRunning ? nil : process.terminationStatus, - timedOut: timedOut, - stdout: stdout.dataValue, - stderr: stderr.dataValue, - stdoutTruncated: stdout.truncated, - stderrTruncated: stderr.truncated - ) + self?.append(chunk) } } - func configure(process: Process, executable: String, arguments: [String]) { - if executable.contains("/") { - process.executableURL = URL(fileURLWithPath: executable) - process.arguments = arguments - } else { - process.executableURL = URL(fileURLWithPath: "/usr/bin/env") - process.arguments = [executable] + arguments - } + var stringValue: String { + lock.lock() + defer { lock.unlock() } + return String(decoding: data, as: UTF8.self) } - final class OutputCollector: @unchecked Sendable { - let pipe = Pipe() - private let limit: Int - private let lock = NSLock() - private let endOfFile = DispatchSemaphore(value: 0) - private var data = Data() - private(set) var truncated = false - - init(limit: Int) { - self.limit = limit - pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in - let chunk = handle.availableData - guard !chunk.isEmpty else { - self?.endOfFile.signal() - return - } - self?.append(chunk) - } - } - - var stringValue: String { - lock.lock() - defer { lock.unlock() } - return String(decoding: data, as: UTF8.self) - } + var dataValue: Data { + lock.lock() + defer { lock.unlock() } + return data + } - var dataValue: Data { - lock.lock() - defer { lock.unlock() } - return data + func stop(processHasExited: Bool) { + if processHasExited { + // Process termination can race the final readability callback. Waiting for + // EOF ensures every earlier callback has appended its bytes before the + // result snapshot is created. + _ = endOfFile.wait(timeout: .now() + .seconds(2)) } - - func stop(processHasExited: Bool) { - if processHasExited { - // Process termination can race the final readability callback. Waiting for - // EOF ensures every earlier callback has appended its bytes before the - // result snapshot is created. - _ = endOfFile.wait(timeout: .now() + .seconds(2)) - } - pipe.fileHandleForReading.readabilityHandler = nil - if processHasExited { - append(pipe.fileHandleForReading.readDataToEndOfFile()) - } + pipe.fileHandleForReading.readabilityHandler = nil + if processHasExited { + append(pipe.fileHandleForReading.readDataToEndOfFile()) } + } - private func append(_ chunk: Data) { - lock.lock() - defer { lock.unlock() } + private func append(_ chunk: Data) { + lock.lock() + defer { lock.unlock() } - guard !chunk.isEmpty else { - return - } + guard !chunk.isEmpty else { + return + } - let remaining = limit - data.count - if remaining <= 0 { - truncated = true - return - } + let remaining = limit - data.count + if remaining <= 0 { + truncated = true + return + } - if chunk.count > remaining { - data.append(chunk.prefix(remaining)) - truncated = true - } else { - data.append(chunk) - } + if chunk.count > remaining { + data.append(chunk.prefix(remaining)) + truncated = true + } else { + data.append(chunk) } } -#endif +} diff --git a/Sources/CodexAdapter/CommandRunner+Windows.swift b/Sources/CodexAdapter/CommandRunner+Windows.swift deleted file mode 100644 index 5671bef..0000000 --- a/Sources/CodexAdapter/CommandRunner+Windows.swift +++ /dev/null @@ -1,129 +0,0 @@ -#if os(Windows) - import Dispatch - import Foundation - import Synchronization - import WinSDK - - final class ProcessCommandRunner: CommandRunning, Sendable { - private let baseEnvironment: [String: String] - - init(environment: [String: String] = ProcessInfo.processInfo.environment) { - baseEnvironment = environment - } - - /// Synchronous compatibility entry point. Call from a blocking executor, not the main actor. - func runData( - executable: String, arguments: [String], workingDirectory: URL?, - environment: [String: String], timeoutMilliseconds: Int, maxOutputBytes: Int - ) throws -> CommandDataResult { - let result = Mutex?>(nil) - let completed = DispatchSemaphore(value: 0) - // The synchronous protocol has no task cancellation channel. Its deadline owns termination. - Task.detached { - let value: Result - do { - value = .success( - try await self.runDataAsync( - executable: executable, arguments: arguments, workingDirectory: workingDirectory, - environment: environment, timeoutMilliseconds: timeoutMilliseconds, - maxOutputBytes: maxOutputBytes)) - } catch { value = .failure(error) } - result.withLock { $0 = value } - completed.signal() - } - completed.wait() - return try result.withLock { try $0!.get() } - } - - func run( - executable: String, arguments: [String], workingDirectory: URL?, - environment: [String: String], timeoutMilliseconds: Int, maxOutputBytes: Int - ) throws -> CommandResult { - let value = try runData( - executable: executable, arguments: arguments, workingDirectory: workingDirectory, - environment: environment, timeoutMilliseconds: timeoutMilliseconds, - maxOutputBytes: maxOutputBytes) - return CommandResult( - executable: value.executable, arguments: value.arguments, exitCode: value.exitCode, - timedOut: value.timedOut, stdout: value.stdoutString, stderr: value.stderrString, - stdoutTruncated: value.stdoutTruncated, stderrTruncated: value.stderrTruncated) - } - - func runDataAsync( - executable: String, arguments: [String], workingDirectory: URL?, - environment: [String: String], timeoutMilliseconds: Int, maxOutputBytes: Int - ) async throws -> CommandDataResult { - try Task.checkCancellation() - guard (1...3_600_000).contains(timeoutMilliseconds), - (1...32_000_000).contains(maxOutputBytes), - arguments.allSatisfy({ !$0.contains("\0") }) - else { - throw CommandRunnerError.launchFailed("Invalid command arguments or execution limits.") - } - let cwd = workingDirectory ?? URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - guard let nativeCwd = WindowsFilePath.native(cwd), WindowsFilePath.isValid(nativeCwd), - WindowsFilePath.isAbsolute(nativeCwd) - else { throw CommandRunnerError.launchFailed("Invalid command working directory.") } - let childEnvironment = try WindowsProcessEnvironment.merging( - baseEnvironment, overrides: environment) - let resolved = try WindowsExecutable.resolve( - executable, workspace: cwd, environment: childEnvironment) - guard let path = WindowsFilePath.native(resolved) else { - throw CommandRunnerError.launchFailed("Invalid command executable path.") - } - let job = try WindowsProcessJob() - // Native IO must finish before its buffers and handles are released. - // Cancellation stops the job, while this owner drains and joins without task cancellation. - let owner = Task.detached { - try await self.execute( - path: path, arguments: arguments, cwd: nativeCwd, environment: childEnvironment, - timeoutMilliseconds: timeoutMilliseconds, limit: maxOutputBytes, job: job) - } - return try await withTaskCancellationHandler { - let value = try await owner.value - try Task.checkCancellation() - return CommandDataResult( - executable: executable, arguments: arguments, exitCode: value.exitCode, - timedOut: job.timedOut, stdout: value.stdout.data, stderr: value.stderr.data, - stdoutTruncated: value.stdout.truncated, stderrTruncated: value.stderr.truncated) - } onCancel: { - job.stop(.cancelled) - } - } - - private struct Outcome: Sendable { - let exitCode: Int32 - let stdout: WindowsCommandProcess.Capture - let stderr: WindowsCommandProcess.Capture - } - - private func execute( - path: String, arguments: [String], cwd: String, environment: [String: String], - timeoutMilliseconds: Int, limit: Int, job: WindowsProcessJob - ) async throws -> Outcome { - try await withThrowingTaskGroup(of: Void.self) { timers in - timers.addTask { - do { try await Task.sleep(for: .milliseconds(timeoutMilliseconds)) } catch { return } - job.stop(.timedOut) - } - defer { timers.cancelAll() } - do { - let process = try WindowsCommandProcess( - path: path, arguments: arguments, cwd: cwd, environment: environment, job: job) - async let stdout = process.stdout.capture(limit: limit, job: job) - async let stderr = process.stderr.capture(limit: limit, job: job) - async let root = process.wait(job: job) - let values = await (stdout, stderr, root) - try await job.confirmCleanup() - return try Outcome( - exitCode: values.2.get(), stdout: values.0.get(), stderr: values.1.get()) - } catch { - job.stop(.failed) - try await job.confirmCleanup() - throw error - } - } - } - - } -#endif diff --git a/Sources/CodexAdapter/MCPInheritedPipeTransport.swift b/Sources/CodexAdapter/MCPInheritedPipeTransport.swift deleted file mode 100644 index 6748889..0000000 --- a/Sources/CodexAdapter/MCPInheritedPipeTransport.swift +++ /dev/null @@ -1,191 +0,0 @@ -#if os(Windows) - import Foundation - import Logging - import MCP - @preconcurrency import SystemPackage - import WinSDK - import ucrt - - /// Read and write endpoints belong to this child, independently of its standard streams. - struct MCPInheritedPipeEndpoint: Sendable { - static let readEnvironmentKey = "COMPUTER_MCP_HOST_READ_HANDLE" - static let writeEnvironmentKey = "COMPUTER_MCP_HOST_WRITE_HANDLE" - let input: MCPInheritedPipeHandle - let output: MCPInheritedPipeHandle - - static func inherited(environment: [String: String]) throws -> Self? { - func value(_ name: String) throws -> String? { - let matches = environment.filter { $0.key.caseInsensitiveCompare(name) == .orderedSame } - guard matches.count <= 1 else { - throw MCPError.invalidParams("Ambiguous inherited host environment.") - } - return matches.first?.value - } - let read = try value(readEnvironmentKey) - let write = try value(writeEnvironmentKey) - let legacy = try value("COMPUTER_MCP_HOST_FD") - guard read != nil || write != nil || legacy != nil else { return nil } - guard legacy == nil, try value("COMPUTER_MCP_HOST_CONTEXT") != nil, - let read, let write, read != write - else { throw MCPError.invalidParams("Incomplete inherited host pipe endpoints.") } - let input = try inheritedHandle(read) - let output = try inheritedHandle(write) - return Self( - input: MCPInheritedPipeHandle(takingOwnershipOf: input), - output: MCPInheritedPipeHandle(takingOwnershipOf: output)) - } - - private static func inheritedHandle(_ text: String) throws -> HANDLE { - guard let value = UInt(text), String(value) == text, value > 0, value < UInt.max, - let handle = HANDLE(bitPattern: value) - else { throw MCPError.invalidParams("Invalid inherited host pipe handle.") } - var flags: DWORD = 0 - var mode: DWORD = 0 - guard handle != GetStdHandle(STD_INPUT_HANDLE), handle != GetStdHandle(STD_OUTPUT_HANDLE), - handle != GetStdHandle(STD_ERROR_HANDLE), GetHandleInformation(handle, &flags), - flags & DWORD(HANDLE_FLAG_INHERIT) != 0, GetFileType(handle) == DWORD(FILE_TYPE_PIPE), - GetNamedPipeInfo(handle, &mode, nil, nil, nil), mode & DWORD(PIPE_TYPE_MESSAGE) == 0 - else { throw MCPError.invalidParams("Host transport requires inherited byte pipes.") } - return handle - } - } - - /// Owns inherited endpoints until the MCP transport has duplicated them for native I/O. - /// No endpoint name, listener or credential is accepted from tool arguments. - actor MCPInheritedPipeTransport: Transport { - nonisolated let logger = Logger( - label: "mcp.inherited-pipe", factory: { _ in SwiftLogNoOpLogHandler() }) - private let descriptors: InheritedPipeDescriptors - private let base: StdioTransport - private var connection: Task, Error>? - private var stream = AsyncThrowingStream { - $0.finish(throwing: MCPError.connectionClosed) - } - private var closing: Task? - - init( - takingOwnershipOf endpoint: MCPInheritedPipeEndpoint, - maximumMessageBytes: Int = 1_048_576, maximumQueuedMessages: Int = 32, - writeTimeout: Duration = .seconds(30) - ) throws { - let descriptors = try InheritedPipeDescriptors(endpoint) - self.descriptors = descriptors - base = StdioTransport( - input: descriptors.input, output: descriptors.output, - maximumMessageBytes: maximumMessageBytes, maximumQueuedMessages: maximumQueuedMessages, - maximumQueuedBytes: 16_777_216, writeTimeout: writeTimeout) - } - - func connect() async throws { - guard closing == nil else { throw MCPError.connectionClosed } - if let connection { - stream = try await connection.value - try await base.connect() - return - } - let task = Task { [base, descriptors] in - defer { descriptors.close() } - try await base.connect() - return await base.receive() - } - connection = task - stream = try await task.value - guard closing == nil else { throw MCPError.connectionClosed } - } - - func send(_ data: Data) async throws { try await base.send(data) } - func receive() -> AsyncThrowingStream { stream } - - func disconnect() async { - if let closing { - await closing.value - return - } - let task = Task { [base, descriptors, connection] in - await base.disconnect() - _ = await connection?.result - descriptors.close() - } - closing = task - await task.value - } - } - - /// The lock serializes native handle borrowing with its once-only close. - final class MCPInheritedPipeHandle: @unchecked Sendable { - private let lock = NSLock() - private var handle: HANDLE? - init(takingOwnershipOf handle: HANDLE) { self.handle = handle } - func withHandle(_ body: (HANDLE) throws -> T) throws -> T { - try lock.withLock { - guard let handle else { throw MCPError.connectionClosed } - return try body(handle) - } - } - func close() { - lock.withLock { - guard let handle else { return } - self.handle = nil - CloseHandle(handle) - } - } - deinit { close() } - } - - private final class InheritedPipeDescriptors: @unchecked Sendable { - let input: FileDescriptor - let output: FileDescriptor - private let endpoint: MCPInheritedPipeEndpoint - private let lock = NSLock() - private var closed = false - - init(_ endpoint: MCPInheritedPipeEndpoint) throws { - self.endpoint = endpoint - input = try Self.duplicate(endpoint.input, reading: true) - do { - output = try Self.duplicate(endpoint.output, reading: false) - } catch { - _ = ucrt._close(input.rawValue) - throw error - } - } - - private static func duplicate(_ file: MCPInheritedPipeHandle, reading: Bool) throws - -> FileDescriptor - { - try file.withHandle { handle in - var mode: DWORD = 0 - guard GetFileType(handle) == DWORD(FILE_TYPE_PIPE), - GetNamedPipeInfo(handle, &mode, nil, nil, nil), mode & DWORD(PIPE_TYPE_MESSAGE) == 0, - SetHandleInformation(handle, DWORD(HANDLE_FLAG_INHERIT), 0) - else { throw MCPError.invalidParams("Host transport requires owned byte pipes.") } - var duplicate: HANDLE? - guard - DuplicateHandle( - GetCurrentProcess(), handle, GetCurrentProcess(), &duplicate, 0, false, - DWORD(DUPLICATE_SAME_ACCESS)), let duplicate - else { throw MCPError.connectionClosed } - let descriptor = ucrt._open_osfhandle( - Int(bitPattern: duplicate), (reading ? _O_RDONLY : _O_WRONLY) | _O_BINARY | _O_NOINHERIT) - guard descriptor >= 0 else { - CloseHandle(duplicate) - throw MCPError.connectionClosed - } - return FileDescriptor(rawValue: descriptor) - } - } - - func close() { - lock.withLock { - guard !closed else { return } - closed = true - _ = ucrt._close(input.rawValue) - _ = ucrt._close(output.rawValue) - endpoint.input.close() - endpoint.output.close() - } - } - - deinit { close() } - } -#endif diff --git a/Sources/CodexAdapter/MCPInheritedSocketTransport.swift b/Sources/CodexAdapter/MCPInheritedSocketTransport.swift index 728d42a..62f5eca 100644 --- a/Sources/CodexAdapter/MCPInheritedSocketTransport.swift +++ b/Sources/CodexAdapter/MCPInheritedSocketTransport.swift @@ -1,221 +1,218 @@ -#if !os(Windows) - import Darwin - import Foundation - import Logging - import MCP +import Darwin +import Foundation +import Logging +import MCP - /// A private inherited socket carries standard newline-delimited MCP messages. - /// It owns its descriptor; it never opens a pathname, listener or privileged socket. - actor MCPInheritedSocketTransport: Transport { - nonisolated let logger = Logger( - label: "mcp.inherited-socket", factory: { _ in SwiftLogNoOpLogHandler() }) - private let handle: FileHandle - private let maximumMessageBytes: Int - private let maximumQueuedMessages: Int - private let writeTimeout: Duration - private let stream: AsyncThrowingStream - private let continuation: AsyncThrowingStream.Continuation - private var connected = false - private var closed = false - private var pending = Data() - private var reader: Task? - private var writer: Task? - private var queuedWrites = 0 +/// A private inherited socket carries standard newline-delimited MCP messages. +/// It owns its descriptor; it never opens a pathname, listener or privileged socket. +actor MCPInheritedSocketTransport: Transport { + nonisolated let logger = Logger( + label: "mcp.inherited-socket", factory: { _ in SwiftLogNoOpLogHandler() }) + private let handle: FileHandle + private let maximumMessageBytes: Int + private let maximumQueuedMessages: Int + private let writeTimeout: Duration + private let stream: AsyncThrowingStream + private let continuation: AsyncThrowingStream.Continuation + private var connected = false + private var closed = false + private var pending = Data() + private var reader: Task? + private var writer: Task? + private var queuedWrites = 0 - init( - takingOwnershipOf handle: FileHandle, maximumMessageBytes: Int = 1_048_576, - maximumQueuedMessages: Int = 32, writeTimeout: Duration = .seconds(30) - ) throws { - guard (1...16_777_216).contains(maximumMessageBytes), - (1...256).contains(maximumQueuedMessages), writeTimeout > .zero - else { throw MCPError.invalidParams("Invalid inherited transport bounds.") } - let descriptor = handle.fileDescriptor - var info = stat() - var type: Int32 = 0 - var length = socklen_t(MemoryLayout.size) - var address = sockaddr_storage() - var addressLength = socklen_t(MemoryLayout.size) - let peerResult = withUnsafeMutablePointer(to: &address) { - $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { - getpeername(descriptor, $0, &addressLength) - } - } - guard descriptor >= 3, fstat(descriptor, &info) == 0, - info.st_mode & S_IFMT == S_IFSOCK, - getsockopt(descriptor, SOL_SOCKET, SO_TYPE, &type, &length) == 0, - type == SOCK_STREAM, peerResult == 0, Int32(address.ss_family) == AF_UNIX - else { - throw MCPError.invalidParams("Host transport requires a connected Unix stream socket.") + init( + takingOwnershipOf handle: FileHandle, maximumMessageBytes: Int = 1_048_576, + maximumQueuedMessages: Int = 32, writeTimeout: Duration = .seconds(30) + ) throws { + guard (1...16_777_216).contains(maximumMessageBytes), + (1...256).contains(maximumQueuedMessages), writeTimeout > .zero + else { throw MCPError.invalidParams("Invalid inherited transport bounds.") } + let descriptor = handle.fileDescriptor + var info = stat() + var type: Int32 = 0 + var length = socklen_t(MemoryLayout.size) + var address = sockaddr_storage() + var addressLength = socklen_t(MemoryLayout.size) + let peerResult = withUnsafeMutablePointer(to: &address) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + getpeername(descriptor, $0, &addressLength) } - let flags = fcntl(descriptor, F_GETFL) - let fdFlags = fcntl(descriptor, F_GETFD) - var noSigpipe: Int32 = 1 - guard flags >= 0, fdFlags >= 0, - fcntl(descriptor, F_SETFL, flags | O_NONBLOCK) == 0, - fcntl(descriptor, F_SETFD, fdFlags | FD_CLOEXEC) == 0, - setsockopt(descriptor, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, length) == 0 - else { throw MCPError.transportError(POSIXError(.EIO)) } - self.handle = handle - self.maximumMessageBytes = maximumMessageBytes - self.maximumQueuedMessages = maximumQueuedMessages - self.writeTimeout = writeTimeout - (stream, continuation) = AsyncThrowingStream.makeStream( - bufferingPolicy: .bufferingOldest(maximumQueuedMessages)) } + guard descriptor >= 3, fstat(descriptor, &info) == 0, + info.st_mode & S_IFMT == S_IFSOCK, + getsockopt(descriptor, SOL_SOCKET, SO_TYPE, &type, &length) == 0, + type == SOCK_STREAM, peerResult == 0, Int32(address.ss_family) == AF_UNIX + else { + throw MCPError.invalidParams("Host transport requires a connected Unix stream socket.") + } + let flags = fcntl(descriptor, F_GETFL) + let fdFlags = fcntl(descriptor, F_GETFD) + var noSigpipe: Int32 = 1 + guard flags >= 0, fdFlags >= 0, + fcntl(descriptor, F_SETFL, flags | O_NONBLOCK) == 0, + fcntl(descriptor, F_SETFD, fdFlags | FD_CLOEXEC) == 0, + setsockopt(descriptor, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, length) == 0 + else { throw MCPError.transportError(POSIXError(.EIO)) } + self.handle = handle + self.maximumMessageBytes = maximumMessageBytes + self.maximumQueuedMessages = maximumQueuedMessages + self.writeTimeout = writeTimeout + (stream, continuation) = AsyncThrowingStream.makeStream( + bufferingPolicy: .bufferingOldest(maximumQueuedMessages)) + } - static func makePair() throws -> (FileHandle, FileHandle) { - var descriptors: [Int32] = [-1, -1] - guard socketpair(AF_UNIX, SOCK_STREAM, 0, &descriptors) == 0 else { - throw MCPError.transportError(POSIXError(.EMFILE)) - } - let pair = ( - FileHandle(fileDescriptor: descriptors[0], closeOnDealloc: true), - FileHandle(fileDescriptor: descriptors[1], closeOnDealloc: true) - ) - for descriptor in descriptors { - let flags = fcntl(descriptor, F_GETFD) - guard flags >= 0, fcntl(descriptor, F_SETFD, flags | FD_CLOEXEC) == 0 else { - try? pair.0.close() - try? pair.1.close() - throw MCPError.transportError(POSIXError(.EIO)) - } + static func makePair() throws -> (FileHandle, FileHandle) { + var descriptors: [Int32] = [-1, -1] + guard socketpair(AF_UNIX, SOCK_STREAM, 0, &descriptors) == 0 else { + throw MCPError.transportError(POSIXError(.EMFILE)) + } + let pair = ( + FileHandle(fileDescriptor: descriptors[0], closeOnDealloc: true), + FileHandle(fileDescriptor: descriptors[1], closeOnDealloc: true) + ) + for descriptor in descriptors { + let flags = fcntl(descriptor, F_GETFD) + guard flags >= 0, fcntl(descriptor, F_SETFD, flags | FD_CLOEXEC) == 0 else { + try? pair.0.close() + try? pair.1.close() + throw MCPError.transportError(POSIXError(.EIO)) } - return pair } + return pair + } - func connect() async throws { - guard !closed else { throw MCPError.connectionClosed } - guard !connected else { return } - connected = true - reader = Task { [weak self] in - while !Task.isCancelled { - guard let self, await self.readAvailable() else { break } - do { try await Task.sleep(for: .milliseconds(5)) } catch { break } - } + func connect() async throws { + guard !closed else { throw MCPError.connectionClosed } + guard !connected else { return } + connected = true + reader = Task { [weak self] in + while !Task.isCancelled { + guard let self, await self.readAvailable() else { break } + do { try await Task.sleep(for: .milliseconds(5)) } catch { break } } } + } - func receive() -> AsyncThrowingStream { stream } + func receive() -> AsyncThrowingStream { stream } - func send(_ message: Data) async throws { + func send(_ message: Data) async throws { + try Task.checkCancellation() + guard connected, !closed else { throw MCPError.connectionClosed } + guard !message.isEmpty, message.count <= maximumMessageBytes, + !message.contains(0x0A), String(data: message, encoding: .utf8) != nil + else { throw MCPError.invalidRequest("Invalid inherited MCP frame.") } + guard queuedWrites < maximumQueuedMessages else { + finish(throwing: MCPError.transportError(POSIXError(.ENOBUFS))) + throw MCPError.connectionClosed + } + queuedWrites += 1 + defer { queuedWrites -= 1 } + let predecessor = writer + var line = message + line.append(0x0A) + let payload = line + let deadline = ContinuousClock.now + writeTimeout + let task = Task { [weak self] in + _ = await predecessor?.result try Task.checkCancellation() - guard connected, !closed else { throw MCPError.connectionClosed } - guard !message.isEmpty, message.count <= maximumMessageBytes, - !message.contains(0x0A), String(data: message, encoding: .utf8) != nil - else { throw MCPError.invalidRequest("Invalid inherited MCP frame.") } - guard queuedWrites < maximumQueuedMessages else { - finish(throwing: MCPError.transportError(POSIXError(.ENOBUFS))) - throw MCPError.connectionClosed - } - queuedWrites += 1 - defer { queuedWrites -= 1 } - let predecessor = writer - var line = message - line.append(0x0A) - let payload = line - let deadline = ContinuousClock.now + writeTimeout - let task = Task { [weak self] in - _ = await predecessor?.result - try Task.checkCancellation() - guard let self else { throw MCPError.connectionClosed } - try await self.write(payload, deadline: deadline) - } - writer = task - try await withTaskCancellationHandler { - try await task.value - } onCancel: { - task.cancel() - } + guard let self else { throw MCPError.connectionClosed } + try await self.write(payload, deadline: deadline) } - - func disconnect() async { - finish() - let reading = reader - let writing = writer - reader = nil - writer = nil - reading?.cancel() - writing?.cancel() - await reading?.value - _ = await writing?.result + writer = task + try await withTaskCancellationHandler { + try await task.value + } onCancel: { + task.cancel() } + } - private func write(_ data: Data, deadline: ContinuousClock.Instant) async throws { - var offset = 0 - do { - while offset < data.count { - try Task.checkCancellation() - guard connected, !closed else { throw MCPError.connectionClosed } - guard ContinuousClock.now < deadline else { - throw MCPError.transportError(POSIXError(.ETIMEDOUT)) - } - let count = data.withUnsafeBytes { - Darwin.send( - handle.fileDescriptor, $0.baseAddress!.advanced(by: offset), data.count - offset, 0) - } - if count > 0 { - offset += count - } else if count < 0 && errno == EINTR { - continue - } else if count < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) { - try await Task.sleep(for: .milliseconds(5)) - } else { - throw MCPError.transportError(POSIXError(.EPIPE)) - } + func disconnect() async { + finish() + let reading = reader + let writing = writer + reader = nil + writer = nil + reading?.cancel() + writing?.cancel() + await reading?.value + _ = await writing?.result + } + + private func write(_ data: Data, deadline: ContinuousClock.Instant) async throws { + var offset = 0 + do { + while offset < data.count { + try Task.checkCancellation() + guard connected, !closed else { throw MCPError.connectionClosed } + guard ContinuousClock.now < deadline else { + throw MCPError.transportError(POSIXError(.ETIMEDOUT)) + } + let count = data.withUnsafeBytes { + Darwin.send( + handle.fileDescriptor, $0.baseAddress!.advanced(by: offset), data.count - offset, 0) + } + if count > 0 { + offset += count + } else if count < 0 && errno == EINTR { + continue + } else if count < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) { + try await Task.sleep(for: .milliseconds(5)) + } else { + throw MCPError.transportError(POSIXError(.EPIPE)) } - } catch { - // A partly transmitted request cannot be retried on this byte stream. - finish(throwing: error) - throw error } + } catch { + // A partly transmitted request cannot be retried on this byte stream. + finish(throwing: error) + throw error } + } - private func readAvailable() -> Bool { - guard connected, !closed else { return false } - var buffer = [UInt8](repeating: 0, count: 16_384) - let count = recv(handle.fileDescriptor, &buffer, buffer.count, 0) - if count < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { return true } - guard count > 0 else { - finish() + private func readAvailable() -> Bool { + guard connected, !closed else { return false } + var buffer = [UInt8](repeating: 0, count: 16_384) + let count = recv(handle.fileDescriptor, &buffer, buffer.count, 0) + if count < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { return true } + guard count > 0 else { + finish() + return false + } + pending.append(contentsOf: buffer.prefix(count)) + while let newline = pending.firstIndex(of: 0x0A) { + let message = Data(pending[.. - - private let configuration: Configuration - private let state: ManagedLineProcessState - private let launchTask: Task - private let closeLock = NSLock() - private var closeTask: Task? - - init(configuration: Configuration) throws { - try configuration.validate() - self.configuration = configuration - let streamAndContinuation = AsyncThrowingStream.makeStream( - bufferingPolicy: .bufferingOldest(16)) - self.inboundLines = streamAndContinuation.stream - let state = ManagedLineProcessState( - continuation: streamAndContinuation.continuation, - maximumMessageBytes: configuration.maximumMessageBytes, - ownerProcessID: configuration.ownerProcessID - ) - self.state = state - self.launchTask = Task.detached(priority: .userInitiated) { - await Self.launch(configuration: configuration, state: state) - } +import Darwin +import Foundation +import Subprocess +import System + +/// Owns one supervisor generation. The lock protects closeTask; all process state is actor-owned. +/// Foundation's callback reader is the only other lock boundary; no host process is adopted. +final class ManagedLineProcess: @unchecked Sendable { + + let inboundLines: AsyncThrowingStream + + private let configuration: Configuration + private let state: ManagedLineProcessState + private let launchTask: Task + private let closeLock = NSLock() + private var closeTask: Task? + + init(configuration: Configuration) throws { + try configuration.validate() + self.configuration = configuration + let streamAndContinuation = AsyncThrowingStream.makeStream( + bufferingPolicy: .bufferingOldest(16)) + self.inboundLines = streamAndContinuation.stream + let state = ManagedLineProcessState( + continuation: streamAndContinuation.continuation, + maximumMessageBytes: configuration.maximumMessageBytes, + ownerProcessID: configuration.ownerProcessID + ) + self.state = state + self.launchTask = Task.detached(priority: .userInitiated) { + await Self.launch(configuration: configuration, state: state) } + } - func sendLine(_ line: String) async throws { - try await state.send(line: line) - } + func sendLine(_ line: String) async throws { + try await state.send(line: line) + } - func close() async { - let task = closeLock.withLock { () -> Task in - if let closeTask { - return closeTask - } - let task = Task { [weak self] in - if let self { - await self.performClose() - } - } - closeTask = task - return task + func close() async { + let task = closeLock.withLock { () -> Task in + if let closeTask { + return closeTask } - await task.value - } - - private func performClose() async { - await state.beginShutdown() - - // The child has a separate job-control group. Do not kill its supervisor before - // it publishes that group's identity, or a concurrently spawned child could escape. - // Readiness also ensures EOF delivery sees the input writer attached during launch. - let startupDeadline = ContinuousClock.now + .seconds(5) - while !(await state.canSignalOwnedProcess()) { - if ContinuousClock.now >= startupDeadline { - await state.failTerminationTimeout() - return + let task = Task { [weak self] in + if let self { + await self.performClose() } - try? await Task.sleep(for: .milliseconds(10)) } + closeTask = task + return task + } + await task.value + } - let finishInput = Task { - try? await state.finishInput() - } + private func performClose() async { + await state.beginShutdown() - // Start EOF delivery without waiting for a back-pressured pipe writer. The - // bounded process wait and signal escalation below remain authoritative. - if await waitForExit(milliseconds: configuration.terminationGraceMilliseconds) { - await launchTask.value - await finishInput.value + // The child has a separate job-control group. Do not kill its supervisor before + // it publishes that group's identity, or a concurrently spawned child could escape. + // Readiness also ensures EOF delivery sees the input writer attached during launch. + let startupDeadline = ContinuousClock.now + .seconds(5) + while !(await state.canSignalOwnedProcess()) { + if ContinuousClock.now >= startupDeadline { + await state.failTerminationTimeout() return } + try? await Task.sleep(for: .milliseconds(10)) + } - if !(await state.signalChildGroup(SIGTERM)), let execution = await state.runningExecution() { - try? execution.send(signal: .terminate, toProcessGroup: true) - } - if await waitForExit(milliseconds: configuration.terminationGraceMilliseconds) { - await launchTask.value - await finishInput.value - return - } + let finishInput = Task { + try? await state.finishInput() + } - if let execution = await state.runningExecution() { - await state.recordTerminationEscalation() - if !(await state.signalChildGroup(SIGKILL)) { - try? execution.send(signal: .kill, toProcessGroup: true) - } - } + // Start EOF delivery without waiting for a back-pressured pipe writer. The + // bounded process wait and signal escalation below remain authoritative. + if await waitForExit(milliseconds: configuration.terminationGraceMilliseconds) { + await launchTask.value + await finishInput.value + return + } - if !(await waitForExit(milliseconds: configuration.killGraceMilliseconds)) { - await state.failTerminationTimeout() - return - } + if !(await state.signalChildGroup(SIGTERM)), let execution = await state.runningExecution() { + try? execution.send(signal: .terminate, toProcessGroup: true) + } + if await waitForExit(milliseconds: configuration.terminationGraceMilliseconds) { await launchTask.value await finishInput.value + return } - func snapshot() async -> ManagedLineProcessSnapshot { - await state.snapshot() + if let execution = await state.runningExecution() { + await state.recordTerminationEscalation() + if !(await state.signalChildGroup(SIGKILL)) { + try? execution.send(signal: .kill, toProcessGroup: true) + } } - private func waitForExit(milliseconds: Int) async -> Bool { - let deadline = ContinuousClock.now + .milliseconds(max(0, milliseconds)) - repeat { - if await state.cleanupIsConfirmed() { - return true - } - if ContinuousClock.now >= deadline { - return false - } - try? await Task.sleep(for: .milliseconds(10)) - } while true + if !(await waitForExit(milliseconds: configuration.killGraceMilliseconds)) { + await state.failTerminationTimeout() + return } + await launchTask.value + await finishInput.value + } - private static func launch( - configuration: Configuration, - state: ManagedLineProcessState - ) async { - var supervisorDirectory: URL? - do { - if await state.isShuttingDown() { - await state.finish(status: .exited(0)) - return + func snapshot() async -> ManagedLineProcessSnapshot { + await state.snapshot() + } + + private func waitForExit(milliseconds: Int) async -> Bool { + let deadline = ContinuousClock.now + .milliseconds(max(0, milliseconds)) + repeat { + if await state.cleanupIsConfirmed() { + return true + } + if ContinuousClock.now >= deadline { + return false + } + try? await Task.sleep(for: .milliseconds(10)) + } while true + } + + private static func launch( + configuration: Configuration, + state: ManagedLineProcessState + ) async { + var supervisorDirectory: URL? + do { + if await state.isShuttingDown() { + await state.finish(status: .exited(0)) + return + } + let environment = Dictionary( + uniqueKeysWithValues: configuration.environment.compactMap { key, value in + Subprocess.Environment.Key(rawValue: key).map { ($0, value) } } - let environment = Dictionary( - uniqueKeysWithValues: configuration.environment.compactMap { key, value in - Subprocess.Environment.Key(rawValue: key).map { ($0, value) } - } - ) - let supervisor = try makeSupervisor(configuration: configuration) - supervisorDirectory = supervisor.directory - let executable: Executable = .path(FilePath("/bin/sh")) - var platformOptions = PlatformOptions() - platformOptions.processGroupID = 0 - let standardOutput = try FileDescriptor.pipe() - let outputReader = try ManagedLineProcessOutputReader( - readEnd: standardOutput.readEnd, - state: state - ) - outputReader.start() - do { - let outcome = try await Subprocess.run( - executable, - arguments: Arguments(supervisor.arguments), - environment: .custom(environment), - workingDirectory: FilePath(configuration.workingDirectory.path), - platformOptions: platformOptions, - output: FileDescriptorOutput.fileDescriptor( - standardOutput.writeEnd, - closeAfterSpawningProcess: true - ) - ) { execution, inputWriter, stderr in - await state.attach( - execution: execution, - inputWriter: inputWriter + ) + let supervisor = try makeSupervisor(configuration: configuration) + supervisorDirectory = supervisor.directory + let executable: Executable = .path(FilePath("/bin/sh")) + var platformOptions = PlatformOptions() + platformOptions.processGroupID = 0 + let standardOutput = try FileDescriptor.pipe() + let outputReader = try ManagedLineProcessOutputReader( + readEnd: standardOutput.readEnd, + state: state + ) + outputReader.start() + do { + let outcome = try await Subprocess.run( + executable, + arguments: Arguments(supervisor.arguments), + environment: .custom(environment), + workingDirectory: FilePath(configuration.workingDirectory.path), + platformOptions: platformOptions, + output: FileDescriptorOutput.fileDescriptor( + standardOutput.writeEnd, + closeAfterSpawningProcess: true + ) + ) { execution, inputWriter, stderr in + await state.attach( + execution: execution, + inputWriter: inputWriter + ) + guard let processID = await waitForProcessID(at: supervisor.processIDFile) + else { + try? execution.send(signal: .kill, toProcessGroup: true) + throw ManagedLineProcessError.launchFailed( + "The process supervisor did not report the child PID." ) - guard let processID = await waitForProcessID(at: supervisor.processIDFile) - else { - try? execution.send(signal: .kill, toProcessGroup: true) - throw ManagedLineProcessError.launchFailed( - "The process supervisor did not report the child PID." - ) - } - await state.attachChild(processID: processID) - do { - for try await _ in stderr {} - } catch { - await state.recordStreamError(error) - } } - await outputReader.stop(drainRemainingOutput: true) - await state.finish(status: outcome.terminationStatus) - } catch { - await outputReader.stop(drainRemainingOutput: false) - throw error + await state.attachChild(processID: processID) + do { + for try await _ in stderr {} + } catch { + await state.recordStreamError(error) + } } + await outputReader.stop(drainRemainingOutput: true) + await state.finish(status: outcome.terminationStatus) } catch { - await state.failLaunch(error) - } - if let supervisorDirectory { - try? FileManager.default.removeItem(at: supervisorDirectory) + await outputReader.stop(drainRemainingOutput: false) + throw error } + } catch { + await state.failLaunch(error) } - - private struct SupervisorLaunch { - let directory: URL - let processIDFile: URL - let arguments: [String] + if let supervisorDirectory { + try? FileManager.default.removeItem(at: supervisorDirectory) } + } - private static func makeSupervisor( - configuration: Configuration - ) throws -> SupervisorLaunch { - let directory = FileManager.default.temporaryDirectory - .appendingPathComponent("computer-mcp-process-\(UUID().uuidString)", isDirectory: true) - try FileManager.default.createDirectory( - at: directory, - withIntermediateDirectories: true, - attributes: [.posixPermissions: NSNumber(value: Int16(0o700))] - ) - let script = directory.appendingPathComponent("supervisor.sh") - let processIDFile = directory.appendingPathComponent("child.pid") - try Data( - """ - #!/bin/sh - set -m - pid_file=$1 - owner_pid=$2 - grace_seconds=$3 - shift 3 - - child= - watchdog= - timer= - cleanup_timer() { - if [ -n "$timer" ]; then - kill -KILL "$timer" 2>/dev/null || true - wait "$timer" 2>/dev/null || true - timer= - fi - } - terminate_group() { - kill -TERM -- -"$child" 2>/dev/null || true - # One owned timer measures elapsed grace independently of polling cost. - /bin/sleep "$grace_seconds" & - timer=$! - while kill -0 -- -"$child" 2>/dev/null; do - if ! kill -0 "$timer" 2>/dev/null; then - kill -KILL -- -"$child" 2>/dev/null || true - break - fi - /bin/sleep 0.01 - done - cleanup_timer - } - cleanup() { - trap - EXIT HUP INT TERM - if [ -n "$child" ]; then - if kill -0 -- -"$child" 2>/dev/null; then - terminate_group - fi - wait "$child" 2>/dev/null || true + private struct SupervisorLaunch { + let directory: URL + let processIDFile: URL + let arguments: [String] + } + + private static func makeSupervisor( + configuration: Configuration + ) throws -> SupervisorLaunch { + let directory = FileManager.default.temporaryDirectory + .appendingPathComponent("computer-mcp-process-\(UUID().uuidString)", isDirectory: true) + try FileManager.default.createDirectory( + at: directory, + withIntermediateDirectories: true, + attributes: [.posixPermissions: NSNumber(value: Int16(0o700))] + ) + let script = directory.appendingPathComponent("supervisor.sh") + let processIDFile = directory.appendingPathComponent("child.pid") + try Data( + """ + #!/bin/sh + set -m + pid_file=$1 + owner_pid=$2 + grace_seconds=$3 + shift 3 + + child= + watchdog= + timer= + cleanup_timer() { + if [ -n "$timer" ]; then + kill -KILL "$timer" 2>/dev/null || true + wait "$timer" 2>/dev/null || true + timer= + fi + } + terminate_group() { + kill -TERM -- -"$child" 2>/dev/null || true + # One owned timer measures elapsed grace independently of polling cost. + /bin/sleep "$grace_seconds" & + timer=$! + while kill -0 -- -"$child" 2>/dev/null; do + if ! kill -0 "$timer" 2>/dev/null; then + kill -KILL -- -"$child" 2>/dev/null || true + break fi - if [ -n "$watchdog" ]; then - kill -CONT "$watchdog" 2>/dev/null || true - kill -TERM "$watchdog" 2>/dev/null || true - wait "$watchdog" 2>/dev/null || true + /bin/sleep 0.01 + done + cleanup_timer + } + cleanup() { + trap - EXIT HUP INT TERM + if [ -n "$child" ]; then + if kill -0 -- -"$child" 2>/dev/null; then + terminate_group fi - } - trap cleanup EXIT HUP INT TERM - - "$@" <&0 >&1 2>&2 & - child=$! - exec 0<&- - if ! printf '%s\n' "$child" > "$pid_file"; then exit 1; fi - ( - trap '' HUP INT - trap 'cleanup_timer; exit 0' TERM - while kill -0 "$owner_pid" 2>/dev/null; do - if ! kill -0 -- -"$child" 2>/dev/null; then exit 0; fi - /bin/sleep 0.1 - done - terminate_group - ) /dev/null 2>&1 & - watchdog=$! - - wait "$child" - status=$? - cleanup - exit "$status" - """.utf8 - ).write(to: script, options: .atomic) - try FileManager.default.setAttributes( - [.posixPermissions: NSNumber(value: Int16(0o700))], - ofItemAtPath: script.path - ) - let graceSeconds = Double(configuration.terminationGraceMilliseconds) / 1_000 - return SupervisorLaunch( - directory: directory, - processIDFile: processIDFile, - arguments: [ - script.path, - processIDFile.path, - String(configuration.ownerProcessID), - String(graceSeconds), - configuration.executable, - ] + configuration.arguments - ) - } + wait "$child" 2>/dev/null || true + fi + if [ -n "$watchdog" ]; then + kill -CONT "$watchdog" 2>/dev/null || true + kill -TERM "$watchdog" 2>/dev/null || true + wait "$watchdog" 2>/dev/null || true + fi + } + trap cleanup EXIT HUP INT TERM + + "$@" <&0 >&1 2>&2 & + child=$! + exec 0<&- + if ! printf '%s\n' "$child" > "$pid_file"; then exit 1; fi + ( + trap '' HUP INT + trap 'cleanup_timer; exit 0' TERM + while kill -0 "$owner_pid" 2>/dev/null; do + if ! kill -0 -- -"$child" 2>/dev/null; then exit 0; fi + /bin/sleep 0.1 + done + terminate_group + ) /dev/null 2>&1 & + watchdog=$! + + wait "$child" + status=$? + cleanup + exit "$status" + """.utf8 + ).write(to: script, options: .atomic) + try FileManager.default.setAttributes( + [.posixPermissions: NSNumber(value: Int16(0o700))], + ofItemAtPath: script.path + ) + let graceSeconds = Double(configuration.terminationGraceMilliseconds) / 1_000 + return SupervisorLaunch( + directory: directory, + processIDFile: processIDFile, + arguments: [ + script.path, + processIDFile.path, + String(configuration.ownerProcessID), + String(graceSeconds), + configuration.executable, + ] + configuration.arguments + ) + } - private static func waitForProcessID(at file: URL) async -> Int32? { - for _ in 0..<500 { - if Task.isCancelled { return nil } - if let text = try? String(contentsOf: file, encoding: .utf8), - let processID = Int32(text.trimmingCharacters(in: .whitespacesAndNewlines)) - { - return processID - } - do { try await Task.sleep(for: .milliseconds(10)) } catch { return nil } - } - return nil - } - - } - - /// File descriptor access and callback/task ownership are serialized by lock. Nonblocking reads - /// let shutdown drain a finite pipe without waiting on a descendant that retained stdout. - private final class ManagedLineProcessOutputReader: @unchecked Sendable { - private let handle: FileHandle - private let state: ManagedLineProcessState - private let lock = NSLock() - private var stopped = false - private var consumptionTask: Task? - - init( - readEnd: FileDescriptor, - state: ManagedLineProcessState - ) throws { - self.handle = FileHandle(fileDescriptor: readEnd.rawValue, closeOnDealloc: true) - self.state = state - let flags = fcntl(readEnd.rawValue, F_GETFL) - guard flags >= 0, fcntl(readEnd.rawValue, F_SETFL, flags | O_NONBLOCK) >= 0 else { - throw ManagedLineProcessError.launchFailed( - "Cannot configure the managed process output pipe.") + private static func waitForProcessID(at file: URL) async -> Int32? { + for _ in 0..<500 { + if Task.isCancelled { return nil } + if let text = try? String(contentsOf: file, encoding: .utf8), + let processID = Int32(text.trimmingCharacters(in: .whitespacesAndNewlines)) + { + return processID } + do { try await Task.sleep(for: .milliseconds(10)) } catch { return nil } } + return nil + } - func start() { - arm() +} + +/// File descriptor access and callback/task ownership are serialized by lock. Nonblocking reads +/// let shutdown drain a finite pipe without waiting on a descendant that retained stdout. +private final class ManagedLineProcessOutputReader: @unchecked Sendable { + private let handle: FileHandle + private let state: ManagedLineProcessState + private let lock = NSLock() + private var stopped = false + private var consumptionTask: Task? + + init( + readEnd: FileDescriptor, + state: ManagedLineProcessState + ) throws { + self.handle = FileHandle(fileDescriptor: readEnd.rawValue, closeOnDealloc: true) + self.state = state + let flags = fcntl(readEnd.rawValue, F_GETFL) + guard flags >= 0, fcntl(readEnd.rawValue, F_SETFL, flags | O_NONBLOCK) >= 0 else { + throw ManagedLineProcessError.launchFailed( + "Cannot configure the managed process output pipe.") } + } - func stop(drainRemainingOutput: Bool) async { - let task = lock.withLock { () -> Task? in - if !stopped { - stopped = true - handle.readabilityHandler = nil - if drainRemainingOutput { - // A pipe holds fewer bytes than this budget. A concurrent writer cannot make drain infinite. - var remaining = 1_048_576 - while remaining > 0, let data = readChunkLocked(), !data.isEmpty { - remaining -= data.count - enqueueLocked(data) - } - if remaining <= 0 { - let previous = consumptionTask - consumptionTask = Task { [state] in - await previous?.value - await state.recordStreamError(ManagedLineProcessError.bufferOverflow) - } + func start() { + arm() + } + + func stop(drainRemainingOutput: Bool) async { + let task = lock.withLock { () -> Task? in + if !stopped { + stopped = true + handle.readabilityHandler = nil + if drainRemainingOutput { + // A pipe holds fewer bytes than this budget. A concurrent writer cannot make drain infinite. + var remaining = 1_048_576 + while remaining > 0, let data = readChunkLocked(), !data.isEmpty { + remaining -= data.count + enqueueLocked(data) + } + if remaining <= 0 { + let previous = consumptionTask + consumptionTask = Task { [state] in + await previous?.value + await state.recordStreamError(ManagedLineProcessError.bufferOverflow) } } - try? handle.close() } - return consumptionTask + try? handle.close() } - await task?.value + return consumptionTask } + await task?.value + } + + private func arm() { + lock.withLock { armLocked() } + } - private func arm() { - lock.withLock { armLocked() } + private func armLocked() { + guard !stopped else { return } + handle.readabilityHandler = { [weak self] handle in + self?.consumeAvailableData(from: handle) } + } - private func armLocked() { + private func consumeAvailableData(from handle: FileHandle) { + lock.withLock { guard !stopped else { return } - handle.readabilityHandler = { [weak self] handle in - self?.consumeAvailableData(from: handle) + handle.readabilityHandler = nil + guard let data = readChunkLocked() else { + armLocked() + return + } + guard !data.isEmpty else { + stopped = true + try? handle.close() + return } + enqueueLocked(data) } + } - private func consumeAvailableData(from handle: FileHandle) { - lock.withLock { - guard !stopped else { return } - handle.readabilityHandler = nil - guard let data = readChunkLocked() else { - armLocked() - return - } - guard !data.isEmpty else { - stopped = true - try? handle.close() - return - } - enqueueLocked(data) + private func readChunkLocked() -> Data? { + var bytes = [UInt8](repeating: 0, count: 65_536) + while true { + let count = Darwin.read(handle.fileDescriptor, &bytes, bytes.count) + if count >= 0 { return Data(bytes.prefix(count)) } + if errno == EINTR { continue } + if errno == EAGAIN || errno == EWOULDBLOCK { return nil } + let previous = consumptionTask + consumptionTask = Task { [state] in + await previous?.value + await state.recordStreamError( + ManagedLineProcessError.launchFailed("Cannot read managed process stdout.")) } + return Data() } + } - private func readChunkLocked() -> Data? { - var bytes = [UInt8](repeating: 0, count: 65_536) - while true { - let count = Darwin.read(handle.fileDescriptor, &bytes, bytes.count) - if count >= 0 { return Data(bytes.prefix(count)) } - if errno == EINTR { continue } - if errno == EAGAIN || errno == EWOULDBLOCK { return nil } - let previous = consumptionTask - consumptionTask = Task { [state] in - await previous?.value - await state.recordStreamError( - ManagedLineProcessError.launchFailed("Cannot read managed process stdout.")) - } - return Data() + private func enqueueLocked(_ data: Data) { + let previous = consumptionTask + consumptionTask = Task { [weak self, state] in + await previous?.value + do { + try await state.appendStandardOutput(data) + self?.arm() + } catch { + await state.recordStreamError(error) + self?.stopProducing() } } + } - private func enqueueLocked(_ data: Data) { - let previous = consumptionTask - consumptionTask = Task { [weak self, state] in - await previous?.value - do { - try await state.appendStandardOutput(data) - self?.arm() - } catch { - await state.recordStreamError(error) - self?.stopProducing() - } - } + private func stopProducing() { + lock.withLock { + guard !stopped else { return } + stopped = true + handle.readabilityHandler = nil + try? handle.close() } + } +} + +private actor ManagedLineProcessState { + private let continuation: AsyncThrowingStream.Continuation + private let maximumMessageBytes: Int + private let ownerProcessID: Int32 + private var state: ManagedLineProcessSnapshot.State = .starting + private var execution: Execution? + private var inputWriter: StandardInputWriter? + private var processID: Int32? + private var supervisorProcessID: Int32? + private var startedAt: Date? + private var stoppedAt: Date? + private var exitCode: Int32? + private var signal: Int32? + private var terminationEscalated = false + private var pendingWrites = 0 + private var lastError: String? + private var outputBuffer = Data() + private var shutdownRequested = false + private var launchFinished = false + private var readyWaiters: [CheckedContinuation] = [] + + init( + continuation: AsyncThrowingStream.Continuation, + maximumMessageBytes: Int, + ownerProcessID: Int32 + ) { + self.continuation = continuation + self.maximumMessageBytes = maximumMessageBytes + self.ownerProcessID = ownerProcessID + } - private func stopProducing() { - lock.withLock { - guard !stopped else { return } - stopped = true - handle.readabilityHandler = nil - try? handle.close() - } + func attach(execution: Execution, inputWriter: StandardInputWriter) { + self.execution = execution + self.inputWriter = inputWriter + supervisorProcessID = Int32(execution.processIdentifier.value) + startedAt = Date() + if shutdownRequested { + state = .stopping + failReadyWaiters(ManagedLineProcessError.closed) } } - private actor ManagedLineProcessState { - private let continuation: AsyncThrowingStream.Continuation - private let maximumMessageBytes: Int - private let ownerProcessID: Int32 - private var state: ManagedLineProcessSnapshot.State = .starting - private var execution: Execution? - private var inputWriter: StandardInputWriter? - private var processID: Int32? - private var supervisorProcessID: Int32? - private var startedAt: Date? - private var stoppedAt: Date? - private var exitCode: Int32? - private var signal: Int32? - private var terminationEscalated = false - private var pendingWrites = 0 - private var lastError: String? - private var outputBuffer = Data() - private var shutdownRequested = false - private var launchFinished = false - private var readyWaiters: [CheckedContinuation] = [] - - init( - continuation: AsyncThrowingStream.Continuation, - maximumMessageBytes: Int, - ownerProcessID: Int32 - ) { - self.continuation = continuation - self.maximumMessageBytes = maximumMessageBytes - self.ownerProcessID = ownerProcessID - } - - func attach(execution: Execution, inputWriter: StandardInputWriter) { - self.execution = execution - self.inputWriter = inputWriter - supervisorProcessID = Int32(execution.processIdentifier.value) - startedAt = Date() - if shutdownRequested { - state = .stopping - failReadyWaiters(ManagedLineProcessError.closed) - } + func attachChild(processID: Int32) { + self.processID = processID + guard !shutdownRequested else { return } + state = .running + let waiters = readyWaiters + readyWaiters.removeAll() + for waiter in waiters { + waiter.resume() } + } - func attachChild(processID: Int32) { - self.processID = processID - guard !shutdownRequested else { return } - state = .running - let waiters = readyWaiters - readyWaiters.removeAll() - for waiter in waiters { - waiter.resume() - } + func send(line: String) async throws { + try await waitUntilReady() + guard state == .running, let inputWriter else { + throw ManagedLineProcessError.closed } + guard line.utf8.count <= maximumMessageBytes else { + throw ManagedLineProcessError.oversizedMessage(maximumMessageBytes) + } + guard !line.contains("\n") else { throw ManagedLineProcessError.invalidConfiguration } + let frame = Data((line + "\n").utf8) + pendingWrites += 1 + defer { pendingWrites -= 1 } + _ = try await inputWriter.write(Array(frame)) + } - func send(line: String) async throws { - try await waitUntilReady() - guard state == .running, let inputWriter else { - throw ManagedLineProcessError.closed - } - guard line.utf8.count <= maximumMessageBytes else { - throw ManagedLineProcessError.oversizedMessage(maximumMessageBytes) - } - guard !line.contains("\n") else { throw ManagedLineProcessError.invalidConfiguration } - let frame = Data((line + "\n").utf8) - pendingWrites += 1 - defer { pendingWrites -= 1 } - _ = try await inputWriter.write(Array(frame)) + func appendStandardOutput(_ data: Data) throws { + guard !data.isEmpty else { return } + outputBuffer.append(data) + guard outputBuffer.count <= maximumMessageBytes || outputBuffer.contains(0x0A) else { + let error = ManagedLineProcessError.oversizedMessage(maximumMessageBytes) + continuation.finish(throwing: error) + throw error } - func appendStandardOutput(_ data: Data) throws { - guard !data.isEmpty else { return } - outputBuffer.append(data) - guard outputBuffer.count <= maximumMessageBytes || outputBuffer.contains(0x0A) else { + while let newline = outputBuffer.firstIndex(of: 0x0A) { + var line = outputBuffer[.. Execution? { - guard !hasFinished() else { return nil } - return execution - } + func finishInput() async throws { + try await inputWriter?.finish() + } - func isShuttingDown() -> Bool { shutdownRequested } + func runningExecution() -> Execution? { + guard !hasFinished() else { return nil } + return execution + } - func canSignalOwnedProcess() -> Bool { processID != nil || hasFinished() } + func isShuttingDown() -> Bool { shutdownRequested } - func signalChildGroup(_ signal: Int32) -> Bool { - // The live supervisor pins the child identity. Its exit does not authorize signalling a reused group. - guard !hasFinished(), let processID, processID > 1 else { return false } - return Darwin.kill(-processID, signal) == 0 || errno == ESRCH - } + func canSignalOwnedProcess() -> Bool { processID != nil || hasFinished() } - func recordTerminationEscalation() { - terminationEscalated = true - } + func signalChildGroup(_ signal: Int32) -> Bool { + // The live supervisor pins the child identity. Its exit does not authorize signalling a reused group. + guard !hasFinished(), let processID, processID > 1 else { return false } + return Darwin.kill(-processID, signal) == 0 || errno == ESRCH + } - func recordStreamError(_ error: Error) { - guard state != .stopped else { return } - lastError = Self.safeMessage(error) - continuation.finish(throwing: error) - } + func recordTerminationEscalation() { + terminationEscalated = true + } - func finish(status: TerminationStatus) { - launchFinished = true - switch status { - case .exited(let code): - exitCode = code - case .signaled(let code): - signal = code - } - inputWriter = nil - execution = nil - stoppedAt = Date() - if state != .failed { - state = .stopped - } - failReadyWaiters(ManagedLineProcessError.closed) - continuation.finish() - } - - func failLaunch(_ error: Error) { - guard !hasFinished() else { return } - launchFinished = true - state = .failed - lastError = Self.safeMessage(error) - stoppedAt = Date() - inputWriter = nil - execution = nil - let launchError = ManagedLineProcessError.launchFailed( - Self.safeMessage(error) - ) - failReadyWaiters(launchError) - continuation.finish(throwing: launchError) - } + func recordStreamError(_ error: Error) { + guard state != .stopped else { return } + lastError = Self.safeMessage(error) + continuation.finish(throwing: error) + } - func failTerminationTimeout() { - guard !cleanupIsConfirmed() else { return } - state = .failed - let error = ManagedLineProcessError.terminationTimedOut(processID: processID) - lastError = Self.safeMessage(error) - stoppedAt = Date() - failReadyWaiters(error) - continuation.finish(throwing: error) - } + func finish(status: TerminationStatus) { + launchFinished = true + switch status { + case .exited(let code): + exitCode = code + case .signaled(let code): + signal = code + } + inputWriter = nil + execution = nil + stoppedAt = Date() + if state != .failed { + state = .stopped + } + failReadyWaiters(ManagedLineProcessError.closed) + continuation.finish() + } - func hasFinished() -> Bool { - launchFinished && execution == nil - } - - func cleanupIsConfirmed() -> Bool { - guard hasFinished() else { return false } - guard let processID else { return supervisorProcessID == nil } - return Darwin.kill(-processID, 0) == -1 && errno == ESRCH - } - - func snapshot() -> ManagedLineProcessSnapshot { - ManagedLineProcessSnapshot( - state: hasFinished() && !cleanupIsConfirmed() ? .failed : state, - processID: processID, - supervisorProcessID: supervisorProcessID, - parentProcessID: ownerProcessID, - processGroupID: processID, - startedAt: startedAt, - stoppedAt: stoppedAt, - exitCode: exitCode, - signal: signal, - terminationEscalated: terminationEscalated, - pendingWrites: pendingWrites, - hasExited: hasFinished(), - lastError: hasFinished() && !cleanupIsConfirmed() - ? "Supervisor exited; child process-group cleanup is unconfirmed." : lastError, - cleanupConfirmed: cleanupIsConfirmed() - ) - } + func failLaunch(_ error: Error) { + guard !hasFinished() else { return } + launchFinished = true + state = .failed + lastError = Self.safeMessage(error) + stoppedAt = Date() + inputWriter = nil + execution = nil + let launchError = ManagedLineProcessError.launchFailed( + Self.safeMessage(error) + ) + failReadyWaiters(launchError) + continuation.finish(throwing: launchError) + } - private func waitUntilReady() async throws { - switch state { - case .running: - return - case .starting: - try await withCheckedThrowingContinuation { continuation in - readyWaiters.append(continuation) - } - case .stopping, .stopped: - throw ManagedLineProcessError.closed - case .failed: - throw ManagedLineProcessError.launchFailed( - lastError ?? "Unknown launch failure." - ) - } - } + func failTerminationTimeout() { + guard !cleanupIsConfirmed() else { return } + state = .failed + let error = ManagedLineProcessError.terminationTimedOut(processID: processID) + lastError = Self.safeMessage(error) + stoppedAt = Date() + failReadyWaiters(error) + continuation.finish(throwing: error) + } - private func failReadyWaiters(_ error: Error) { - let waiters = readyWaiters - readyWaiters.removeAll() - for waiter in waiters { - waiter.resume(throwing: error) - } + func hasFinished() -> Bool { + launchFinished && execution == nil + } + + func cleanupIsConfirmed() -> Bool { + guard hasFinished() else { return false } + guard let processID else { return supervisorProcessID == nil } + return Darwin.kill(-processID, 0) == -1 && errno == ESRCH + } + + func snapshot() -> ManagedLineProcessSnapshot { + ManagedLineProcessSnapshot( + state: hasFinished() && !cleanupIsConfirmed() ? .failed : state, + processID: processID, + supervisorProcessID: supervisorProcessID, + parentProcessID: ownerProcessID, + processGroupID: processID, + startedAt: startedAt, + stoppedAt: stoppedAt, + exitCode: exitCode, + signal: signal, + terminationEscalated: terminationEscalated, + pendingWrites: pendingWrites, + hasExited: hasFinished(), + lastError: hasFinished() && !cleanupIsConfirmed() + ? "Supervisor exited; child process-group cleanup is unconfirmed." : lastError, + cleanupConfirmed: cleanupIsConfirmed() + ) + } + + private func waitUntilReady() async throws { + switch state { + case .running: + return + case .starting: + try await withCheckedThrowingContinuation { continuation in + readyWaiters.append(continuation) + } + case .stopping, .stopped: + throw ManagedLineProcessError.closed + case .failed: + throw ManagedLineProcessError.launchFailed( + lastError ?? "Unknown launch failure." + ) } + } - private static func safeMessage(_ error: Error) -> String { - // Generic I/O errors can contain argv, environment values or protocol payloads. - guard let error = error as? ManagedLineProcessError else { - return "Managed process I/O failed." - } - return error.localizedDescription + private func failReadyWaiters(_ error: Error) { + let waiters = readyWaiters + readyWaiters.removeAll() + for waiter in waiters { + waiter.resume(throwing: error) } } -#endif + private static func safeMessage(_ error: Error) -> String { + // Generic I/O errors can contain argv, environment values or protocol payloads. + guard let error = error as? ManagedLineProcessError else { + return "Managed process I/O failed." + } + return error.localizedDescription + } +} diff --git a/Sources/CodexAdapter/ManagedLineProcess+Windows.swift b/Sources/CodexAdapter/ManagedLineProcess+Windows.swift deleted file mode 100644 index 80f5abb..0000000 --- a/Sources/CodexAdapter/ManagedLineProcess+Windows.swift +++ /dev/null @@ -1,240 +0,0 @@ -#if os(Windows) - import CodexAppServerRuntime - import CodexAppServerStdio - import Foundation - import WinSDK - - /// The SDK owns launch, Job Object membership, pipes and joined native cleanup. - /// The adapter owns shutdown deadlines and the lifetime reported in runtime receipts. - final class ManagedLineProcess: Sendable { - let inboundLines: AsyncThrowingStream - private let transport: CodexAppServerStdioTransport - private let state: ManagedWindowsProcessState - private let lifecycle: Task - - init(configuration: Configuration) throws { - try configuration.validate() - // Retain the configured owner's identity before admitting a child. The current - // process is also protected by the SDK's non-inherited kill-on-close Job handle. - let parent = try ManagedWindowsProcessParent(processID: configuration.ownerProcessID) - let transport: CodexAppServerStdioTransport - do { - transport = try CodexAppServerStdioTransport( - configuration: .init( - executableURL: URL(fileURLWithPath: configuration.executable), - arguments: configuration.arguments, environment: configuration.environment, - workingDirectoryURL: configuration.workingDirectory, - maximumMessageBytes: configuration.maximumMessageBytes)) - } catch { - throw ManagedLineProcessError.launchFailed("Native process admission failed.") - } - self.transport = transport - self.inboundLines = transport.inboundLines - let state = ManagedWindowsProcessState(configuration: configuration, transport: transport) - self.state = state - let parentWait = parent.wait() - let parentCleanup = Task.detached { - switch await parentWait.value { - case .stopped: return - case .exited: await state.ownerEnded(failed: false) - case .failed: await state.ownerEnded(failed: true) - } - await transport.close() - } - self.lifecycle = Task.detached { - let result: Result - do { result = .success(try await transport.waitForExit()) } catch { - result = .failure(error) - } - parent.stop() - await parentCleanup.value - await state.finished(result) - } - } - - deinit { - let transport = transport - let lifecycle = lifecycle - Task { - await transport.close() - await lifecycle.value - } - } - - func sendLine(_ line: String) async throws { try await state.send(line) } - func close() async { - await state.close() - if await state.snapshot().cleanupConfirmed == true { await lifecycle.value } - } - func snapshot() async -> ManagedLineProcessSnapshot { await state.snapshot() } - } - - private actor ManagedWindowsProcessState { - private let configuration: ManagedLineProcess.Configuration - private let transport: CodexAppServerStdioTransport - private let startedAt = Date() - private var state: ManagedLineProcessSnapshot.State = .running - private var stoppedAt: Date? - private var exitCode: Int32? - private var signal: Int32? - private var terminationEscalated = false - private var pendingWrites = 0 - private var hasExited = false - private var cleanupConfirmed = false - private var lastError: String? - private var closeTask: Task? - private var inputFinish: Task? - private var forcedClose: Task? - - init(configuration: ManagedLineProcess.Configuration, transport: CodexAppServerStdioTransport) { - self.configuration = configuration - self.transport = transport - } - - func send(_ line: String) async throws { - guard state == .running else { throw ManagedLineProcessError.closed } - pendingWrites += 1 - defer { pendingWrites -= 1 } - do { try await transport.sendLine(line) } catch CodexAppServerStdioError.closed { - throw ManagedLineProcessError.closed - } catch let error as CodexAppServerConnectionFoundation.FoundationError { - switch error { - case .messageTooLarge(let limit): throw ManagedLineProcessError.oversizedMessage(limit) - case .embeddedNewline: throw ManagedLineProcessError.invalidConfiguration - case .bufferLimitExceeded: throw ManagedLineProcessError.bufferOverflow - default: throw ManagedLineProcessError.launchFailed("Managed process I/O failed.") - } - } catch { - throw ManagedLineProcessError.launchFailed("Managed process I/O failed.") - } - } - - func close() async { - if let closeTask { - await closeTask.value - return - } - if cleanupConfirmed { return } - if state == .running { state = .stopping } - // The actor retains the shutdown owner through every caller's cancellation. - let task = Task { await self.performClose() } - closeTask = task - await task.value - } - - private func performClose() async { - let finishing = Task { _ = try? await transport.finishInput() } - inputFinish = finishing - if await waitForCleanup(milliseconds: configuration.terminationGraceMilliseconds) { - await finishing.value - return - } - terminationEscalated = true - let forced = Task { await transport.close() } - forcedClose = forced - if await waitForCleanup(milliseconds: configuration.killGraceMilliseconds) { - await forced.value - await finishing.value - } else { - // These tasks keep the exact SDK owner alive after the reporting deadline. - // Only finished() can subsequently confirm cleanup and release the lifetime. - state = .failed - lastError = "Native process cleanup did not finish before the configured deadline." - } - } - - private func waitForCleanup(milliseconds: Int) async -> Bool { - let deadline = ContinuousClock.now + .milliseconds(milliseconds) - while !cleanupConfirmed { - if ContinuousClock.now >= deadline { return false } - try? await Task.sleep(for: .milliseconds(10)) - } - return true - } - - func ownerEnded(failed: Bool) { - if state == .running { state = .stopping } - terminationEscalated = true - if failed { - state = .failed - lastError = "Native parent ownership observation failed." - } - } - - func finished(_ result: Result) async { - await inputFinish?.value - await forcedClose?.value - stoppedAt = Date() - switch result { - case .success(let termination): - hasExited = true - cleanupConfirmed = true - switch termination { - case .exited(let code): exitCode = code - case .signalled(let code): signal = code - } - if state != .failed { state = .stopped } - case .failure: - state = .failed - lastError = "Native process cleanup could not be confirmed." - } - } - - func snapshot() -> ManagedLineProcessSnapshot { - .init( - state: state, processID: transport.processIdentifier, supervisorProcessID: nil, - parentProcessID: configuration.ownerProcessID, processGroupID: nil, - startedAt: startedAt, stoppedAt: stoppedAt, exitCode: exitCode, signal: signal, - terminationEscalated: terminationEscalated, pendingWrites: pendingWrites, - hasExited: hasExited, lastError: lastError, cleanupConfirmed: cleanupConfirmed) - } - } - - /// Both non-inheritable handles remain immutable until the native waiter completes. - /// Closing the child never signals the borrowed owner process. - private final class ManagedWindowsProcessParent: @unchecked Sendable { - enum Result: Sendable { case exited, stopped, failed } - private let process: HANDLE - private let stopped: HANDLE - - init(processID: Int32) throws { - guard let process = OpenProcess(DWORD(SYNCHRONIZE), false, DWORD(bitPattern: processID)) - else { - throw ManagedLineProcessError.launchFailed("Cannot observe the configured process owner.") - } - guard WaitForSingleObject(process, 0) == DWORD(WAIT_TIMEOUT) else { - CloseHandle(process) - throw ManagedLineProcessError.launchFailed("The configured process owner is not running.") - } - guard let stopped = CreateEventW(nil, true, false, nil) else { - CloseHandle(process) - throw ManagedLineProcessError.launchFailed("Cannot create the process owner observation.") - } - self.process = process - self.stopped = stopped - } - - deinit { - CloseHandle(stopped) - CloseHandle(process) - } - - func stop() { _ = SetEvent(stopped) } - - func wait() -> Task { - Task.detached { - await withCheckedContinuation { continuation in - DispatchQueue.global(qos: .utility).async { [self] in - var handles: [HANDLE?] = [stopped, process] - let result = WaitForMultipleObjects(2, &handles, false, DWORD(INFINITE)) - switch result { - case DWORD(WAIT_OBJECT_0): continuation.resume(returning: .stopped) - case DWORD(WAIT_OBJECT_0 + 1): continuation.resume(returning: .exited) - default: continuation.resume(returning: .failed) - } - } - } - } - } - } -#endif diff --git a/Sources/CodexAdapter/ManagedLineProcess.swift b/Sources/CodexAdapter/ManagedLineProcess.swift index 1a3eec2..bd0d39f 100644 --- a/Sources/CodexAdapter/ManagedLineProcess.swift +++ b/Sources/CodexAdapter/ManagedLineProcess.swift @@ -102,24 +102,14 @@ extension ManagedLineProcess { } func validate() throws { - #if os(Windows) - let validPaths = - Self.isAbsoluteWindowsPath(executable) - && workingDirectory.isFileURL && Self.isAbsoluteWindowsPath(workingDirectory.path) - let validOwner = UInt32(bitPattern: ownerProcessID) > 1 - let validEnvironment = environment.allSatisfy { - !$0.key.isEmpty && !$0.key.contains("\0") && !$0.value.contains("\0") - } - #else - let validPaths = - executable.hasPrefix("/") - && workingDirectory.isFileURL && workingDirectory.path.hasPrefix("/") - let validOwner = ownerProcessID > 1 - let validEnvironment = environment.allSatisfy { - !$0.key.isEmpty && !$0.key.contains("=") && !$0.key.contains("\0") - && !$0.value.contains("\0") - } - #endif + let validPaths = + executable.hasPrefix("/") + && workingDirectory.isFileURL && workingDirectory.path.hasPrefix("/") + let validOwner = ownerProcessID > 1 + let validEnvironment = environment.allSatisfy { + !$0.key.isEmpty && !$0.key.contains("=") && !$0.key.contains("\0") + && !$0.value.contains("\0") + } guard validPaths, validOwner, validEnvironment, !executable.contains("\0"), !workingDirectory.path.contains("\0"), arguments.allSatisfy({ !$0.contains("\0") }), @@ -129,14 +119,5 @@ extension ManagedLineProcess { else { throw ManagedLineProcessError.invalidConfiguration } } - #if os(Windows) - private static func isAbsoluteWindowsPath(_ path: String) -> Bool { - let bytes = Array(path.replacingOccurrences(of: "/", with: "\\").utf8) - if bytes.starts(with: [92, 92]) { return bytes.count > 2 } - guard bytes.count >= 3, bytes[1] == 58, bytes[2] == 92 else { return false } - return (65...90).contains(bytes[0]) || (97...122).contains(bytes[0]) - } - #endif - } } diff --git a/Sources/CodexAdapter/WindowsCommandProcess.swift b/Sources/CodexAdapter/WindowsCommandProcess.swift deleted file mode 100644 index 19cb30d..0000000 --- a/Sources/CodexAdapter/WindowsCommandProcess.swift +++ /dev/null @@ -1,223 +0,0 @@ -#if os(Windows) - import Dispatch - import Foundation - import Synchronization - import WinSDK - - /// Owns the root process and the parent ends of its two output pipes. - final class WindowsCommandProcess: Sendable { - struct Capture: Sendable { - var data = Data() - var truncated = false - } - - // HANDLE is an opaque kernel token. Only this owner's mutex guards access - // and closing; synchronous Job admission borrows the tokens without retaining them. - private struct Handles: @unchecked Sendable { - let process: HANDLE - let thread: HANDLE - } - - private let handles: Mutex - let stdout: WindowsCommandPipe - let stderr: WindowsCommandPipe - - init( - path: String, arguments: [String], cwd: String, environment: [String: String], - job: WindowsProcessJob - ) throws { - var command = Array(([path] + arguments).map(Self.quote).joined(separator: " ").utf16) + [0] - guard command.count <= 32_767 else { - throw CommandRunnerError.launchFailed("Command line exceeds the Windows launch limit.") - } - let output = try Self.pipe() - defer { CloseHandle(output.write) } - let stdout = WindowsCommandPipe(output.read) - let error = try Self.pipe() - defer { CloseHandle(error.write) } - let stderr = WindowsCommandPipe(error.read) - let input = try Self.pipe() - // Closing the only writer supplies EOF without inheriting the host's stdin. - CloseHandle(input.write) - defer { CloseHandle(input.read) } - guard SetHandleInformation(input.read, DWORD(HANDLE_FLAG_INHERIT), DWORD(HANDLE_FLAG_INHERIT)) - else { throw Self.error("SetHandleInformation") } - - var environmentBlock = - Array( - environment.sorted { WindowsProcessEnvironment.isOrderedBefore($0.key, $1.key) } - .map { "\($0.key)=\($0.value)" }.joined(separator: "\0").utf16) + [0, 0] - var info = STARTUPINFOEXW() - info.StartupInfo.cb = DWORD(MemoryLayout.size) - info.StartupInfo.dwFlags = DWORD(STARTF_USESTDHANDLES) | DWORD(STARTF_USESHOWWINDOW) - info.StartupInfo.wShowWindow = WORD(SW_HIDE) - info.StartupInfo.hStdInput = input.read - info.StartupInfo.hStdOutput = output.write - info.StartupInfo.hStdError = error.write - - var attributeBytes: SIZE_T = 0 - _ = InitializeProcThreadAttributeList(nil, 1, 0, &attributeBytes) - guard attributeBytes > 0 else { throw Self.error("InitializeProcThreadAttributeList") } - let storage = UnsafeMutableRawPointer.allocate(byteCount: Int(attributeBytes), alignment: 16) - defer { storage.deallocate() } - let attributes = LPPROC_THREAD_ATTRIBUTE_LIST(storage) - guard InitializeProcThreadAttributeList(attributes, 1, 0, &attributeBytes) else { - throw Self.error("InitializeProcThreadAttributeList") - } - var inherited = [input.read, output.write, error.write] - var created = PROCESS_INFORMATION() - try inherited.withUnsafeMutableBufferPointer { list in - defer { DeleteProcThreadAttributeList(attributes) } - // PROC_THREAD_ATTRIBUTE_HANDLE_LIST: attribute 2, input flag. The SDK macro - // uses a C expression that is not imported by Swift. - guard - UpdateProcThreadAttribute( - attributes, 0, 0x0002_0002, list.baseAddress, - SIZE_T(list.count * MemoryLayout.stride), nil, nil) - else { throw Self.error("UpdateProcThreadAttribute") } - info.lpAttributeList = attributes - let launched = path.withCString(encodedAs: UTF16.self) { executable in - cwd.withCString(encodedAs: UTF16.self) { directory in - environmentBlock.withUnsafeMutableBufferPointer { environment in - withUnsafeMutablePointer(to: &info) { extended in - extended.withMemoryRebound(to: STARTUPINFOW.self, capacity: 1) { startup in - CreateProcessW( - executable, &command, nil, nil, true, - DWORD( - CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT), - environment.baseAddress, directory, startup, &created) - } - } - } - } - } - guard launched else { throw Self.error("CreateProcessW") } - } - handles = Mutex(Handles(process: created.hProcess!, thread: created.hThread!)) - self.stdout = stdout - self.stderr = stderr - job.start(process: created.hProcess!, thread: created.hThread!) - } - - deinit { - handles.withLock { - CloseHandle($0.thread) - CloseHandle($0.process) - } - } - - func wait(job: WindowsProcessJob) async -> Result { - while true { - let status = handles.withLock { WaitForSingleObject($0.process, 0) } - switch status { - case DWORD(WAIT_OBJECT_0): - job.stop(.exited) - return handles.withLock { - var code: DWORD = 0 - guard GetExitCodeProcess($0.process, &code) else { - return .failure(Self.error("GetExitCodeProcess")) - } - return .success(Int32(bitPattern: code)) - } - case DWORD(WAIT_TIMEOUT): - try? await Task.sleep(for: .milliseconds(10)) - default: - let error = Self.error("WaitForSingleObject") - job.stop(.failed) - return .failure(error) - } - } - } - - private static func pipe() throws -> (read: HANDLE, write: HANDLE) { - var security = SECURITY_ATTRIBUTES() - security.nLength = DWORD(MemoryLayout.size) - security.bInheritHandle = true - var read: HANDLE? - var write: HANDLE? - guard CreatePipe(&read, &write, &security, 0), let read, let write else { - throw error("CreatePipe") - } - guard SetHandleInformation(read, DWORD(HANDLE_FLAG_INHERIT), 0) else { - let failure = error("SetHandleInformation") - CloseHandle(read) - CloseHandle(write) - throw failure - } - return (read, write) - } - - /// Encodes one CRT argument, including empty values and trailing backslashes. - private static func quote(_ argument: String) -> String { - if !argument.isEmpty, !argument.contains(where: { " \t\r\n\"".contains($0) }) { - return argument - } - var result = "\"" - var slashes = 0 - for character in argument.unicodeScalars { - if character == "\\" { - slashes += 1 - } else { - result += String(repeating: "\\", count: character == "\"" ? slashes * 2 + 1 : slashes) - result.unicodeScalars.append(character) - slashes = 0 - } - } - return result + String(repeating: "\\", count: slashes * 2) + "\"" - } - - static func error(_ operation: String, code: DWORD = GetLastError()) -> CommandRunnerError { - .launchFailed("\(operation) failed (Windows error \(code)).") - } - } - - /// A blocking reader runs on a dispatch worker and retains its handle until EOF. - final class WindowsCommandPipe: Sendable { - // The native token is never dereferenced. The mutex guards the reader's - // exclusive ownership through ReadFile and CloseHandle. - private struct State: @unchecked Sendable { - var handle: HANDLE? - } - - private let state: Mutex - private let reader = DispatchQueue(label: "codex-adapter.command.output", qos: .utility) - - init(_ handle: HANDLE) { state = Mutex(State(handle: handle)) } - deinit { state.withLock { if let value = $0.handle { CloseHandle(value) } } } - - func capture( - limit: Int, job: WindowsProcessJob - ) async -> Result { - await withCheckedContinuation { continuation in - reader.async { - let result = self.state.withLock { - state -> Result in - guard let pipe = state.handle else { - return .failure(.launchFailed("Command output pipe is closed.")) - } - defer { - CloseHandle(pipe) - state.handle = nil - } - var captured = WindowsCommandProcess.Capture() - var buffer = [UInt8](repeating: 0, count: 16_384) - while true { - var count: DWORD = 0 - guard ReadFile(pipe, &buffer, DWORD(buffer.count), &count, nil) else { - let code = GetLastError() - if code == DWORD(ERROR_BROKEN_PIPE) { return .success(captured) } - return .failure(WindowsCommandProcess.error("ReadFile", code: code)) - } - if count == 0 { return .success(captured) } - let retained = min(Int(count), limit - captured.data.count) - captured.data.append(contentsOf: buffer.prefix(retained)) - captured.truncated = captured.truncated || retained < Int(count) - } - } - if case .failure = result { job.stop(.failed) } - continuation.resume(returning: result) - } - } - } - } -#endif diff --git a/Sources/CodexAdapter/WindowsExecutable.swift b/Sources/CodexAdapter/WindowsExecutable.swift deleted file mode 100644 index e83d075..0000000 --- a/Sources/CodexAdapter/WindowsExecutable.swift +++ /dev/null @@ -1,49 +0,0 @@ -#if os(Windows) - import Foundation - - enum WindowsExecutable { - static func resolve(_ executable: String, workspace: URL, environment: [String: String]) throws - -> URL - { - guard let directory = WindowsFilePath.native(workspace), - WindowsFilePath.isValid(directory), WindowsFilePath.isAbsolute(directory), - WindowsFilePath.isValid(executable) - else { - throw CommandRunnerError.launchFailed("Invalid Windows executable or working directory.") - } - let candidates: [String] - if executable.contains(where: { "/\\:".contains($0) }) { - candidates = [executable] - } else { - let entries = environment.filter { WindowsProcessEnvironment.namesMatch($0.key, "PATH") } - guard entries.count <= 1, entries.first?.value.utf16.contains(0) != true else { - throw CommandRunnerError.launchFailed("Invalid or ambiguous Windows PATH.") - } - let name = executable.lowercased().hasSuffix(".exe") ? executable : executable + ".exe" - candidates = (entries.first?.value ?? "").split(separator: ";").compactMap { entry in - var path = String(entry) - if path.hasPrefix("\""), path.hasSuffix("\""), path.count >= 2 { - path.removeFirst() - path.removeLast() - } - return path.isEmpty ? nil : path + "\\" + name - } - } - for candidate in candidates { - guard let path = WindowsFilePath.absolute(candidate, cwd: directory) else { - throw CommandRunnerError.launchFailed( - "Executable paths must be native absolute or workspace-relative paths.") - } - let url = URL(fileURLWithPath: path) - var isDirectory: ObjCBool = false - if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory), - !isDirectory.boolValue, FileManager.default.isExecutableFile(atPath: url.path) - { - return url - } - } - throw CommandRunnerError.launchFailed( - "Cannot resolve executable in the launch workspace and PATH.") - } - } -#endif diff --git a/Sources/CodexAdapter/WindowsFilePath.swift b/Sources/CodexAdapter/WindowsFilePath.swift deleted file mode 100644 index c69b81f..0000000 --- a/Sources/CodexAdapter/WindowsFilePath.swift +++ /dev/null @@ -1,65 +0,0 @@ -#if os(Windows) - import Foundation - import WinSDK - - /// Win32 filesystem names; device namespaces and alternate streams are not paths here. - enum WindowsFilePath { - static func native(_ url: URL) -> String? { - guard url.isFileURL else { return nil } - return url.withUnsafeFileSystemRepresentation { $0.map(String.init(cString:)) } - } - - static func isAbsolute(_ value: String) -> Bool { - let path = value.replacingOccurrences(of: "/", with: "\\") - let bytes = Array(path.utf8.prefix(3)) - return path.hasPrefix("\\\\") - || (bytes.count == 3 && isDriveLetter(bytes[0]) && bytes[1] == 58 && bytes[2] == 92) - } - - static func isDriveLetter(_ value: UInt8) -> Bool { - (65...90).contains(value) || (97...122).contains(value) - } - - static func isValid(_ value: String) -> Bool { - guard !value.isEmpty, value.utf16.count < 32_767, - !value.unicodeScalars.contains(where: { $0.value < 32 || $0.value == 127 }) - else { return false } - let path = value.replacingOccurrences(of: "/", with: "\\") - guard !path.hasPrefix("\\\\?\\"), !path.hasPrefix("\\\\.\\"), !path.hasPrefix("\\??\\"), - !path.contains(where: { "<>\"|?*".contains($0) }) - else { return false } - let bytes = Array(path.utf8) - let withoutDrive = - bytes.count >= 3 && isDriveLetter(bytes[0]) && bytes[1] == 58 && bytes[2] == 92 - ? String(path.dropFirst(2)) : path - guard !withoutDrive.contains(":") else { return false } - let components = withoutDrive.split(separator: "\\") - if path.hasPrefix("\\\\") { - guard components.count >= 2, - !components.prefix(2).contains(where: { $0 == "." || $0 == ".." }), - !["pipe", "mailslot"].contains(components[1].lowercased()) - else { return false } - } - return components.allSatisfy { component in - if component == "." || component == ".." { return true } - guard component.last != ".", component.last != " " else { return false } - let stem = component.split(separator: ".", maxSplits: 1).first?.uppercased() ?? "" - return !["CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$"].contains(stem) - && !["COM¹", "COM²", "COM³", "LPT¹", "LPT²", "LPT³"].contains(stem) - && !(1...9).contains(where: { stem == "COM\($0)" || stem == "LPT\($0)" }) - } - } - - static func absolute(_ value: String, cwd: String) -> String? { - guard isValid(value) else { return nil } - let path = value.replacingOccurrences(of: "/", with: "\\") - guard !path.hasPrefix("\\") || path.hasPrefix("\\\\") else { return nil } - let joined = isAbsolute(path) ? path : cwd + "\\" + path - guard joined.utf16.count < 32_767 else { return nil } - var output = [WCHAR](repeating: 0, count: 32_768) - let length = GetFullPathNameW(Array(joined.utf16) + [0], DWORD(output.count), &output, nil) - guard length > 0, length < output.count else { return nil } - return String(decoding: output.prefix(Int(length)), as: UTF16.self) - } - } -#endif diff --git a/Sources/CodexAdapter/WindowsPrivateDirectory.swift b/Sources/CodexAdapter/WindowsPrivateDirectory.swift deleted file mode 100644 index 1c76f46..0000000 --- a/Sources/CodexAdapter/WindowsPrivateDirectory.swift +++ /dev/null @@ -1,322 +0,0 @@ -#if os(Windows) - import Foundation - import WinSDK - - enum WindowsPrivateDirectoryError: Error, LocalizedError, Sendable { - case invalid(String) - case native(String, DWORD) - - var errorDescription: String? { - switch self { - case .invalid(let message): message - case .native(let operation, let code): "\(operation) failed (Windows error \(code))." - } - } - } - - /// Keeps the private directory and its ancestry stable for its consumer's lifetime. - final class WindowsPrivateDirectory: @unchecked Sendable { - // These non-inherited kernel handles are immutable, never exposed and closed only on deinit. - private let handles: [HANDLE] - private let security: PrivateSecurity - private let ancestor: WindowsPrivateDirectory? - private let requiresPrivateDACL: Bool - let url: URL - - convenience init(_ url: URL) throws { - try self.init(url, createMissing: true, ancestor: nil, requiresPrivateDACL: true) - } - - convenience init(existingDirectory url: URL) throws { - try self.init(url, createMissing: false, ancestor: nil, requiresPrivateDACL: true) - } - - convenience init(existingDirectory url: URL, containedIn root: URL) throws { - try self.init( - url, createMissing: false, ancestor: WindowsPrivateDirectory(existingDirectory: root), - requiresPrivateDACL: false) - } - - convenience init(creatingDirectory url: URL, containedIn root: URL) throws { - try self.init( - url, createMissing: true, ancestor: WindowsPrivateDirectory(existingDirectory: root), - requiresPrivateDACL: true) - } - - private init( - _ url: URL, createMissing: Bool, ancestor: WindowsPrivateDirectory?, - requiresPrivateDACL: Bool - ) throws { - guard let native = WindowsFilePath.native(url), WindowsFilePath.isAbsolute(native), - let path = WindowsFilePath.absolute(native, cwd: native) - else { - throw WindowsPrivateDirectoryError.invalid( - "Private state requires a native absolute directory.") - } - let prefixes = try Self.ancestors(path) - let security = try PrivateSecurity() - let ancestorIdentity = try ancestor.map { try WindowsDirectoryIdentity($0.handles.last!) } - var foundAncestor = ancestor == nil - var retained: [HANDLE] = [] - do { - for (index, prefix) in prefixes.enumerated() { - let final = index == prefixes.count - 1 - var inspectSecurity = final && requiresPrivateDACL - var handle = Self.open(prefix, inspectSecurity: inspectSecurity) - if handle == nil || handle == INVALID_HANDLE_VALUE { - let code = GetLastError() - guard createMissing, foundAncestor, index > 0, - code == DWORD(ERROR_FILE_NOT_FOUND) || code == DWORD(ERROR_PATH_NOT_FOUND) - else { - throw WindowsPrivateDirectoryError.native("Open private directory ancestry", code) - } - var attributes = SECURITY_ATTRIBUTES() - attributes.nLength = DWORD(MemoryLayout.size) - attributes.lpSecurityDescriptor = security.descriptor - attributes.bInheritHandle = false - if !CreateDirectoryW(Array(prefix.utf16) + [0], &attributes) { - let code = GetLastError() - guard code == DWORD(ERROR_ALREADY_EXISTS) else { - throw WindowsPrivateDirectoryError.native("Create private directory", code) - } - } - inspectSecurity = true - handle = Self.open(prefix, inspectSecurity: true) - } - guard let handle, handle != INVALID_HANDLE_VALUE else { - throw WindowsPrivateDirectoryError.native("Open private directory", GetLastError()) - } - retained.append(handle) - try Self.validateDirectory(handle) - if inspectSecurity { try security.validate(handle) } - if let ancestorIdentity, try WindowsDirectoryIdentity(handle) == ancestorIdentity { - foundAncestor = true - } - } - guard foundAncestor else { - throw WindowsPrivateDirectoryError.invalid( - "Managed directory ancestry does not contain the owned private root.") - - } - } catch { - for handle in retained.reversed() { CloseHandle(handle) } - throw error - } - handles = retained - self.security = security - self.ancestor = ancestor - self.requiresPrivateDACL = requiresPrivateDACL - self.url = URL(fileURLWithPath: path, isDirectory: true) - } - - deinit { for handle in handles.reversed() { CloseHandle(handle) } } - - func validate() throws { - for handle in handles { try Self.validateDirectory(handle) } - try ancestor?.validate() - if requiresPrivateDACL { try security.validate(handles[handles.count - 1]) } - } - - private static func open(_ path: String, inspectSecurity: Bool) -> HANDLE? { - // Attribute-only handles do not participate in data/delete sharing checks. - // Directory read access makes denial of rename/delete effective for this handle's lifetime. - CreateFileW( - Array(path.utf16) + [0], - DWORD(FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES) - | (inspectSecurity ? DWORD(READ_CONTROL) : 0), - DWORD(FILE_SHARE_READ), nil, DWORD(OPEN_EXISTING), - DWORD(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT), nil) - } - - private static func validateDirectory(_ handle: HANDLE) throws { - var information = BY_HANDLE_FILE_INFORMATION() - guard GetFileType(handle) == DWORD(FILE_TYPE_DISK), - GetFileInformationByHandle(handle, &information) - else { - throw WindowsPrivateDirectoryError.native("Inspect private directory", GetLastError()) - } - guard information.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) != 0, - information.dwFileAttributes & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == 0 - else { - throw WindowsPrivateDirectoryError.invalid( - "Private state paths must be real directories without reparse points.") - } - } - - private static func ancestors(_ path: String) throws -> [String] { - let components = path.split(separator: "\\") - var current: String - let suffix: ArraySlice - if path.hasPrefix("\\\\") { - guard components.count >= 3 else { - throw WindowsPrivateDirectoryError.invalid( - "A network share root cannot be private adapter state.") - } - current = "\\\\" + components[0] + "\\" + components[1] - suffix = components.dropFirst(2) - } else { - guard components.count >= 2 else { - throw WindowsPrivateDirectoryError.invalid( - "A volume root cannot be private adapter state.") - } - current = String(path.prefix(3)) - suffix = components.dropFirst() - } - guard components.count <= 1_024 else { - throw WindowsPrivateDirectoryError.invalid( - "Private directory ancestry exceeds its inspection bound.") - } - var result = [current] - for component in suffix { - current += (current.hasSuffix("\\") ? "" : "\\") + component - result.append(current) - } - return result - } - - private final class PrivateSecurity { - // FILE_ALL_ACCESS from winnt.h; Swift cannot import its mixed-type C expression. - private static let fileAllAccess = - DWORD(STANDARD_RIGHTS_REQUIRED) | DWORD(SYNCHRONIZE) | 0x1FF - let descriptor: PSECURITY_DESCRIPTOR - private let owner: PSID - - init() throws { - let sid = try Self.currentUserSID() - let text = "O:\(sid)D:P(A;OICI;FA;;;\(sid))" - var value: PSECURITY_DESCRIPTOR? - guard - ConvertStringSecurityDescriptorToSecurityDescriptorW( - Array(text.utf16) + [0], DWORD(SDDL_REVISION_1), &value, nil), let value - else { - throw WindowsPrivateDirectoryError.native( - "Create private security descriptor", GetLastError()) - } - var owner: PSID? - var defaulted: WindowsBool = false - guard GetSecurityDescriptorOwner(value, &owner, &defaulted), let owner, IsValidSid(owner) - else { - let code = GetLastError() - LocalFree(value) - throw WindowsPrivateDirectoryError.native("Inspect private security owner", code) - } - descriptor = value - self.owner = owner - } - - deinit { LocalFree(descriptor) } - - func validate(_ handle: HANDLE) throws { - var flags: DWORD = 0 - guard GetVolumeInformationByHandleW(handle, nil, 0, nil, nil, &flags, nil, 0), - flags & DWORD(FILE_PERSISTENT_ACLS) != 0 - else { - throw WindowsPrivateDirectoryError.invalid( - "Private state requires a filesystem with persistent access controls.") - } - var actualOwner: PSID? - var acl: PACL? - var actual: PSECURITY_DESCRIPTOR? - let result = GetSecurityInfo( - handle, SE_FILE_OBJECT, - DWORD(OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION), &actualOwner, nil, &acl, - nil, &actual) - guard result == DWORD(ERROR_SUCCESS), let actual else { - throw WindowsPrivateDirectoryError.native("Read private directory security", result) - } - defer { LocalFree(actual) } - guard let actualOwner, IsValidSid(actualOwner), EqualSid(actualOwner, owner) else { - throw WindowsPrivateDirectoryError.invalid( - "Private state directory belongs to a different or unverifiable Windows user.") - } - var control: SECURITY_DESCRIPTOR_CONTROL = 0 - var revision: DWORD = 0 - guard GetSecurityDescriptorControl(actual, &control, &revision), - control & SECURITY_DESCRIPTOR_CONTROL(SE_DACL_PROTECTED) != 0, - let acl, IsValidAcl(acl), acl.pointee.AceCount == 1 - else { - throw WindowsPrivateDirectoryError.invalid( - "Existing state directory is not protected by the adapter's private DACL; it was left unchanged." - ) - } - var entry: LPVOID? - guard GetAce(acl, 0, &entry), let entry else { - throw WindowsPrivateDirectoryError.native( - "Read private directory access entry", GetLastError()) - } - let allowed = entry.assumingMemoryBound(to: ACCESS_ALLOWED_ACE.self) - let header = allowed.pointee.Header - guard header.AceType == BYTE(ACCESS_ALLOWED_ACE_TYPE), - header.AceFlags == BYTE(OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE), - allowed.pointee.Mask == Self.fileAllAccess, - Int(header.AceSize) >= MemoryLayout.size + MemoryLayout.size - else { - throw WindowsPrivateDirectoryError.invalid( - "Existing state directory has incompatible access entries; it was left unchanged.") - } - let sid = entry.advanced(by: MemoryLayout.offset(of: \.SidStart)!) - guard IsValidSid(sid), - Int(GetLengthSid(sid)) <= Int(header.AceSize) - MemoryLayout.offset( - of: \.SidStart)!, - EqualSid(sid, owner) - else { - throw WindowsPrivateDirectoryError.invalid( - "Existing state directory grants access to another principal; it was left unchanged.") - } - } - - private static func currentUserSID() throws -> String { - var token: HANDLE? - if !OpenThreadToken(GetCurrentThread(), DWORD(TOKEN_QUERY), true, &token) { - let code = GetLastError() - guard code == DWORD(ERROR_NO_TOKEN) else { - throw WindowsPrivateDirectoryError.native("Open effective user token", code) - } - guard OpenProcessToken(GetCurrentProcess(), DWORD(TOKEN_QUERY), &token) else { - throw WindowsPrivateDirectoryError.native("Open process user token", GetLastError()) - } - } - guard let token else { - throw WindowsPrivateDirectoryError.invalid("Windows user token is unavailable.") - } - defer { CloseHandle(token) } - var count: DWORD = 0 - _ = GetTokenInformation(token, TokenUser, nil, 0, &count) - guard count >= MemoryLayout.size, count <= 4_096 else { - throw WindowsPrivateDirectoryError.invalid("Windows user token has an invalid size.") - } - let storage = UnsafeMutableRawPointer.allocate( - byteCount: Int(count), alignment: MemoryLayout.alignment) - defer { storage.deallocate() } - guard GetTokenInformation(token, TokenUser, storage, count, &count), - let sid = storage.assumingMemoryBound(to: TOKEN_USER.self).pointee.User.Sid, - IsValidSid(sid) - else { - throw WindowsPrivateDirectoryError.native("Read Windows user identity", GetLastError()) - } - var text: LPWSTR? - guard ConvertSidToStringSidW(sid, &text), let text else { - throw WindowsPrivateDirectoryError.native("Encode Windows user identity", GetLastError()) - } - defer { LocalFree(text) } - return String(decodingCString: text, as: UTF16.self) - } - } - } - struct WindowsDirectoryIdentity: Equatable { - private let volume: UInt64 - private let fileID: Data - - init(_ handle: HANDLE) throws { - var information = FILE_ID_INFO() - guard - GetFileInformationByHandleEx( - handle, FileIdInfo, &information, DWORD(MemoryLayout.size)) - else { - throw WindowsPrivateDirectoryError.native("Read directory identity", GetLastError()) - } - volume = information.VolumeSerialNumber - fileID = withUnsafeBytes(of: information.FileId.Identifier) { Data($0) } - } - } -#endif diff --git a/Sources/CodexAdapter/WindowsProcessEnvironment.swift b/Sources/CodexAdapter/WindowsProcessEnvironment.swift deleted file mode 100644 index 32c0360..0000000 --- a/Sources/CodexAdapter/WindowsProcessEnvironment.swift +++ /dev/null @@ -1,54 +0,0 @@ -#if os(Windows) - import Foundation - import WinSDK - - enum WindowsProcessEnvironment { - static func namesMatch(_ lhs: String, _ rhs: String) -> Bool { - compare(lhs, rhs) == CSTR_EQUAL - } - - static func isOrderedBefore(_ lhs: String, _ rhs: String) -> Bool { - compare(lhs, rhs) == CSTR_LESS_THAN - } - - private static func compare(_ lhs: String, _ rhs: String) -> Int32 { - let left = Array(lhs.utf16) - let right = Array(rhs.utf16) - guard !left.isEmpty, !right.isEmpty, left.count <= Int32.max, right.count <= Int32.max else { - return lhs == rhs ? CSTR_EQUAL : (lhs < rhs ? CSTR_LESS_THAN : CSTR_GREATER_THAN) - } - return left.withUnsafeBufferPointer { l in - right.withUnsafeBufferPointer { r in - CompareStringOrdinal(l.baseAddress, Int32(l.count), r.baseAddress, Int32(r.count), true) - } - } - } - - static func merging(_ base: [String: String], overrides: [String: String]) throws -> [String: - String] - { - var result: [String: String] = [:] - for (index, input) in [base, overrides].enumerated() { - var admitted: [String] = [] - for (key, value) in input { - let units = Array(key.utf16) - // Win32 may inherit drive-specific current directories such as =C:. - let inheritedDrive = - index == 0 && units.count == 3 && units[0] == 61 - && ((65...90).contains(units[1]) || (97...122).contains(units[1])) && units[2] == 58 - guard !key.isEmpty, !units.contains(0), !value.utf16.contains(0), - inheritedDrive || !key.contains("="), - !admitted.contains(where: { namesMatch($0, key) }) - else { throw CommandRunnerError.launchFailed("Invalid or ambiguous child environment.") } - admitted.append(key) - if let previous = result.keys.first(where: { namesMatch($0, key) }) { - result.removeValue(forKey: previous) - } - result[key] = value - } - } - if !result.keys.contains(where: { namesMatch($0, "PATH") }) { result["PATH"] = "" } - return result - } - } -#endif diff --git a/Sources/CodexAdapter/WindowsProcessJob.swift b/Sources/CodexAdapter/WindowsProcessJob.swift deleted file mode 100644 index e1b359a..0000000 --- a/Sources/CodexAdapter/WindowsProcessJob.swift +++ /dev/null @@ -1,203 +0,0 @@ -#if os(Windows) - import Foundation - import Synchronization - import WinSDK - - /// Owns only this invocation's descendants. The handle is never inherited or exposed. - final class WindowsProcessJob: Sendable { - enum StopReason { case exited, timedOut, cancelled, failed } - - // Native handles identify kernel objects, not Swift memory. The enclosing - // mutex serializes every state mutation, borrowed admission and handle close. - private struct State: @unchecked Sendable { - let handle: HANDLE - var assigned = false - var reason: StopReason? - var failure: CommandRunnerError? - var members: [HANDLE] = [] - } - - private let state: Mutex - - init() throws { - guard let handle = CreateJobObjectW(nil, nil) else { - throw Self.error("CreateJobObjectW") - } - var limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION() - limits.BasicLimitInformation.LimitFlags = DWORD(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) - guard - SetInformationJobObject( - handle, JobObjectExtendedLimitInformation, &limits, - DWORD(MemoryLayout.size(ofValue: limits))) - else { - let error = Self.error("SetInformationJobObject") - CloseHandle(handle) - throw error - } - state = Mutex(State(handle: handle)) - } - - deinit { - state.withLock { - for member in $0.members { CloseHandle(member) } - CloseHandle($0.handle) - } - } - - var timedOut: Bool { state.withLock { $0.reason == .timedOut } } - - /// Called while the initial thread is suspended. The command owns the borrowed handles. - func start(process: HANDLE, thread: HANDLE) { - state.withLock { state in - if state.reason != nil { - Self.terminateSuspended(process, state: &state) - return - } - guard AssignProcessToJobObject(state.handle, process) else { - state.failure = Self.error("AssignProcessToJobObject") - state.reason = .failed - Self.terminateSuspended(process, state: &state) - return - } - state.assigned = true - guard ResumeThread(thread) != DWORD.max else { - state.failure = Self.error("ResumeThread") - Self.stop(.failed, state: &state) - return - } - } - } - - func stop(_ reason: StopReason) { - state.withLock { Self.stop(reason, state: &$0) } - } - - /// A successful termination request alone does not prove that descendants have exited. - func confirmCleanup() async throws { - let deadline = ContinuousClock.now.advanced(by: .seconds(5)) - while true { - let complete = try state.withLock { state in - if let failure = state.failure { throw failure } - var accounting = JOBOBJECT_BASIC_ACCOUNTING_INFORMATION() - guard - QueryInformationJobObject( - state.handle, JobObjectBasicAccountingInformation, &accounting, - DWORD(MemoryLayout.size(ofValue: accounting)), nil) - else { throw Self.error("QueryInformationJobObject") } - var membersExited = true - for member in state.members { - switch WaitForSingleObject(member, 0) { - case DWORD(WAIT_OBJECT_0): break - case DWORD(WAIT_TIMEOUT): membersExited = false - default: throw Self.error("WaitForSingleObject") - } - } - return accounting.ActiveProcesses == 0 && membersExited - } - if complete { return } - guard ContinuousClock.now < deadline else { - throw CommandRunnerError.launchFailed( - "Command descendant cleanup could not be confirmed.") - } - try await Task.sleep(for: .milliseconds(10)) - } - } - - private static func stop(_ reason: StopReason, state: inout State) { - guard state.reason == nil else { return } - state.reason = reason - guard state.assigned else { return } - do { try retainMembers(state: &state) } catch { - state.failure = - (error as? CommandRunnerError) ?? .launchFailed("Cannot observe command descendants.") - } - if !TerminateJobObject(state.handle, 1) { - state.failure = state.failure ?? error("TerminateJobObject") - } - } - - /// Stop admission before enumerating, so a live member cannot create an unobserved child. - private static func retainMembers(state: inout State) throws { - var limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION() - limits.BasicLimitInformation.LimitFlags = DWORD( - JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_ACTIVE_PROCESS) - limits.BasicLimitInformation.ActiveProcessLimit = 1 - guard - SetInformationJobObject( - state.handle, JobObjectExtendedLimitInformation, &limits, - DWORD(MemoryLayout.size(ofValue: limits))) - else { throw error("SetInformationJobObject") } - - var capacity = 16 - for _ in 0..<4 { - // The native structure ends in a variable-length ULONG_PTR process-id array. - let bytes = 2 * MemoryLayout.size + capacity * MemoryLayout.stride - let storage = UnsafeMutableRawPointer.allocate( - byteCount: bytes, alignment: MemoryLayout.alignment) - defer { storage.deallocate() } - storage.initializeMemory(as: UInt8.self, repeating: 0, count: bytes) - let queried = QueryInformationJobObject( - state.handle, JobObjectBasicProcessIdList, storage, DWORD(bytes), nil) - let code = GetLastError() - let assigned = Int(storage.load(as: DWORD.self)) - let count = Int(storage.load(fromByteOffset: MemoryLayout.size, as: DWORD.self)) - if !queried { - guard code == DWORD(ERROR_MORE_DATA), assigned > capacity, assigned <= 131_072 else { - throw WindowsCommandProcess.error("QueryInformationJobObject", code: code) - } - capacity = assigned - continue - } - guard count <= capacity, count == assigned else { - throw CommandRunnerError.launchFailed( - "Command descendant inventory changed during cleanup.") - } - for index in 0...size + index * MemoryLayout.stride, - as: ULONG_PTR.self) - guard let pid = DWORD(exactly: id) else { - throw CommandRunnerError.launchFailed("Invalid native command process identifier.") - } - guard - let member = OpenProcess( - DWORD(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION), false, pid) - else { - let code = GetLastError() - // A process whose object has already disappeared has completed termination. - if code == DWORD(ERROR_INVALID_PARAMETER) { continue } - throw WindowsCommandProcess.error("OpenProcess", code: code) - } - var belongs: WindowsBool = false - guard IsProcessInJob(member, state.handle, &belongs) else { - let failure = error("IsProcessInJob") - CloseHandle(member) - throw failure - } - if !belongs.boolValue { - let exited = WaitForSingleObject(member, 0) == DWORD(WAIT_OBJECT_0) - CloseHandle(member) - if exited { continue } - throw CommandRunnerError.launchFailed( - "Command process ownership changed during cleanup.") - } - state.members.append(member) - } - return - } - throw CommandRunnerError.launchFailed("Command descendant inventory could not be confirmed.") - } - - private static func terminateSuspended(_ process: HANDLE, state: inout State) { - if !TerminateProcess(process, 1), - WaitForSingleObject(process, 0) != DWORD(WAIT_OBJECT_0) - { - state.failure = error("TerminateProcess") - } - } - - private static func error(_ operation: String) -> CommandRunnerError { - .launchFailed("\(operation) failed (Windows error \(GetLastError())).") - } - } -#endif diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 4f8fcff..623097b 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -15,21 +15,6 @@ The upstream Apache 2.0 license and applicable schema notice are included in `ThirdPartyNotices/codex-protocol/`. No vendor Codex executable, credentials, or user configuration is bundled. -Windows archives include the required Swift runtime DLLs alongside the adapter. -`ThirdPartyNotices/windows-runtime/` preserves the original Swift, Foundation, -libdispatch, ICU, string-processing, curl and zlib notices. Its `sources.json` -binds those texts to upstream commits and maps runtime libraries to notices. -`ThirdPartyNotices/sqlite/` retains the original SQLite header with its -public-domain statement and the pinned source identity. The package receipt -separately binds the selected runtime DLLs and compiled SQLite library. - -The Microsoft Visual C++ runtime is a user-installed prerequisite, supplied by -Microsoft's official installer. The plugin archive carries the Swift/open-source -runtime closure and records external Microsoft runtime requirements in its receipt. -The upstream provenance mapping includes Microsoft entries for identification; -those entries are not a bundled-file inventory. See the -[official runtime downloads](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170). - An ad-hoc signature and checksum support local integrity checks. They do not establish official publisher provenance, Developer ID signing, notarization, or permission to publish this package. diff --git a/Tests/CodexAdapterTests/CodexExecutableResolutionTests.swift b/Tests/CodexAdapterTests/CodexExecutableResolutionTests.swift index 824c364..bdf8bcf 100644 --- a/Tests/CodexAdapterTests/CodexExecutableResolutionTests.swift +++ b/Tests/CodexAdapterTests/CodexExecutableResolutionTests.swift @@ -3,10 +3,6 @@ import Testing @testable import CodexAdapter -#if os(Windows) - import WinSDK -#endif - struct CodexExecutableResolutionTests { @Test(arguments: ["absolute", "relative", "path", "relative-path"]) func resolvesTheConfiguredNameInLaunchContext(form: String) throws { @@ -50,73 +46,16 @@ struct CodexExecutableResolutionTests { defer { try? FileManager.default.removeItem(at: root) } let executable = try installExecutable(in: root, name: "custom-codex") let configuration = CodexConfig(executable: "custom-codex") - #if os(Windows) - #expect(FileManager.default.fileExists(atPath: executable.path)) - #expect(throws: ConfigurationError.self) { - try configuration.resolvedExecutableURL(workspaceURL: root, environment: ["Path": ""]) - } - #else - let resolved = try configuration.resolvedExecutableURL( - workspaceURL: root, environment: ["PATH": ""]) - #expect(resolved == executable.standardizedFileURL) - #endif + let resolved = try configuration.resolvedExecutableURL( + workspaceURL: root, environment: ["PATH": ""]) + #expect(resolved == executable.standardizedFileURL) } - #if os(Windows) - @Test - func nativePathSpellingAndQuotedEntriesUseTheLaunchWorkspace() throws { - let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - let bin = root.appendingPathComponent("tools 雪 with space") - try FileManager.default.createDirectory(at: bin, withIntermediateDirectories: true) - defer { try? FileManager.default.removeItem(at: root) } - let executable = try installExecutable(in: bin, name: "custom-codex") - let byPath = try CodexConfig(executable: "custom-codex").resolvedExecutableURL( - workspaceURL: root, environment: ["pAtH": "missing;\"tools 雪 with space\";;"]) - let byRelative = try CodexConfig(executable: "tools 雪 with space\\custom-codex.exe") - .resolvedExecutableURL(workspaceURL: root, environment: [:]) - #expect(byPath == executable.standardizedFileURL) - #expect(byRelative == executable.standardizedFileURL) - } - - @Test - func ambiguousEnvironmentAndDriveRelativeNamesFailWithoutShellDiscovery() throws { - let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) - defer { try? FileManager.default.removeItem(at: root) } - _ = try installExecutable(in: root, name: "custom-codex") - #expect(throws: ConfigurationError.self) { - try CodexConfig(executable: "custom-codex").resolvedExecutableURL( - workspaceURL: root, environment: ["PATH": root.path, "Path": root.path]) - } - #expect(throws: ConfigurationError.self) { - try CodexConfig(executable: "C:custom-codex.exe").resolvedExecutableURL( - workspaceURL: root, environment: [:]) - } - try Data("@exit /b 0\r\n".utf8).write(to: root.appendingPathComponent("script-only.cmd")) - #expect(throws: ConfigurationError.self) { - try CodexConfig(executable: "script-only").resolvedExecutableURL( - workspaceURL: root, environment: ["Path": root.path, "PATHEXT": ".CMD"]) - } - } - #endif - private func installExecutable(in directory: URL, name: String) throws -> URL { - #if os(Windows) - var system = [WCHAR](repeating: 0, count: 32_768) - let length = GetSystemDirectoryW(&system, UINT(system.count)) - try #require(length > 0 && length < system.count) - let source = URL( - fileURLWithPath: String(decoding: system.prefix(Int(length)), as: UTF16.self) - ) - .appendingPathComponent("cmd.exe") - let executable = directory.appendingPathComponent(name + ".exe") - try FileManager.default.copyItem(at: source, to: executable) - #else - let executable = directory.appendingPathComponent(name) - try Data("#!/bin/sh\nexit 0\n".utf8).write(to: executable) - try FileManager.default.setAttributes( - [.posixPermissions: 0o700], ofItemAtPath: executable.path) - #endif + let executable = directory.appendingPathComponent(name) + try Data("#!/bin/sh\nexit 0\n".utf8).write(to: executable) + try FileManager.default.setAttributes( + [.posixPermissions: 0o700], ofItemAtPath: executable.path) return executable } } diff --git a/Tests/CodexAdapterTests/CodexProcessEnvironmentTests.swift b/Tests/CodexAdapterTests/CodexProcessEnvironmentTests.swift index 4ff5e75..52851e1 100644 --- a/Tests/CodexAdapterTests/CodexProcessEnvironmentTests.swift +++ b/Tests/CodexAdapterTests/CodexProcessEnvironmentTests.swift @@ -47,24 +47,13 @@ final class CodexProcessEnvironmentTests { #expect(environment["PATH"] == "/usr/bin") #expect(environment["HTTP_PROXY"] == "http://127.0.0.1:6152") - #if !os(Windows) - #expect(environment["http_proxy"] == "http://127.0.0.1:6152") - #endif + #expect(environment["http_proxy"] == "http://127.0.0.1:6152") #expect(environment["HTTPS_PROXY"] == "http://127.0.0.1:6152") - #if !os(Windows) - #expect(environment["https_proxy"] == "http://127.0.0.1:6152") - #endif + #expect(environment["https_proxy"] == "http://127.0.0.1:6152") #expect(environment["ALL_PROXY"] == "socks5://127.0.0.1:6153") - #if !os(Windows) - #expect(environment["all_proxy"] == "socks5://127.0.0.1:6153") - #endif + #expect(environment["all_proxy"] == "socks5://127.0.0.1:6153") #expect(environment["NO_PROXY"] == "localhost,127.0.0.1,::1,*.local") - #if !os(Windows) - #expect(environment["no_proxy"] == "localhost,127.0.0.1,::1,*.local") - #else - #expect( - environment.keys.sorted() == ["ALL_PROXY", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "PATH"]) - #endif + #expect(environment["no_proxy"] == "localhost,127.0.0.1,::1,*.local") } @Test @@ -84,42 +73,14 @@ final class CodexProcessEnvironmentTests { #expect(environment["HTTP_PROXY"] == nil) #expect(environment["http_proxy"] == nil) - #if os(Windows) - #expect(environment["HTTPS_PROXY"] == nil) - #else - #expect(environment["HTTPS_PROXY"] == "http://inherited.example:8080") - #endif + #expect(environment["HTTPS_PROXY"] == "http://inherited.example:8080") #expect(environment["https_proxy"] == "http://inherited.example:8080") #expect(environment["ALL_PROXY"] == nil) #expect(environment["all_proxy"] == nil) #expect(environment["NO_PROXY"] == "internal.example") - #if os(Windows) - #expect(environment["no_proxy"] == nil) - #else - #expect(environment["no_proxy"] == "internal.example") - #endif + #expect(environment["no_proxy"] == "internal.example") } - #if os(Windows) - @Test - func testNativeCaseAliasesDoNotLeakParentAuthorityOrDuplicateProxies() { - let base = [ - "Computer_Mcp_Host_Context": "parent-context", "Computer_Mcp_Host_Fd": "123", - "Codex_Thread_Id": "parent-thread", "Codex_Permission_Profile": "parent-profile", - "Codex_Home": "C:\\Codex", "hTtPs_PrOxY": "http://proxy.example:8080", - "No_PrOxY": "internal.example", "CODEX_THREAD_ID\0suffix": "invalid-key", - ] - let environment = CodexProcessEnvironment.resolved( - base: base, - systemProxy: SystemNetworkProxySettings(httpsProxy: "http://system.example:9000")) - #expect( - environment == [ - "Codex_Home": "C:\\Codex", "hTtPs_PrOxY": "http://proxy.example:8080", - "No_PrOxY": "internal.example", "CODEX_THREAD_ID\0suffix": "invalid-key", - ]) - } - #endif - @Test func testNoProxyConfigurationLeavesTheBaseEnvironmentUnchanged() { let base = ["PATH": "/usr/bin", "LANG": "en_US.UTF-8"] diff --git a/Tests/WindowsAdapter/ProtocolCheck.py b/Tests/WindowsAdapter/ProtocolCheck.py deleted file mode 100644 index 28d04c5..0000000 --- a/Tests/WindowsAdapter/ProtocolCheck.py +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env python3 -"""Exercise a linked adapter and real Codex protocol without model authentication.""" - -import argparse -import ctypes -import hashlib -import importlib.util -import json -import os -from pathlib import Path -import shutil -import stat -import subprocess -import tempfile -import uuid - - -repository = Path(__file__).resolve().parents[2] -spec = importlib.util.spec_from_file_location("workflow_check", repository / "Scripts/check-workflow.py") -workflow = importlib.util.module_from_spec(spec) -spec.loader.exec_module(workflow) -runtime_spec = importlib.util.spec_from_file_location("windows_runtime", repository / "Scripts/windows_runtime.py") -runtime = importlib.util.module_from_spec(runtime_spec) -runtime_spec.loader.exec_module(runtime) - - -class ProcessObservation: - """Retain the native child identity before asking its owner to stop it.""" - - def __init__(self, pid): - self.pid = pid - self.handle = None - if os.name == "nt": - from ctypes import wintypes - self.kernel = ctypes.WinDLL("kernel32", use_last_error=True) - self.kernel.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] - self.kernel.OpenProcess.restype = wintypes.HANDLE - self.kernel.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD] - self.kernel.WaitForSingleObject.restype = wintypes.DWORD - self.kernel.CloseHandle.argtypes = [wintypes.HANDLE] - self.kernel.CloseHandle.restype = wintypes.BOOL - self.handle = self.kernel.OpenProcess(0x00100000, False, pid) # SYNCHRONIZE - if not self.handle: - raise ctypes.WinError(ctypes.get_last_error()) - - def require_exited(self): - if self.handle: - assert self.kernel.WaitForSingleObject(self.handle, 0) == 0, "Owned child has not exited" - else: - try: - os.kill(self.pid, 0) - except ProcessLookupError: - return - raise AssertionError("Owned child has not exited") - - def close(self): - if self.handle: - self.kernel.CloseHandle(self.handle) - self.handle = None - - -def remove_private_state(root): - """Git marks immutable object files read-only on Windows, even in a private home.""" - repaired = 0 - - def remove_read_only(function, path, failure): - nonlocal repaired - error = failure[1] - mode = os.lstat(path).st_mode - if (not isinstance(error, PermissionError) or function is not os.unlink - or not stat.S_ISREG(mode) or mode & stat.S_IWRITE): - raise error - os.chmod(path, mode | stat.S_IWRITE) - function(path) - repaired += 1 - - shutil.rmtree(root, onerror=remove_read_only) - return repaired - - -def run(adapter, codex, evidence_directory=None, app_local_runtime=False, package_receipt=None): - if app_local_runtime and os.name != "nt": - raise ValueError("App-local runtime acceptance requires native Windows") - if evidence_directory is not None: - evidence_directory.mkdir(parents=True, exist_ok=False) - root = Path(tempfile.mkdtemp(prefix="native-adapter-汉字-")).resolve() - workspace = root / "workspace" - state = root / "codex" - home = root / "home" - for directory in [workspace, state, home, home / "AppData/Roaming", home / "AppData/Local"]: - directory.mkdir(parents=True, mode=0o700, exist_ok=True) - # Only system/toolchain lookup inputs are inherited; credentials and user homes are not. - environment = {"PATH": os.environ.get("PATH", ""), "HOME": str(home), - "USERPROFILE": str(home), "APPDATA": str(home / "AppData/Roaming"), - "LOCALAPPDATA": str(home / "AppData/Local"), "CODEX_HOME": str(state), - "TEMP": str(root), "TMP": str(root)} - for key in ["SystemRoot", "WINDIR", "COMSPEC"]: - if key in os.environ: - environment[key] = os.environ[key] - if app_local_runtime: - system_directory = Path(os.environ["SystemRoot"]) / "System32" - environment["PATH"] = os.pathsep.join([str(system_directory), os.environ["SystemRoot"]]) - (state / "config.toml").write_text('cli_auth_credentials_store = "file"\n', encoding="utf-8") - context = {"formatVersion": 1, "runtimeID": str(uuid.uuid4()), "caller": "local-mcp", - "profileID": "local-admin", "principalID": "native-protocol-check", - "workspace": {"id": "native-protocol-check", "rootPath": str(workspace)}} - environment["COMPUTER_MCP_HOST_CONTEXT"] = json.dumps(context) - config = root / "adapter.json" - config.write_text(json.dumps({"enabled": True, "executable": str(codex), - "app_server_enabled": True, "exec_enabled": True, - "sandbox": "read-only", "approval_policy": "untrusted"}), encoding="utf-8") - arguments = [str(adapter), "--config", str(config), "--state-directory", str(root / "adapter-state")] - receipt = {"evidence_class": "native-standard-mcp-protocol", "real_model_verified": False, - "model_turns_started": 0, "production_host_used": False, "inherited_credentials": False, - "runtime_environment": "selected-toolchain", "clean_machine_relocation_verified": False, - "adapter_sha256": hashlib.sha256(adapter.read_bytes()).hexdigest(), - "codex_sha256": hashlib.sha256(codex.read_bytes()).hexdigest(), "connections": []} - if app_local_runtime: - receipt["runtime_environment"] = "app-local-with-system-only-path" - receipt["child_path"] = environment["PATH"] - receipt["module_observations"] = [] - process = None - observations = [] - confirmed = False - phase = "executable versions" - try: - prerequisites = [] - if app_local_runtime: - phase = "Microsoft runtime prerequisites" - if package_receipt is None: - raise ValueError("App-local acceptance requires the verified package receipt") - package = json.loads(package_receipt.read_text(encoding="utf-8")) - if package["files"]["bin/codex-mcp-adapter.exe"] != receipt["adapter_sha256"]: - raise ValueError("Runtime prerequisite receipt does not identify this adapter") - prerequisites = runtime.verify_prerequisites( - adapter, system_directory, package["windows_runtime"]["external_prerequisites"]) - receipt["external_prerequisites"] = prerequisites - phase = "executable versions" - for executable, key in [(adapter, "adapter_version"), (codex, "codex_version")]: - receipt[key] = subprocess.run([str(executable), "--version"], env=environment, - capture_output=True, text=True, check=True, timeout=10).stdout.strip() - with (root / "adapter-stderr.log").open("w", encoding="utf-8") as stderr: - for attempt in range(2): - process = subprocess.Popen(arguments, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=stderr, text=True, encoding="utf-8", cwd=workspace, env=environment) - client = workflow.MCPClient(process) - phase = f"connection {attempt + 1} initialize" - initialized = client.request("initialize", {"protocolVersion": "2025-11-25", "capabilities": {}, - "clientInfo": {"name": "native-adapter-protocol-check", "version": "1"}}) - client.send({"method": "notifications/initialized", "params": {}}) - client.request("ping", {}) - phase = f"connection {attempt + 1} catalog" - catalog = client.request("tools/list", {})["tools"] - names = {tool["name"] for tool in catalog} - assert len(names) == len(catalog), "Duplicate tool names" - assert {"codex.app.thread.start", "codex.app.runtime.stop", "codex.exec.start", - "codex.exec.cancel", "codex.diagnostics.snapshot"} <= names, names - assert client.work() == [], "Discovery created native work" - diagnostic = client.request("tools/call", {"name": "codex.diagnostics.snapshot", "arguments": {"limit": 10}}) - assert diagnostic.get("isError") is False, diagnostic - result = diagnostic["structuredContent"]["result"] - assert json.loads(diagnostic["content"][0]["text"]) == result, diagnostic - assert result["persistence_available"] is True, result - assert result["host_diagnostics_available"] is False, result - assert client.call("thread.loaded.list")["data"] == [] - phase = f"connection {attempt + 1} native lifecycle" - if app_local_runtime: - receipt["module_observations"].append(runtime.verify_app_local_modules( - runtime.loaded_modules(process.pid), adapter, system_directory, prerequisites)) - started = client.call("thread.start") - thread_id = started["thread"]["id"] - origin = client.last_invocation - assert thread_id in client.call("thread.loaded.list")["data"] - rows = client.work() - threads = [row for row in rows if row["kind"] == "codex.app.thread"] - assert len(threads) == 1 and threads[0]["handles"]["thread_id"] == thread_id, rows - assert threads[0]["acquired_by"] == origin, rows - status = client.call("status") - native = status["process"] - assert native["state"] == "running" and native["parent_process_id"] == process.pid, status - owned = [native["process_id"]] - if native.get("supervisor_process_id") is not None: - owned.append(native["supervisor_process_id"]) - for pid in owned: - assert isinstance(pid, int) and pid > 1 and pid != process.pid, native - observations.append(ProcessObservation(pid)) - stopped = client.call("runtime.stop") - assert stopped["runtime_state"] == "stopped" and stopped["process"]["state"] == "stopped", stopped - if os.name == "nt": - assert stopped["process"]["cleanup_confirmed"] is True, stopped - for observation in observations: - observation.require_exited() - observation.close() - observations.clear() - assert client.work() == [], "Joined cleanup retained native work" - assert client.call("runtime.stop")["runtime_state"] == "stopped" - process.stdin.close() - assert process.wait(timeout=10) == 0 - client.reader.join(timeout=2) - assert not client.reader.is_alive(), "Protocol reader did not join EOF" - receipt["connections"].append({"protocol": initialized["protocolVersion"], - "tool_count": len(catalog), "thread_id": thread_id, "native_processes": owned, - "owned_stop": stopped["process"], "adapter_exit_code": process.returncode}) - confirmed = True - if app_local_runtime: - receipt["app_local_runtime_verified"] = True - except Exception as error: - receipt["failure_phase"] = phase - receipt["error"] = str(error)[:4096] - raise RuntimeError(f"Native protocol check failed during {phase}; evidence retained at {root}: {error}") from error - finally: - if process and process.poll() is None: - if not process.stdin.closed: - process.stdin.close() - try: - process.wait(timeout=10) - except subprocess.TimeoutExpired: - process.kill() - process.wait(timeout=5) - for observation in observations: - observation.close() - receipt["success"] = confirmed - receipt["protocol_success"] = confirmed - receipt["adapter_exit_code"] = process.poll() if process else None - stderr_path = root / "adapter-stderr.log" - if stderr_path.exists(): - with stderr_path.open("rb") as handle: - stderr_bytes = handle.read(16385) - receipt["stderr"] = stderr_bytes[:16384].decode("utf-8", errors="replace") - receipt["stderr_truncated"] = len(stderr_bytes) > 16384 - cleanup_error = None - if confirmed: - try: - receipt["read_only_files_removed"] = remove_private_state(root) - receipt["private_state_removed"] = True - except Exception as error: - cleanup_error = error - receipt["success"] = False - receipt["private_state_removed"] = False - receipt["failure_phase"] = "private-state cleanup" - receipt["error"] = str(error)[:4096] - if evidence_directory is not None: - (evidence_directory / "receipt.json").write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf-8") - if not receipt["success"]: - print(json.dumps(receipt, indent=2), flush=True) - if cleanup_error is not None: - raise RuntimeError(f"Native protocol passed but private-state cleanup failed at {root}") from cleanup_error - return receipt - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--adapter", type=Path, required=True) - parser.add_argument("--codex", type=Path, required=True) - parser.add_argument("--evidence-directory", type=Path) - parser.add_argument("--app-local-runtime", action="store_true") - parser.add_argument("--package-receipt", type=Path) - options = parser.parse_args() - for executable in [options.adapter, options.codex]: - if not executable.is_absolute() or not executable.is_file(): - parser.error("Executables must be existing absolute file paths") - print(json.dumps(run(options.adapter, options.codex, options.evidence_directory, - options.app_local_runtime, options.package_receipt), indent=2)) diff --git a/Tests/WindowsAdapter/README.md b/Tests/WindowsAdapter/README.md deleted file mode 100644 index 8b5bf72..0000000 --- a/Tests/WindowsAdapter/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Linked native adapter acceptance - -The complete Windows candidate audit links `codex-mcp-adapter.exe`, copies the -executable and actual SwiftPM resource directories to a new path, records their -hashes, and runs help, version and the standard MCP protocol check. The native -Codex release archive is pinned by checksum and used only in the disposable -candidate; it is neither installed nor included in the plugin payload. - -The audit uses the adapter's shipping dependency graph. The requested SDK -revision must equal the lock; resolved SDK/MCP checkout identities and the -unchanged shipping lock are verified before building. No editable dependency -override is part of this acceptance. - -`ProtocolCheck.py` uses the existing workflow's standard MCP client with two -successive adapter connections, isolated homes and no inherited credentials. -It verifies catalog/resource discovery, database availability, real App Server -thread creation, creator correlation, process ownership, idempotent runtime stop, -joined native exit, work retirement and MCP EOF. On Windows, a synchronization -handle retains each observed process identity through shutdown. It does not -start model turns or establish authenticated-model acceptance. - -The selected Swift toolchain supplies runtime DLL lookup through PATH. This -check is independent of the release archive and clean-machine runtime contract; -it does not establish that an artifact contains all redistributable libraries. -The same protocol check can run on macOS against existing candidate executables -to validate its platform-neutral assertions. - -The `artifact` validation scope accepts a completed source-audit run and exact -SDK revision. It verifies that run belongs to this repository and checks every -linked payload file against the source inventory before executing the same bytes -in a fresh runner. The receipt binds the binary/source revision separately from -the check-definition revision. This supports runtime diagnosis and acceptance -without rebuilding unchanged source; it never promotes a failed source run to -a successful release. Failure evidence includes the phase, adapter exit status -and bounded stderr. Private databases and credentials are not uploaded. diff --git a/Tests/WindowsAdapter/codex-binary.json b/Tests/WindowsAdapter/codex-binary.json deleted file mode 100644 index a1e9d90..0000000 --- a/Tests/WindowsAdapter/codex-binary.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": "0.154.0", - "archiveURL": "https://github.com/openai/codex/releases/download/rust-v0.154.0/codex-x86_64-pc-windows-msvc.exe.zip", - "archiveSHA256": "53685f9f6bd171d4bd7d6c2be724fc04f6737a4001eb8471ec59824e5adc8042", - "executable": "codex-x86_64-pc-windows-msvc.exe", - "executableSHA256": "be96b992178b1e467c225800da0d65f2c86d5eba1ef0b14632f65db381cbdfde" -} diff --git a/Tests/WindowsHostMCP/HostPipeFixture.swift b/Tests/WindowsHostMCP/HostPipeFixture.swift deleted file mode 100644 index 9fafe08..0000000 --- a/Tests/WindowsHostMCP/HostPipeFixture.swift +++ /dev/null @@ -1,52 +0,0 @@ -#if os(Windows) - import Foundation - import MCP - import WinSDK - - @main - struct HostPipeFixture { - static func main() async { - do { try await run() } catch { ExitProcess(10) } - } - - static func run() async throws { - let environment = ProcessInfo.processInfo.environment - guard let endpoint = try MCPInheritedPipeEndpoint.inherited(environment: environment) else { - ExitProcess(11) - } - // If this unrelated inheritable event escaped the host's explicit list, - // setting it is observable in the parent irrespective of handle-number reuse. - if let text = environment["FIXTURE_EXCLUDED_EVENT"], let value = UInt(text), - let handle = HANDLE(bitPattern: value) - { - _ = SetEvent(handle) - } - let transport = try MCPInheritedPipeTransport(takingOwnershipOf: endpoint) - var flags: DWORD = 0 - guard try endpoint.input.withHandle({ GetHandleInformation($0, &flags) }), - flags & DWORD(HANDLE_FLAG_INHERIT) == 0, - try endpoint.output.withHandle({ GetHandleInformation($0, &flags) }), - flags & DWORD(HANDLE_FLAG_INHERIT) == 0 - else { ExitProcess(12) } - let client = Client(name: "independent-plugin", version: "1") - do { - let initialized = try await client.connect(transport: transport) - guard initialized.serverInfo.name == "independent-host" else { ExitProcess(13) } - let request: RequestContext = try await client.callTool( - name: "echo", - arguments: ["value": .int(Int.max), "pid": .int(Int(GetCurrentProcessId()))]) - guard try await request.value.structuredContent?.objectValue?["echo"] == .int(Int.max) - else { ExitProcess(14) } - guard try FileHandle.standardInput.readToEnd()?.isEmpty != false else { ExitProcess(15) } - try FileHandle.standardOutput.write(contentsOf: Data("fixture-stdout\n".utf8)) - try FileHandle.standardError.write(contentsOf: Data("fixture-stderr\n".utf8)) - } catch { - await client.disconnect() - await transport.disconnect() - throw error - } - await client.disconnect() - await transport.disconnect() - } - } -#endif diff --git a/Tests/WindowsHostMCP/HostPipeTests.swift b/Tests/WindowsHostMCP/HostPipeTests.swift deleted file mode 100644 index 04f477f..0000000 --- a/Tests/WindowsHostMCP/HostPipeTests.swift +++ /dev/null @@ -1,331 +0,0 @@ -#if os(Windows) - import Foundation - import HostProcess - import MCP - import Testing - import WinSDK - - @testable import HostPipe - - @Suite("Native inherited host MCP pipes", .serialized, .timeLimit(.minutes(1))) - struct HostPipeTests { - @Test("Standard MCP callbacks retain exact integer payloads") - func standardMCP() async throws { - let pair = try PipePair() - let serverTransport = try MCPInheritedPipeTransport(takingOwnershipOf: pair.left) - let clientTransport = try MCPInheritedPipeTransport(takingOwnershipOf: pair.right) - let server = Server(name: "host-pipe", version: "1", capabilities: .init(tools: .init())) - await server.withMethodHandler(CallTool.self) { request in - try CallTool.Result( - content: [], - structuredContent: Value.object(["echo": request.arguments?["value"] ?? .null])) - } - let client = Client(name: "owned-plugin", version: "1") - do { - try await server.start(transport: serverTransport) - let initialized = try await client.connect(transport: clientTransport) - #expect(initialized.serverInfo.name == "host-pipe") - let request: RequestContext = try await client.callTool( - name: "echo", arguments: ["value": .int(Int.max)]) - #expect(try await request.value.structuredContent?.objectValue?["echo"] == .int(Int.max)) - } catch { - await client.disconnect() - await server.stop() - await clientTransport.disconnect() - await serverTransport.disconnect() - throw error - } - await client.disconnect() - await server.stop() - await clientTransport.disconnect() - await serverTransport.disconnect() - } - - @Test( - "Independent child uses only inherited callback pipes and keeps standard streams separate") - func independentChild() async throws { - let pair = try PipePair() - let transport = try MCPInheritedPipeTransport(takingOwnershipOf: pair.left) - let child = try NativeHostChild(endpoint: pair.right) - let pid = child.pid - #expect(pid != GetCurrentProcessId()) - let server = Server( - name: "independent-host", version: "1", capabilities: .init(tools: .init())) - await server.withMethodHandler(CallTool.self) { request in - #expect(request.arguments?["pid"] == .int(Int(pid))) - return CallTool.Result( - content: [], structuredContent: .object(["echo": request.arguments?["value"] ?? .null])) - } - do { - try await server.start(transport: transport) - #expect(try await child.wait() == 0) - #expect(child.output(standardError: false) == "fixture-stdout\n") - #expect(child.output(standardError: true) == "fixture-stderr\n") - #expect(!child.excludedEventWasInherited) - } catch { - #expect(child.stop()) - await server.stop() - await transport.disconnect() - throw error - } - await server.stop() - await transport.disconnect() - } - - @Test("Stopping an independent child blocked in initialization releases peer EOF") - func stoppedChildReleasesEndpoint() async throws { - let pair = try PipePair() - let transport = try MCPInheritedPipeTransport(takingOwnershipOf: pair.left) - let child = try NativeHostChild(endpoint: pair.right) - do { - try await transport.connect() - var iterator = await transport.receive().makeAsyncIterator() - let request = try #require(try await iterator.next()) - #expect(String(decoding: request, as: UTF8.self).contains("initialize")) - #expect(child.stop()) - #expect(try await child.wait() != 0) - #expect(try await iterator.next() == nil) - #expect(!child.excludedEventWasInherited) - } catch { - #expect(child.stop()) - await transport.disconnect() - throw error - } - await transport.disconnect() - } - - @Test("Connected transport consumes original endpoints and concurrent close reaches peer EOF") - func endpointOwnership() async throws { - let pair = try PipePair() - let left = try MCPInheritedPipeTransport(takingOwnershipOf: pair.left) - let right = try MCPInheritedPipeTransport(takingOwnershipOf: pair.right) - do { - try await left.connect() - try await right.connect() - #expect(throws: (any Error).self) { try pair.left.output.withHandle { _ in } } - #expect(throws: (any Error).self) { try pair.left.input.withHandle { _ in } } - async let first: Void = left.disconnect() - async let second: Void = left.disconnect() - _ = await (first, second) - var iterator = await right.receive().makeAsyncIterator() - #expect(try await iterator.next() == nil) - await #expect(throws: (any Error).self) { try await left.connect() } - } catch { - await left.disconnect() - await right.disconnect() - throw error - } - await right.disconnect() - } - - @Test("Inherited handles require exact paired provenance and lose inheritance on admission") - func inheritedAdmission() async throws { - let pair = try PipePair() - let input = try HandleLease(duplicating: pair.left.input) - let output = try HandleLease(duplicating: pair.left.output) - let environment = [ - "COMPUTER_MCP_HOST_CONTEXT": "bound-by-host", - MCPInheritedPipeEndpoint.readEnvironmentKey: input.text, - MCPInheritedPipeEndpoint.writeEnvironmentKey: output.text, - ] - let endpoint = try #require(try MCPInheritedPipeEndpoint.inherited(environment: environment)) - input.transfer() - output.transfer() - let transport = try MCPInheritedPipeTransport(takingOwnershipOf: endpoint) - var inputFlags: DWORD = 0 - var outputFlags: DWORD = 0 - #expect(try endpoint.input.withHandle { GetHandleInformation($0, &inputFlags) }) - #expect(try endpoint.output.withHandle { GetHandleInformation($0, &outputFlags) }) - #expect(inputFlags & DWORD(HANDLE_FLAG_INHERIT) == 0) - #expect(outputFlags & DWORD(HANDLE_FLAG_INHERIT) == 0) - await transport.disconnect() - #expect(throws: (any Error).self) { try endpoint.output.withHandle { _ in } } - } - - @Test("Missing, ambiguous, standard-stream and noninherited handles are not consumed") - func rejectedAdmission() throws { - let pair = try PipePair() - #expect(try MCPInheritedPipeEndpoint.inherited(environment: [:]) == nil) - let values = ["", "0", "-1", "01", " 12", "12x", String(UInt.max)] - for value in values { - #expect(throws: (any Error).self) { - try MCPInheritedPipeEndpoint.inherited(environment: [ - "COMPUTER_MCP_HOST_CONTEXT": "bound-by-host", - MCPInheritedPipeEndpoint.readEnvironmentKey: value, - MCPInheritedPipeEndpoint.writeEnvironmentKey: "1", - ]) - } - } - let read = try pair.left.input.withHandle { String(UInt(bitPattern: $0)) } - let write = try pair.left.output.withHandle { String(UInt(bitPattern: $0)) } - let environment = [ - "COMPUTER_MCP_HOST_CONTEXT": "bound-by-host", - MCPInheritedPipeEndpoint.readEnvironmentKey: read, - MCPInheritedPipeEndpoint.writeEnvironmentKey: write, - ] - #expect(throws: (any Error).self) { - try MCPInheritedPipeEndpoint.inherited(environment: environment) - } - var ambiguous = environment - ambiguous[MCPInheritedPipeEndpoint.readEnvironmentKey.lowercased()] = read - #expect(throws: (any Error).self) { - try MCPInheritedPipeEndpoint.inherited(environment: ambiguous) - } - var standard = environment - let standardInput = try #require(GetStdHandle(STD_INPUT_HANDLE)) - standard[MCPInheritedPipeEndpoint.readEnvironmentKey] = String( - UInt(bitPattern: standardInput)) - #expect(throws: (any Error).self) { - try MCPInheritedPipeEndpoint.inherited(environment: standard) - } - var flags: DWORD = 0 - #expect(try pair.left.input.withHandle { GetHandleInformation($0, &flags) }) - #expect(try pair.left.output.withHandle { GetHandleInformation($0, &flags) }) - } - - @Test("A pre-cancelled receiver releases native endpoints and permits peer EOF") - func cancelledReceive() async throws { - let pair = try PipePair() - let left = try MCPInheritedPipeTransport(takingOwnershipOf: pair.left) - let right = try MCPInheritedPipeTransport(takingOwnershipOf: pair.right) - do { - try await left.connect() - try await right.connect() - let stream = await left.receive() - let cancelled = Task { - withUnsafeCurrentTask { $0?.cancel() } - var iterator = stream.makeAsyncIterator() - _ = try? await iterator.next() - } - await cancelled.value - var iterator = await right.receive().makeAsyncIterator() - #expect(try await iterator.next() == nil) - } catch { - await left.disconnect() - await right.disconnect() - throw error - } - await left.disconnect() - await right.disconnect() - } - } - - private struct PipePair: Sendable { - let left: MCPInheritedPipeEndpoint - let right: MCPInheritedPipeEndpoint - init() throws { - func pipe() throws -> (MCPInheritedPipeHandle, MCPInheritedPipeHandle) { - var read: HANDLE? - var write: HANDLE? - guard CreatePipe(&read, &write, nil, 4096), let read, let write else { - throw MCPError.connectionClosed - } - return ( - MCPInheritedPipeHandle(takingOwnershipOf: read), - MCPInheritedPipeHandle(takingOwnershipOf: write) - ) - } - let request = try pipe() - let response = try pipe() - left = MCPInheritedPipeEndpoint(input: request.0, output: response.1) - right = MCPInheritedPipeEndpoint(input: response.0, output: request.1) - } - } - - /// The native fixture owns one Job Object and never borrows the test runner's standard streams. - private final class NativeHostChild { - private let process: OpaquePointer - private let excluded: MCPInheritedPipeHandle - var pid: UInt32 { hmcp_pid(process) } - - init(endpoint: MCPInheritedPipeEndpoint) throws { - defer { - endpoint.input.close() - endpoint.output.close() - } - var security = SECURITY_ATTRIBUTES() - security.nLength = DWORD(MemoryLayout.size) - security.bInheritHandle = true - let event = try #require(CreateEventW(&security, true, false, nil)) - excluded = MCPInheritedPipeHandle(takingOwnershipOf: event) - let environment = ProcessInfo.processInfo.environment - let executable = try #require(environment["HOST_PIPE_FIXTURE_PATH"]) - var values = environment.filter { - ![ - "COMPUTER_MCP_HOST_CONTEXT", "COMPUTER_MCP_HOST_FD", - MCPInheritedPipeEndpoint.readEnvironmentKey, MCPInheritedPipeEndpoint.writeEnvironmentKey, - ] - .contains($0.key.uppercased()) - } - values["COMPUTER_MCP_HOST_CONTEXT"] = "bound-by-fixture-host" - values["FIXTURE_EXCLUDED_EVENT"] = String(UInt(bitPattern: event)) - var failure: UInt32 = 0 - process = try endpoint.input.withHandle { input in - try endpoint.output.withHandle { output in - #expect( - SetHandleInformation(input, DWORD(HANDLE_FLAG_INHERIT), DWORD(HANDLE_FLAG_INHERIT))) - #expect( - SetHandleInformation(output, DWORD(HANDLE_FLAG_INHERIT), DWORD(HANDLE_FLAG_INHERIT))) - values[MCPInheritedPipeEndpoint.readEnvironmentKey] = String(UInt(bitPattern: input)) - values[MCPInheritedPipeEndpoint.writeEnvironmentKey] = String(UInt(bitPattern: output)) - var block = - Array( - values.sorted { $0.key.lowercased() < $1.key.lowercased() } - .map { "\($0.key)=\($0.value)" }.joined(separator: "\0").utf16) + [0, 0] - let launched = executable.withCString(encodedAs: UTF16.self) { path in - hmcp_launch(path, &block, UInt(bitPattern: input), UInt(bitPattern: output), &failure) - } - return try #require(launched, "Native child launch failed: \(failure)") - } - } - } - - var excludedEventWasInherited: Bool { - (try? excluded.withHandle { WaitForSingleObject($0, 0) }) != DWORD(WAIT_TIMEOUT) - } - - func wait() async throws -> UInt32 { - let deadline = ContinuousClock.now.advanced(by: .seconds(10)) - while true { - var exit: UInt32 = 0 - switch hmcp_poll(process, &exit) { - case 1: return exit - case 0: break - default: throw MCPError.internalError("Cannot observe fixture process exit") - } - guard ContinuousClock.now < deadline else { - throw MCPError.internalError("Fixture process exit timed out") - } - try await Task.sleep(for: .milliseconds(10)) - } - } - - func stop() -> Bool { hmcp_stop(process) != 0 } - func output(standardError: Bool) -> String { - var bytes = [UInt8](repeating: 0, count: 1024) - let count = hmcp_output(process, standardError ? 1 : 0, &bytes, bytes.count) - return String(decoding: bytes.prefix(count), as: UTF8.self) - } - deinit { hmcp_destroy(process) } - } - - /// A duplicate is transferred exactly once; failed admission leaves cleanup with the fixture. - private final class HandleLease { - private var owned: HANDLE? - let text: String - init(duplicating file: MCPInheritedPipeHandle) throws { - var copy: HANDLE? - guard - try file.withHandle({ - DuplicateHandle( - GetCurrentProcess(), $0, GetCurrentProcess(), ©, 0, true, - DWORD(DUPLICATE_SAME_ACCESS)) - }), let copy - else { throw MCPError.connectionClosed } - owned = copy - text = String(UInt(bitPattern: copy)) - } - func transfer() { owned = nil } - deinit { if let owned { CloseHandle(owned) } } - } -#endif diff --git a/Tests/WindowsHostMCP/HostProcess/HostProcess.c b/Tests/WindowsHostMCP/HostProcess/HostProcess.c deleted file mode 100644 index 403daa2..0000000 --- a/Tests/WindowsHostMCP/HostProcess/HostProcess.c +++ /dev/null @@ -1,131 +0,0 @@ -#include "HostProcess.h" -#include -#include -#include - -struct HMCPChild { - HANDLE process, job, output, error; - DWORD pid; -}; - -static void close_handle(HANDLE *handle) { - if (*handle) CloseHandle(*handle); - *handle = NULL; -} - -static BOOL pipe_pair(HANDLE *read, HANDLE *write) { - SECURITY_ATTRIBUTES security = { sizeof(security), NULL, TRUE }; - if (!CreatePipe(read, write, &security, 4096)) return FALSE; - return SetHandleInformation(*read, HANDLE_FLAG_INHERIT, 0); -} - -// The fixture host lends precisely two callback endpoints to a suspended child. -// Standard streams are separate. No name, listener, or credential is created. -HMCPChild *hmcp_launch(const wchar_t *executable, wchar_t *environment, - uintptr_t input, uintptr_t output, uint32_t *error) { - HMCPChild *child = calloc(1, sizeof(*child)); - HANDLE stdin_read = NULL, stdin_write = NULL, stdout_write = NULL, stderr_write = NULL; - PROCESS_INFORMATION process = {0}; - STARTUPINFOEXW startup = {0}; - SIZE_T size = 0; - BOOL initialized = FALSE; - wchar_t *command = NULL; - DWORD failure = ERROR_NOT_ENOUGH_MEMORY; - if (!child) goto cleanup; - child->job = CreateJobObjectW(NULL, NULL); - if (!child->job) goto failed; - JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {0}; - limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - if (!SetInformationJobObject(child->job, JobObjectExtendedLimitInformation, - &limits, sizeof(limits))) goto failed; - if (!pipe_pair(&child->output, &stdout_write) || - !pipe_pair(&child->error, &stderr_write) || - !pipe_pair(&stdin_read, &stdin_write)) goto failed; - close_handle(&stdin_write); - if (!SetHandleInformation(stdin_read, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) goto failed; - InitializeProcThreadAttributeList(NULL, 1, 0, &size); - startup.lpAttributeList = malloc(size); - if (!startup.lpAttributeList) goto cleanup; - if (!InitializeProcThreadAttributeList(startup.lpAttributeList, 1, 0, &size)) goto failed; - initialized = TRUE; - HANDLE inherited[] = { stdin_read, stdout_write, stderr_write, (HANDLE)input, (HANDLE)output }; - if (!UpdateProcThreadAttribute(startup.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, - inherited, sizeof(inherited), NULL, NULL)) goto failed; - startup.StartupInfo.cb = sizeof(startup); - startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES; - startup.StartupInfo.hStdInput = stdin_read; - startup.StartupInfo.hStdOutput = stdout_write; - startup.StartupInfo.hStdError = stderr_write; - size_t count = wcslen(executable); - command = calloc(count + 3, sizeof(wchar_t)); - if (!command) goto cleanup; - command[0] = L'"'; - memcpy(command + 1, executable, count * sizeof(wchar_t)); - command[count + 1] = L'"'; - if (!CreateProcessW(executable, command, NULL, NULL, TRUE, - CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, - environment, NULL, &startup.StartupInfo, &process)) goto failed; - child->process = process.hProcess; - child->pid = process.dwProcessId; - if (!AssignProcessToJobObject(child->job, child->process)) goto failed; - if (ResumeThread(process.hThread) == (DWORD)-1) goto failed; - failure = ERROR_SUCCESS; - goto cleanup; -failed: - failure = GetLastError(); -cleanup: - close_handle(&process.hThread); - close_handle(&stdin_read); - close_handle(&stdin_write); - close_handle(&stdout_write); - close_handle(&stderr_write); - if (initialized) DeleteProcThreadAttributeList(startup.lpAttributeList); - free(startup.lpAttributeList); - free(command); - *error = failure; - if (failure != ERROR_SUCCESS) { - if (child && child->process) { - TerminateProcess(child->process, 1); - WaitForSingleObject(child->process, 5000); - } - hmcp_destroy(child); - return NULL; - } - return child; -} - -uint32_t hmcp_pid(HMCPChild *child) { return child->pid; } - -int hmcp_poll(HMCPChild *child, uint32_t *exit_code) { - DWORD state = WaitForSingleObject(child->process, 0); - if (state == WAIT_TIMEOUT) return 0; - DWORD code = 0; - if (state != WAIT_OBJECT_0 || !GetExitCodeProcess(child->process, &code)) return -1; - *exit_code = code; - return 1; -} - -int hmcp_stop(HMCPChild *child) { - if (!child) return 1; - if (!TerminateJobObject(child->job, 1)) return 0; - return WaitForSingleObject(child->process, 5000) == WAIT_OBJECT_0; -} - -size_t hmcp_output(HMCPChild *child, int standard_error, void *bytes, size_t capacity) { - HANDLE pipe = standard_error ? child->error : child->output; - DWORD available = 0, count = 0; - if (!PeekNamedPipe(pipe, NULL, 0, NULL, &available, NULL)) return 0; - DWORD size = available < capacity ? available : (DWORD)capacity; - if (size == 0 || !ReadFile(pipe, bytes, size, &count, NULL)) return 0; - return count; -} - -void hmcp_destroy(HMCPChild *child) { - if (!child) return; - if (child->process) hmcp_stop(child); - close_handle(&child->process); - close_handle(&child->job); - close_handle(&child->output); - close_handle(&child->error); - free(child); -} diff --git a/Tests/WindowsHostMCP/HostProcess/include/HostProcess.h b/Tests/WindowsHostMCP/HostProcess/include/HostProcess.h deleted file mode 100644 index 923fc6a..0000000 --- a/Tests/WindowsHostMCP/HostProcess/include/HostProcess.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include -#include -#include - -typedef struct HMCPChild HMCPChild; -HMCPChild *hmcp_launch(const wchar_t *executable, wchar_t *environment, - uintptr_t input, uintptr_t output, uint32_t *error); -uint32_t hmcp_pid(HMCPChild *child); -int hmcp_poll(HMCPChild *child, uint32_t *exit_code); -int hmcp_stop(HMCPChild *child); -size_t hmcp_output(HMCPChild *child, int standard_error, void *bytes, size_t capacity); -void hmcp_destroy(HMCPChild *child); diff --git a/Tests/WindowsHostMCP/Package.swift.template b/Tests/WindowsHostMCP/Package.swift.template deleted file mode 100644 index dcee405..0000000 --- a/Tests/WindowsHostMCP/Package.swift.template +++ /dev/null @@ -1,28 +0,0 @@ -// swift-tools-version: 6.2 -import PackageDescription - -let package = Package( - name: "WindowsHostMCPValidation", - dependencies: [ - .package(url: "__MCP_URL__", exact: "__MCP_VERSION__"), - .package(url: "https://github.com/apple/swift-system.git", exact: "1.8.1"), - .package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"), - ], - targets: [ - .target( - name: "HostPipe", - dependencies: [ - .product(name: "MCP", package: "swift-sdk"), - .product(name: "SystemPackage", package: "swift-system"), - .product(name: "Logging", package: "swift-log"), - ]), - .executableTarget( - name: "HostPipeFixture", - dependencies: [ - .product(name: "MCP", package: "swift-sdk"), - .product(name: "SystemPackage", package: "swift-system"), - .product(name: "Logging", package: "swift-log"), - ]), - .target(name: "HostProcess"), - .testTarget(name: "HostPipeTests", dependencies: ["HostPipe", "HostProcess"]), - ]) diff --git a/Tests/WindowsHostMCP/README.md b/Tests/WindowsHostMCP/README.md deleted file mode 100644 index e92da9b..0000000 --- a/Tests/WindowsHostMCP/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Native host MCP transport validation - -`Scripts/test-windows-host-mcp.ps1` copies the exact production inherited-pipe -transport, records its source hash, and resolves the versioned MCP dependency -from the shipping lock. Every selected dependency must match the shipping -location, version and revision. Swift 6.2.3 Windows debug/release tests -exercise actual anonymous pipes, standard MCP calls and exact integers, -endpoint ownership transfer, concurrent close/EOF, inherited-handle admission, -rejected environment/handle inputs and pre-cancelled receive cleanup. - -An independent native child consumes the same production transport. The fixture -host launches it suspended, assigns an owned Job Object, and passes exactly the -paired callback handles plus separate standard streams through -`PROC_THREAD_ATTRIBUTE_HANDLE_LIST`. Tests verify the native child PID, exact -integer callback, standard-stream isolation, exclusion of another inheritable -event, and peer EOF after terminating a child blocked in initialization. The -fixture host closes its child-side copies immediately after launch. - -These tests exercise native launch and transport ownership. They do not claim -a complete adapter artifact, a Windows Computer MCP host application, or -authenticated model execution. The process suite independently checks that -callback metadata is stripped from vendor environments. - -Run with an unchanged SDK checkout matching the shipping lock and a fresh output directory: - -```powershell -./Scripts/test-windows-host-mcp.ps1 -SDKPath -OutputDirectory -``` - -The validation workflow accepts `windows_validation_scope: host-mcp` with an -exact locked SDK revision to run this gate independently. Its default `all` -scope retains the complete source, database, process and transport checks. diff --git a/Tests/WindowsProcess/CommandRunnerTests.swift b/Tests/WindowsProcess/CommandRunnerTests.swift deleted file mode 100644 index e73a202..0000000 --- a/Tests/WindowsProcess/CommandRunnerTests.swift +++ /dev/null @@ -1,215 +0,0 @@ -import Foundation -import Testing -import WinSDK - -@testable import ManagedProcess - -@Suite("Windows adapter finite commands", .timeLimit(.minutes(1))) -struct CommandRunnerTests { - @Test( - "Native argv, Unicode cwd, overridden environment and stdin EOF survive synchronous execution") - func launchContext() async throws { - let directory = try temporaryDirectory() - defer { try? FileManager.default.removeItem(at: directory) } - let arguments = ["", "a b", "汉字 🐈", "quote\"inside", "trailing slash \\", "&|<>%"] - let executable = try fixture() - let runner = ProcessCommandRunner( - environment: environment(mode: "echo").merging(["Value": "base"]) { _, new in new }) - let result = try await Task.detached { - try runner.runData( - executable: executable.path, arguments: arguments, workingDirectory: directory, - environment: ["VALUE": "覆盖"], timeoutMilliseconds: 10_000, maxOutputBytes: 16_384) - }.value - let value = try JSONDecoder().decode(JSONValue.self, from: result.stdout) - #expect(value.objectValue?["arguments"] == .array(arguments.map(JSONValue.string))) - let reportedDirectory = try #require(value.objectValue?["cwd"]?.stringValue) - #expect(try directoryIdentity(reportedDirectory) == directoryIdentity(directory.path)) - #expect(value.objectValue?["value"] == .string("覆盖")) - #expect(value.objectValue?["input"] == .string("")) - #expect(result.exitCode == 0 && !result.timedOut) - #expect(!result.stdoutTruncated && !result.stderrTruncated) - } - - @Test("Both output pipes drain beyond their retained prefixes") - func boundedOutput() async throws { - let result = try await ProcessCommandRunner(environment: environment(mode: "output")) - .runDataAsync( - executable: fixture().path, arguments: [], workingDirectory: nil, environment: [:], - timeoutMilliseconds: 10_000, maxOutputBytes: 64) - #expect(result.stdout == Data(("kept\n" + String(repeating: "x", count: 59)).utf8)) - #expect(result.stderr == Data(repeating: 121, count: 64)) - #expect(result.stdoutTruncated && result.stderrTruncated) - #expect(result.exitCode == 0 && !result.timedOut) - } - - enum End: String, CaseIterable { case natural, timeout, cancellation } - - @Test( - "Every completion route joins descendants and preserves an independent command", - arguments: End.allCases) - func treeCleanup(_ end: End) async throws { - let directory = try temporaryDirectory() - let siblingDirectory = try temporaryDirectory() - defer { - try? FileManager.default.removeItem(at: directory) - try? FileManager.default.removeItem(at: siblingDirectory) - } - let executable = try fixture().path - let runner = ProcessCommandRunner( - environment: environment(mode: end == .natural ? "tree-exit" : "tree", directory: directory)) - let siblingRunner = ProcessCommandRunner( - environment: environment(mode: "tree", directory: siblingDirectory)) - let command = Task { - try await runner.runDataAsync( - executable: executable, arguments: [], workingDirectory: directory, - environment: [:], timeoutMilliseconds: end == .timeout ? 5_000 : 20_000, maxOutputBytes: 64) - } - let sibling = Task { - try await siblingRunner.runDataAsync( - executable: executable, arguments: [], workingDirectory: siblingDirectory, - environment: [:], timeoutMilliseconds: 20_000, maxOutputBytes: 64) - } - do { - let members = try await observeTree(directory) - let others = try await observeTree(siblingDirectory) - if end == .natural { try Data().write(to: directory.appendingPathComponent("release")) } - if end == .cancellation { command.cancel() } - switch await command.result { - case .success(let result): - #expect(end != .cancellation) - #expect(result.timedOut == (end == .timeout)) - #expect(result.stdout == Data("ready\n".utf8)) - if end == .natural { #expect(result.exitCode == 0) } - case .failure(let error): - #expect(end == .cancellation && error is CancellationError) - } - #expect(members.allSatisfy { $0.hasExited }) - #expect(others.allSatisfy { !$0.hasExited }) - sibling.cancel() - await #expect(throws: CancellationError.self) { try await sibling.value } - #expect(others.allSatisfy { $0.hasExited }) - } catch { - command.cancel() - sibling.cancel() - _ = await command.result - _ = await sibling.result - throw error - } - } - - @Test("Codex and finite commands share explicit Windows PATH discovery") - func discovery() throws { - let directory = try temporaryDirectory() - defer { try? FileManager.default.removeItem(at: directory) } - let binaryDirectory = directory.appendingPathComponent("命令 tools") - try FileManager.default.createDirectory(at: binaryDirectory, withIntermediateDirectories: false) - let executable = binaryDirectory.appendingPathComponent("fixture.exe") - try FileManager.default.copyItem(at: fixture(), to: executable) - let environment = ["Path": "\"命令 tools\";;", "PATHEXT": ".cmd"] - let native = try WindowsExecutable.resolve( - "fixture", workspace: directory, environment: environment) - let configured = try CodexConfig(executable: "fixture").resolvedExecutableURL( - workspaceURL: directory, environment: environment) - #expect(native == configured && native == executable) - for invalid in ["C:fixture.exe", "\\fixture.exe", "NUL", "\\\\.\\pipe\\fixture"] { - #expect(throws: CommandRunnerError.self) { - try WindowsExecutable.resolve(invalid, workspace: directory, environment: environment) - } - } - #expect(throws: CommandRunnerError.self) { - try WindowsExecutable.resolve( - "fixture", workspace: directory, - environment: ["Path": binaryDirectory.path, "PATH": binaryDirectory.path]) - } - } - - @Test("Environment aliases and invalid execution limits fail before child launch") - func admission() async throws { - let merged = try WindowsProcessEnvironment.merging( - ["Path": "base", "=C:": "C:\\base"], overrides: ["PATH": "child"]) - #expect(merged == ["PATH": "child", "=C:": "C:\\base"]) - #expect(WindowsProcessEnvironment.namesMatch("path", "PATH")) - #expect(!WindowsProcessEnvironment.namesMatch("PATH\0extra", "PATH")) - for invalid in [["Path": "a", "PATH": "b"], ["=C:": "C:\\bad"], ["KEY": "a\0b"]] { - #expect(throws: CommandRunnerError.self) { - try WindowsProcessEnvironment.merging([:], overrides: invalid) - } - } - for (deadline, limit) in [(0, 64), (10_000, 0), (3_600_001, 64)] { - await #expect(throws: CommandRunnerError.self) { - try await ProcessCommandRunner().runDataAsync( - executable: "missing", arguments: [], - workingDirectory: nil, environment: [:], timeoutMilliseconds: deadline, - maxOutputBytes: limit) - } - } - } - - private func fixture() throws -> URL { - URL(fileURLWithPath: try #require(ProcessInfo.processInfo.environment["CODEX_WINDOWS_FIXTURE"])) - } - - private func environment(mode: String, directory: URL? = nil) -> [String: String] { - var result = ["CODEX_FIXTURE_MODE": mode] - for key in ["PATH", "SystemRoot"] { - if let entry = ProcessInfo.processInfo.environment.first(where: { - WindowsProcessEnvironment.namesMatch($0.key, key) - }) { - result[key] = entry.value - } - } - if let directory { result["CODEX_FIXTURE_DIRECTORY"] = directory.path } - return result - } - - private func temporaryDirectory() throws -> URL { - let directory = FileManager.default.temporaryDirectory.appendingPathComponent( - "command 汉字 \(UUID())") - try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false) - return directory - } - - private func observeTree(_ directory: URL) async throws -> [Observation] { - let ready = directory.appendingPathComponent("root") - let deadline = ContinuousClock.now + .seconds(10) - while !FileManager.default.fileExists(atPath: ready.path) { - guard ContinuousClock.now < deadline else { throw ReadinessTimeout() } - try await Task.sleep(for: .milliseconds(10)) - } - return try ["root", "branch", "leaf"].map { name in - let value = try String(contentsOf: directory.appendingPathComponent(name), encoding: .utf8) - let pid = try #require(DWORD(value)) - return Observation(try #require(OpenProcess(DWORD(SYNCHRONIZE), false, pid))) - } - } - - private struct ReadinessTimeout: Error {} - - private struct DirectoryIdentity: Equatable { - let volume: UInt64 - let file: Data - } - - private func directoryIdentity(_ path: String) throws -> DirectoryIdentity { - let handle = try #require( - CreateFileW( - Array(path.utf16) + [0], DWORD(FILE_READ_ATTRIBUTES), - DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil, DWORD(OPEN_EXISTING), - DWORD(FILE_FLAG_BACKUP_SEMANTICS), nil)) - try #require(handle != INVALID_HANDLE_VALUE) - defer { CloseHandle(handle) } - var identity = FILE_ID_INFO() - try #require( - GetFileInformationByHandleEx( - handle, FileIdInfo, &identity, DWORD(MemoryLayout.size(ofValue: identity)))) - return DirectoryIdentity( - volume: identity.VolumeSerialNumber, file: withUnsafeBytes(of: identity.FileId) { Data($0) }) - } - - private final class Observation { - let handle: HANDLE - init(_ handle: HANDLE) { self.handle = handle } - deinit { CloseHandle(handle) } - var hasExited: Bool { WaitForSingleObject(handle, 0) == DWORD(WAIT_OBJECT_0) } - } -} diff --git a/Tests/WindowsProcess/ManagedProcessTests.swift b/Tests/WindowsProcess/ManagedProcessTests.swift deleted file mode 100644 index c5e5352..0000000 --- a/Tests/WindowsProcess/ManagedProcessTests.swift +++ /dev/null @@ -1,287 +0,0 @@ -import CodexAppServerRuntime -import CodexAppServerStdio -import Foundation -import Testing -import WinSDK - -@testable import ManagedProcess - -@Suite("Windows adapter process ownership", .timeLimit(.minutes(1))) -struct ManagedProcessTests { - @Test("Host callback handles and provenance do not enter vendor environments") - func hostEnvironmentIsolation() { - let environment = CodexProcessEnvironment.resolved( - base: [ - "computer_mcp_host_context": "bound-by-host", "Computer_Mcp_Host_Fd": "3", - "computer_mcp_host_read_handle": "144", "COMPUTER_MCP_HOST_WRITE_HANDLE": "148", - "PRESERVE_VALUE": "user-owned", - ], systemProxy: .init()) - #expect(environment["PRESERVE_VALUE"] == "user-owned") - #expect(!environment.keys.contains { $0.lowercased().hasPrefix("computer_mcp_host_") }) - } - - @Test("Graceful EOF preserves final output and a cancelled close caller joins cleanup") - func gracefulClose() async throws { - let directory = try temporaryDirectory() - defer { try? FileManager.default.removeItem(at: directory) } - let process = try makeProcess(mode: "input-finish", directory: directory, grace: 5_000) - do { - var lines = process.inboundLines.makeAsyncIterator() - try await process.sendLine("汉字 🐈") - #expect(try await lines.next() == "汉字 🐈") - let root = try observe("main", in: directory) - let closing = Task { await process.close() } - closing.cancel() - #expect(try await lines.next() == "eof") - await #expect(throws: ManagedLineProcessError.self) { try await process.sendLine("late") } - #expect(!root.hasExited) - #expect(await process.snapshot().cleanupConfirmed == false) - try Data().write(to: directory.appendingPathComponent("release")) - await closing.value - let stopped = await process.snapshot() - #expect(stopped.cleanupConfirmed == true && stopped.hasExited) - #expect(stopped.exitCode == 23 && !stopped.terminationEscalated) - #expect(stopped.processID == Int32(bitPattern: root.identifier)) - #expect(stopped.processGroupID == nil && stopped.supervisorProcessID == nil) - #expect(root.hasExited) - #expect(try await lines.next() == nil) - } catch { - await process.close() - throw error - } - } - - @Test("Forced close joins a blocked writer and concurrent observers") - func blockedInput() async throws { - let directory = try temporaryDirectory() - defer { try? FileManager.default.removeItem(at: directory) } - let process = try makeProcess(mode: "blocked-input", directory: directory) - do { - var lines = process.inboundLines.makeAsyncIterator() - #expect(try await lines.next() == "ready") - let root = try observe("root", in: directory) - let sending = Task { try await process.sendLine(String(repeating: "x", count: 2_000_000)) } - #expect(try await lines.next() == "receiving") - async let first: Void = process.close() - async let second: Void = process.close() - await first - await second - guard case .failure = await sending.result else { - Issue.record("A blocked native pipe accepted the complete frame.") - return - } - let stopped = await process.snapshot() - #expect(stopped.cleanupConfirmed == true && stopped.hasExited) - #expect(stopped.terminationEscalated && stopped.pendingWrites == 0) - #expect(root.hasExited) - } catch { - await process.close() - throw error - } - } - - @Test("Natural root exit joins inherited descendants") - func naturalExit() async throws { - let directory = try temporaryDirectory() - defer { try? FileManager.default.removeItem(at: directory) } - let process = try makeProcess(mode: "tree-exit", directory: directory) - do { - var lines = process.inboundLines.makeAsyncIterator() - #expect(try await lines.next() == "ready") - let members = try ["root", "branch", "leaf"].map { try observe($0, in: directory) } - try Data().write(to: directory.appendingPathComponent("release")) - #expect(try await lines.next() == nil) - try await waitForCleanup(process) - await process.close() - #expect(members.allSatisfy { $0.hasExited }) - #expect(await process.snapshot().exitCode == 0) - } catch { - await process.close() - throw error - } - } - - @Test("Owner exit cleans only its child tree and preserves an independent invocation") - func externalOwnerExit() async throws { - let firstDirectory = try temporaryDirectory() - let secondDirectory = try temporaryDirectory() - defer { - try? FileManager.default.removeItem(at: firstDirectory) - try? FileManager.default.removeItem(at: secondDirectory) - } - let owner = try CodexAppServerStdioTransport( - configuration: .init(executableURL: fixture(), environment: environment(mode: "lines"))) - do { - let first = try makeProcess( - mode: "tree", directory: firstDirectory, owner: owner.processIdentifier) - do { - let second = try makeProcess(mode: "tree", directory: secondDirectory) - do { - var firstLines = first.inboundLines.makeAsyncIterator() - var secondLines = second.inboundLines.makeAsyncIterator() - #expect(try await firstLines.next() == "ready") - #expect(try await secondLines.next() == "ready") - let firstMembers = try ["root", "branch", "leaf"].map { - try observe($0, in: firstDirectory) - } - let secondMembers = try ["root", "branch", "leaf"].map { - try observe($0, in: secondDirectory) - } - await owner.close() - try await waitForCleanup(first) - #expect(firstMembers.allSatisfy { $0.hasExited }) - #expect(secondMembers.allSatisfy { !$0.hasExited }) - #expect(await second.snapshot().cleanupConfirmed == false) - await second.close() - #expect(secondMembers.allSatisfy { $0.hasExited }) - } catch { - await second.close() - throw error - } - await first.close() - } catch { - await first.close() - throw error - } - } catch { - await owner.close() - throw error - } - await owner.close() - } - - @Test("A retained terminated owner fails before child admission") - func terminatedOwner() async throws { - let directory = try temporaryDirectory() - let childDirectory = try temporaryDirectory() - defer { - try? FileManager.default.removeItem(at: directory) - try? FileManager.default.removeItem(at: childDirectory) - } - let owner = try CodexAppServerStdioTransport( - configuration: .init( - executableURL: fixture(), environment: environment(mode: "lines", directory: directory))) - do { - var lines = owner.inboundLines.makeAsyncIterator() - try await owner.sendLine("ready") - #expect(try await lines.next() == "ready") - let retained = try observe("main", in: directory) - await owner.close() - #expect(retained.hasExited) - #expect(throws: ManagedLineProcessError.self) { - _ = try makeProcess( - mode: "lines", directory: childDirectory, owner: Int32(bitPattern: retained.identifier)) - } - #expect( - !FileManager.default.fileExists(atPath: childDirectory.appendingPathComponent("main").path)) - } catch { - await owner.close() - throw error - } - } - - @Test("The adapter's lower outgoing bound preserves the running process") - func outgoingBound() async throws { - let directory = try temporaryDirectory() - defer { try? FileManager.default.removeItem(at: directory) } - let process = try makeProcess(mode: "lines", directory: directory, limit: 4) - do { - await #expect(throws: ManagedLineProcessError.self) { try await process.sendLine("🐈x") } - var lines = process.inboundLines.makeAsyncIterator() - try await process.sendLine("🐈") - #expect(try await lines.next() == "🐈") - #expect(await process.snapshot().state == .running) - await process.close() - #expect(await process.snapshot().cleanupConfirmed == true) - } catch { - await process.close() - throw error - } - } - - @Test("The adapter's lower incoming bound joins the process on overflow") - func incomingBound() async throws { - let directory = try temporaryDirectory() - defer { try? FileManager.default.removeItem(at: directory) } - let process = try makeProcess(mode: "oversized-frame", directory: directory, limit: 64) - do { - var lines = process.inboundLines.makeAsyncIterator() - #expect(try await lines.next() == "ready") - let root = try observe("root", in: directory) - try await process.sendLine("start") - await #expect( - throws: CodexAppServerConnectionFoundation.FoundationError.messageTooLarge(limitBytes: 64) - ) { try await lines.next() } - try await waitForCleanup(process) - #expect(root.hasExited) - await process.close() - } catch { - await process.close() - throw error - } - } - - private func fixture() throws -> URL { - URL(fileURLWithPath: try #require(ProcessInfo.processInfo.environment["CODEX_WINDOWS_FIXTURE"])) - } - - private func makeProcess( - mode: String, directory: URL, grace: Int = 0, - limit: Int = 16 * 1_024 * 1_024, - owner: Int32 = ProcessInfo.processInfo.processIdentifier - ) throws -> ManagedLineProcess { - try ManagedLineProcess( - configuration: .init( - executable: fixture().path, environment: environment(mode: mode, directory: directory), - workingDirectory: directory, terminationGraceMilliseconds: grace, - killGraceMilliseconds: 5_000, maximumMessageBytes: limit, ownerProcessID: owner)) - } - - private func environment(mode: String, directory: URL? = nil) -> [String: String] { - var result = ["CODEX_FIXTURE_MODE": mode] - for key in ["PATH", "SystemRoot"] { - if let value = ProcessInfo.processInfo.environment.first(where: { - $0.key.caseInsensitiveCompare(key) == .orderedSame - })?.value { - result[key] = value - } - } - if let directory { result["CODEX_FIXTURE_DIRECTORY"] = directory.path } - return result - } - - private func temporaryDirectory() throws -> URL { - let directory = FileManager.default.temporaryDirectory.appendingPathComponent( - "adapter 汉字 \(UUID())") - try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false) - return directory - } - - private func observe(_ name: String, in directory: URL) throws -> NativeProcessObservation { - let text = try String(contentsOf: directory.appendingPathComponent(name), encoding: .utf8) - let identifier = try #require(DWORD(text)) - let handle = try #require(OpenProcess(DWORD(SYNCHRONIZE), false, identifier)) - return NativeProcessObservation(handle: handle, identifier: identifier) - } - - private func waitForCleanup(_ process: ManagedLineProcess) async throws { - let deadline = ContinuousClock.now + .seconds(10) - while await process.snapshot().cleanupConfirmed != true { - guard ContinuousClock.now < deadline else { throw CleanupTimeout() } - try await Task.sleep(for: .milliseconds(10)) - } - } - - private struct CleanupTimeout: Error {} - - private final class NativeProcessObservation { - let handle: HANDLE - let identifier: DWORD - init(handle: HANDLE, identifier: DWORD) { - self.handle = handle - self.identifier = identifier - } - deinit { CloseHandle(handle) } - var hasExited: Bool { WaitForSingleObject(handle, 0) == DWORD(WAIT_OBJECT_0) } - } -} diff --git a/Tests/WindowsProcess/Package.swift.template b/Tests/WindowsProcess/Package.swift.template deleted file mode 100644 index ee4f988..0000000 --- a/Tests/WindowsProcess/Package.swift.template +++ /dev/null @@ -1,15 +0,0 @@ -// swift-tools-version: 6.2 -import PackageDescription - -let package = Package( - name: "WindowsManagedProcessTests", - dependencies: [.package(name: "swift-codex", path: "__SDK_PATH__")], - targets: [ - .target( - name: "ManagedProcess", - dependencies: [ - .product(name: "CodexAppServerStdio", package: "swift-codex"), - .product(name: "CodexAppServerRuntime", package: "swift-codex"), - ]), - .testTarget(name: "ManagedProcessTests", dependencies: ["ManagedProcess"]), - ]) diff --git a/Tests/WindowsProcess/PrivateDirectoryTests.swift b/Tests/WindowsProcess/PrivateDirectoryTests.swift deleted file mode 100644 index 6f33726..0000000 --- a/Tests/WindowsProcess/PrivateDirectoryTests.swift +++ /dev/null @@ -1,174 +0,0 @@ -import Foundation -import Testing -import WinSDK - -@testable import ManagedProcess - -@Suite("Windows private state directories", .timeLimit(.minutes(1))) -struct PrivateDirectoryTests { - @Test("Creation and reopen preserve a protected owner DACL and private child inheritance") - func creationAndInheritance() throws { - let root = temporaryURL() - defer { try? FileManager.default.removeItem(at: root) } - let target = root.appendingPathComponent("subjects/主体", isDirectory: true) - do { - let first = try WindowsPrivateDirectory(target) - let second = try WindowsPrivateDirectory(target) - defer { withExtendedLifetime((first, second)) {} } - let security = try snapshot(target) - #expect(security.protected) - #expect(security.grantees == [security.owner]) - #expect(security.masks == [DWORD(0x001F_01FF)]) - let file = target.appendingPathComponent("record.sqlite") - try Data("private".utf8).write(to: file) - let child = try snapshot(file) - #expect(child.grantees == [security.owner]) - #expect(child.masks == [DWORD(0x001F_01FF)]) - #expect(try Data(contentsOf: file) == Data("private".utf8)) - } - let reopened = try WindowsPrivateDirectory(target) - withExtendedLifetime(reopened) {} - } - - @Test("Existing broader permissions are rejected without changing ACLs or contents") - func existingDirectoryPreserved() throws { - let root = temporaryURL() - defer { try? FileManager.default.removeItem(at: root) } - try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false) - let file = root.appendingPathComponent("existing.txt") - try Data("unrelated".utf8).write(to: file) - let before = try snapshot(root) - #expect(throws: WindowsPrivateDirectoryError.self) { try WindowsPrivateDirectory(root) } - #expect(try snapshot(root) == before) - #expect(try Data(contentsOf: file) == Data("unrelated".utf8)) - } - - @Test("Retained ancestors prevent parent and state-directory replacement until release") - func retainsIdentity() throws { - let root = temporaryURL() - let replacement = root.appendingPathExtension("moved") - defer { - try? FileManager.default.removeItem(at: root) - try? FileManager.default.removeItem(at: replacement) - } - let target = root.appendingPathComponent("state") - var guardDirectory: WindowsPrivateDirectory? = try WindowsPrivateDirectory(target) - try #require(!MoveFileW(Array(root.path.utf16) + [0], Array(replacement.path.utf16) + [0])) - #expect(GetLastError() == DWORD(ERROR_SHARING_VIOLATION)) - try #require( - !MoveFileW( - Array(target.path.utf16) + [0], - Array(target.appendingPathExtension("moved").path.utf16) + [0])) - #expect(GetLastError() == DWORD(ERROR_SHARING_VIOLATION)) - withExtendedLifetime(guardDirectory) {} - guardDirectory = nil - try #require(MoveFileW(Array(root.path.utf16) + [0], Array(replacement.path.utf16) + [0])) - } - - @Test("Directory links are rejected at the state root and within its ancestry") - func rejectsReparsePoints() throws { - let root = temporaryURL() - defer { try? FileManager.default.removeItem(at: root) } - try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false) - let destination = root.appendingPathComponent("destination") - do { - let directory = try WindowsPrivateDirectory(destination) - withExtendedLifetime(directory) {} - } - let link = root.appendingPathComponent("alias") - try #require( - CreateSymbolicLinkW( - Array(link.path.utf16) + [0], Array(destination.path.utf16) + [0], - DWORD(SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) != 0) - #expect(throws: WindowsPrivateDirectoryError.self) { try WindowsPrivateDirectory(link) } - #expect(throws: WindowsPrivateDirectoryError.self) { - try WindowsPrivateDirectory(link.appendingPathComponent("child")) - } - #expect( - !FileManager.default.fileExists(atPath: destination.appendingPathComponent("child").path)) - } - - @Test("File collisions remain untouched and system-owned directories are refused") - func refusesUnownedOrNonDirectory() throws { - let file = temporaryURL() - defer { try? FileManager.default.removeItem(at: file) } - try Data("file".utf8).write(to: file) - #expect(throws: WindowsPrivateDirectoryError.self) { try WindowsPrivateDirectory(file) } - #expect(try Data(contentsOf: file) == Data("file".utf8)) - var path = [WCHAR](repeating: 0, count: 32_768) - let count = GetWindowsDirectoryW(&path, UINT(path.count)) - try #require(count > 0 && count < path.count) - let system = URL(fileURLWithPath: String(decoding: path.prefix(Int(count)), as: UTF16.self)) - let before = try snapshot(system) - #expect(throws: WindowsPrivateDirectoryError.self) { try WindowsPrivateDirectory(system) } - #expect(try snapshot(system) == before) - } - - private func temporaryURL() -> URL { - FileManager.default.temporaryDirectory.appendingPathComponent("private 汉字 \(UUID())") - } - - private struct Snapshot: Equatable { - let owner: String - let protected: Bool - let grantees: [String] - let masks: [DWORD] - let sddl: String - } - - private func snapshot(_ url: URL) throws -> Snapshot { - let handle = try #require( - CreateFileW( - Array(url.path.utf16) + [0], DWORD(READ_CONTROL), - DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil, DWORD(OPEN_EXISTING), - DWORD(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT), nil)) - try #require(handle != INVALID_HANDLE_VALUE) - defer { CloseHandle(handle) } - var owner: PSID? - var acl: PACL? - var descriptor: PSECURITY_DESCRIPTOR? - let information = DWORD(OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION) - try #require( - GetSecurityInfo(handle, SE_FILE_OBJECT, information, &owner, nil, &acl, nil, &descriptor) - == DWORD(ERROR_SUCCESS)) - let value = try #require(descriptor) - defer { LocalFree(value) } - let ownerName = try sidString(try #require(owner)) - var control: SECURITY_DESCRIPTOR_CONTROL = 0 - var revision: DWORD = 0 - try #require(GetSecurityDescriptorControl(value, &control, &revision)) - var text: LPWSTR? - try #require( - ConvertSecurityDescriptorToStringSecurityDescriptorW( - value, DWORD(SDDL_REVISION_1), information, &text, nil)) - let string = try #require(text) - defer { LocalFree(string) } - var grantees: [String] = [] - var masks: [DWORD] = [] - if let acl { - for index in 0...offset(of: \.SidStart)!))) - masks.append(ace.Mask) - } - } - } - return Snapshot( - owner: ownerName, protected: control & SECURITY_DESCRIPTOR_CONTROL(SE_DACL_PROTECTED) != 0, - grantees: grantees, masks: masks, sddl: String(decodingCString: string, as: UTF16.self)) - } - - private func sidString(_ sid: PSID) throws -> String { - var text: LPWSTR? - try #require(ConvertSidToStringSidW(sid, &text)) - let value = try #require(text) - defer { LocalFree(value) } - return String(decodingCString: value, as: UTF16.self) - } -} diff --git a/Tests/WindowsProcess/README.md b/Tests/WindowsProcess/README.md deleted file mode 100644 index fac117a..0000000 --- a/Tests/WindowsProcess/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Native adapter process tests - -`Scripts/test-windows-process.ps1 -SDKPath SDK_CHECKOUT -OutputDirectory OUTPUT` -stages the exact production Windows process source and records its hashes. It -uses the complete swift-codex checkout matching the shipping lock and that SDK's -native process fixture. -Run it on Windows with the selected Swift toolchain. The output directory must -be new; the script restores its temporary environment settings. Native filesystem -sources are typechecked before SDK fixture builds so WinSDK import failures -produce immediate diagnostics. Both runtime configurations remain required. - -Debug and release test EOF completion, cancelled/concurrent shutdown callers, -blocked stdin, configured frame bounds, natural root exit with descendants, -external-owner death, and rejection of a terminated owner before child launch. -Observations retain exact native process handles and check sibling isolation. - -Finite-command tests also exercise synchronous execution, argument quoting, -Unicode working directories, environment overrides, stdin EOF, bounded output -with continued drain, and descendant cleanup after cancellation, timeout and -natural exit. Codex discovery and finite commands use the same Windows path and -environment rules. Each test retains its own native process observations. - -Private state tests inspect actual owner SIDs and protected DACLs, inherited -file permissions, directory replacement during retained ownership, and refusal -of reparses, foreign owners and existing broader permissions. The separate -GRDB audit verifies that connection configuration retains the production -directory guard through close and release. - -Managed-worktree tests run the production finite-command runner against real Git -repositories. They check private root inheritance, physical containment and -identity, retained parent ownership, dirty removal refusal, reparse rejection, -and absence verification without recreating missing content. - -These tests verify the adapter's lifecycle boundary. The complete adapter and -all dependency targets retain their separate source audit. Fixture results do -not establish standard-MCP packaging or authenticated model acceptance. diff --git a/Tests/WindowsProcess/WorktreeFileSystemTests.swift b/Tests/WindowsProcess/WorktreeFileSystemTests.swift deleted file mode 100644 index 3903450..0000000 --- a/Tests/WindowsProcess/WorktreeFileSystemTests.swift +++ /dev/null @@ -1,176 +0,0 @@ -import Foundation -import Testing -import WinSDK - -@testable import ManagedProcess - -@Suite("Windows managed worktree filesystem", .timeLimit(.minutes(2))) -struct WorktreeFileSystemTests { - private typealias FileSystem = CodexWorktreeFileSystem - - @Test("Real Git worktrees inherit private access, retain their parent and refuse dirty removal") - func gitLifecycle() throws { - let container = temporaryURL() - defer { try? FileManager.default.removeItem(at: container) } - let source = container.appendingPathComponent("source 汉字") - try FileManager.default.createDirectory(at: source, withIntermediateDirectories: true) - try git(["init", "--initial-branch=main"], in: source) - try git( - [ - "-c", "user.name=Native Test", "-c", "user.email=native@example.invalid", "commit", - "--allow-empty", "-m", "fixture", - ], in: source) - let root = container.appendingPathComponent("managed") - let parent = root.appendingPathComponent("repository") - let target = parent.appendingPathComponent("worktree 汉字") - let rootProtection = try FileSystem.prepareRoot(root) - let parentProtection = try FileSystem.prepareParent(parent, containedIn: root) - defer { withExtendedLifetime((rootProtection, parentProtection)) {} } - try git(["worktree", "add", "-b", "candidate", target.path, "HEAD"], in: source) - try FileSystem.validateDirectory(target, containedIn: root) - let reported = try git(["rev-parse", "--show-toplevel"], in: target) - .stdout.trimmingCharacters(in: .whitespacesAndNewlines) - let canonical = try FileSystem.canonicalDirectory(reported, relativeTo: target) - #expect(try FileSystem.sameDirectory(canonical, target)) - let common = try git(["rev-parse", "--git-common-dir"], in: target) - .stdout.trimmingCharacters(in: .whitespacesAndNewlines) - #expect( - try FileSystem.sameDirectory( - FileSystem.canonicalDirectory(common, relativeTo: target), - source.appendingPathComponent(".git"))) - try #require( - !MoveFileW( - Array(parent.path.utf16) + [0], - Array(parent.appendingPathExtension("moved").path.utf16) + [0])) - #expect(GetLastError() == DWORD(ERROR_SHARING_VIOLATION)) - let dirty = target.appendingPathComponent("untracked.txt") - try Data("preserve".utf8).write(to: dirty) - let refused = try runGit(["worktree", "remove", target.path], in: source) - #expect(refused.exitCode != 0) - #expect(try Data(contentsOf: dirty) == Data("preserve".utf8)) - try FileManager.default.removeItem(at: dirty) - try git(["worktree", "remove", target.path], in: source) - #expect(try FileSystem.isAbsent(target)) - #expect(throws: WindowsPrivateDirectoryError.self) { - try FileSystem.validateDirectory(target, containedIn: root) - } - #expect(try FileSystem.isAbsent(target)) - try parentProtection.validate() - } - - @Test("Containment is physical and validation cannot create a missing or unrelated path") - func containmentAndInspection() throws { - let container = temporaryURL() - defer { try? FileManager.default.removeItem(at: container) } - let root = container.appendingPathComponent("managed") - let other = container.appendingPathComponent("managed-other") - let protection = try FileSystem.prepareRoot(root) - let otherProtection = try FileSystem.prepareRoot(other) - defer { withExtendedLifetime((protection, otherProtection)) {} } - #expect(try !FileSystem.isDescendant(other, of: root)) - #expect(try !FileSystem.sameDirectory(other, root)) - #expect(throws: WindowsPrivateDirectoryError.self) { - try FileSystem.validateDirectory(other, containedIn: root) - } - let missing = root.appendingPathComponent("missing") - #expect(throws: WindowsPrivateDirectoryError.self) { - try WindowsPrivateDirectory(existingDirectory: missing) - } - #expect(throws: WindowsPrivateDirectoryError.self) { - try FileSystem.validateDirectory(missing, containedIn: root) - } - #expect(try FileSystem.isAbsent(missing)) - let outside = other.appendingPathComponent("child") - #expect(throws: WindowsPrivateDirectoryError.self) { - try FileSystem.prepareParent(outside, containedIn: root) - } - #expect(try FileSystem.isAbsent(outside)) - let inherited = root.appendingPathComponent("inherited") - try FileManager.default.createDirectory(at: inherited, withIntermediateDirectories: false) - try FileSystem.validateDirectory(inherited, containedIn: root) - #expect(throws: WindowsPrivateDirectoryError.self) { - try WindowsPrivateDirectory(existingDirectory: inherited) - } - } - - @Test("Native identity resolves source aliases while managed roots reject directory reparses") - func aliasesAndReparses() throws { - let container = temporaryURL() - defer { try? FileManager.default.removeItem(at: container) } - let root = container.appendingPathComponent("root 汉字") - let protection = try FileSystem.prepareRoot(root) - defer { withExtendedLifetime(protection) {} } - let link = container.appendingPathComponent("alias") - try makeLink(link, to: root) - #expect(try FileSystem.sameDirectory(link, root)) - #expect( - try FileSystem.sameDirectory( - FileSystem.canonicalDirectory(link.path, relativeTo: container), root)) - #expect(throws: WindowsPrivateDirectoryError.self) { - try FileSystem.validateDirectory(link, containedIn: root) - } - let dangling = root.appendingPathComponent("dangling") - try makeLink(dangling, to: root.appendingPathComponent("missing")) - #expect(try !FileSystem.isAbsent(dangling)) - #expect(try !FileSystem.provisionPathIsAvailable(dangling)) - #expect(throws: WindowsPrivateDirectoryError.self) { - try FileSystem.validateDirectory(dangling, containedIn: root) - } - } - - @Test("An inaccessible path is never treated as absent") - func unverifiableAbsence() throws { - let file = temporaryURL() - defer { try? FileManager.default.removeItem(at: file) } - try Data("preserve".utf8).write(to: file) - let handle = try #require( - CreateFileW( - Array(file.path.utf16) + [0], DWORD(READ_CONTROL | WRITE_DAC), - DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil, DWORD(OPEN_EXISTING), 0, - nil)) - try #require(handle != INVALID_HANDLE_VALUE) - defer { CloseHandle(handle) } - var original: PSECURITY_DESCRIPTOR? - try #require( - GetSecurityInfo( - handle, SE_FILE_OBJECT, DWORD(DACL_SECURITY_INFORMATION), nil, nil, nil, nil, &original) - == DWORD(ERROR_SUCCESS)) - let saved = try #require(original) - defer { LocalFree(saved) } - var empty: PSECURITY_DESCRIPTOR? - try #require( - ConvertStringSecurityDescriptorToSecurityDescriptorW( - Array("D:P".utf16) + [0], DWORD(SDDL_REVISION_1), &empty, nil)) - let denied = try #require(empty) - defer { LocalFree(denied) } - try #require(SetKernelObjectSecurity(handle, DWORD(DACL_SECURITY_INFORMATION), denied)) - defer { #expect(SetKernelObjectSecurity(handle, DWORD(DACL_SECURITY_INFORMATION), saved)) } - #expect(throws: WindowsPrivateDirectoryError.self) { try FileSystem.isAbsent(file) } - } - - @discardableResult - private func git(_ arguments: [String], in directory: URL) throws -> CommandResult { - let result = try runGit(arguments, in: directory) - try #require(result.exitCode == 0, "Git failed: \(result.stderr)") - try #require(!result.timedOut) - return result - } - - private func runGit(_ arguments: [String], in directory: URL) throws -> CommandResult { - try ProcessCommandRunner().run( - executable: FileSystem.gitExecutable, arguments: arguments, workingDirectory: directory, - environment: ["GIT_TERMINAL_PROMPT": "0", "GIT_CONFIG_NOSYSTEM": "1", "LC_ALL": "C"], - timeoutMilliseconds: 30_000, maxOutputBytes: 1_048_576) - } - - private func makeLink(_ link: URL, to destination: URL) throws { - try #require( - CreateSymbolicLinkW( - Array(link.path.utf16) + [0], Array(destination.path.utf16) + [0], - DWORD(SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) != 0) - } - - private func temporaryURL() -> URL { - FileManager.default.temporaryDirectory.appendingPathComponent("worktree 汉字 \(UUID())") - } -} diff --git a/Tests/WindowsSQLite/DirectoryLifetimeTests.swift b/Tests/WindowsSQLite/DirectoryLifetimeTests.swift deleted file mode 100644 index 2afed7b..0000000 --- a/Tests/WindowsSQLite/DirectoryLifetimeTests.swift +++ /dev/null @@ -1,73 +0,0 @@ -import Foundation -import GRDB -import Testing -import WinSDK - -@Suite("Windows database directory lifetime", .timeLimit(.minutes(1))) -struct DirectoryLifetimeTests { - @Test("A native GRDB connection retains private ancestry through close and release") - func connectionOwnsDirectory() async throws { - let root = FileManager.default.temporaryDirectory.appendingPathComponent( - "database 汉字 \(UUID())") - let moved = root.appendingPathExtension("moved") - defer { - try? FileManager.default.removeItem(at: root) - try? FileManager.default.removeItem(at: moved) - } - let state = root.appendingPathComponent("state") - let reference = Reference() - try useAndClose(state: state, root: root, moved: moved, reference: reference) - // Dispatch retires GRDB's queue-specific context asynchronously. A cleared weak - // reference alone does not observe completion of the native handle closes. - let deadline = ContinuousClock.now + .seconds(3) - while !isReleased(reference), ContinuousClock.now < deadline { - try await Task.sleep(for: .milliseconds(5)) - } - try #require(reference.directory == nil) - while !MoveFileW(Array(root.path.utf16) + [0], Array(moved.path.utf16) + [0]) { - let code = GetLastError() - try #require(code == DWORD(ERROR_SHARING_VIOLATION), "Directory rename failed: \(code)") - try #require(ContinuousClock.now < deadline, "Native directory handles remain open") - try await Task.sleep(for: .milliseconds(5)) - } - let reopened = try open(state: moved.appendingPathComponent("state"), reference: reference) - let value = try await reopened.read { db in - try Int64.fetchOne(db, sql: "SELECT value FROM records") - } - #expect(value == Int64.max) - try reopened.close() - } - - private func isReleased(_ reference: Reference) -> Bool { reference.directory == nil } - - private func useAndClose(state: URL, root: URL, moved: URL, reference: Reference) throws { - let database = try open(state: state, reference: reference) - defer { withExtendedLifetime(database) {} } - try database.write { db in - try db.execute(sql: "CREATE TABLE records (value INTEGER NOT NULL)") - try db.execute(sql: "INSERT INTO records VALUES (?)", arguments: [Int64.max]) - } - #expect(reference.directory != nil) - try #require(!MoveFileW(Array(root.path.utf16) + [0], Array(moved.path.utf16) + [0])) - try database.close() - #expect(reference.directory != nil) - try #require( - !MoveFileW( - Array(state.path.utf16) + [0], Array(state.appendingPathExtension("moved").path.utf16) + [0] - )) - #expect(GetLastError() == DWORD(ERROR_SHARING_VIOLATION)) - } - - private func open(state: URL, reference: Reference) throws -> DatabaseQueue { - let directory = try WindowsPrivateDirectory(state) - reference.directory = directory - var configuration = Configuration() - configuration.prepareDatabase { [directory] _ in try directory.validate() } - return try DatabaseQueue( - path: state.appendingPathComponent("records.sqlite").path, configuration: configuration) - } - - private final class Reference { - weak var directory: WindowsPrivateDirectory? - } -} diff --git a/Tests/WindowsSQLite/GRDBTests.swift b/Tests/WindowsSQLite/GRDBTests.swift deleted file mode 100644 index a14e734..0000000 --- a/Tests/WindowsSQLite/GRDBTests.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation -import GRDB -import Testing - -@Suite("Native SQLite Swift consumer") -struct GRDBTests { - private enum Rollback: Error, Equatable { case requested } - - @Test - func rollbackAndExactIntegersSurviveReopening() throws { - let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) - defer { try? FileManager.default.removeItem(at: directory) } - let path = directory.appendingPathComponent("ownership.sqlite").path - let database = try DatabaseQueue(path: path) - defer { try? database.close() } - var migrator = DatabaseMigrator() - migrator.registerMigration("ownership") { db in - try db.execute( - sql: "CREATE TABLE owners (id INTEGER PRIMARY KEY, generation INTEGER NOT NULL)") - } - try migrator.migrate(database) - try database.write { db in - try db.execute( - sql: "INSERT INTO owners VALUES (?, ?)", arguments: [Int64.max, Int64.min]) - } - #expect(throws: Rollback.requested) { - try database.write { db in - try db.execute(sql: "UPDATE owners SET generation = 0") - throw Rollback.requested - } - } - try database.close() - let reopened = try DatabaseQueue(path: path) - defer { try? reopened.close() } - try reopened.read { db in - let count = try Int.fetchOne(db, sql: "SELECT count(*) FROM owners") - let id = try Int64.fetchOne(db, sql: "SELECT id FROM owners") - let generation = try Int64.fetchOne(db, sql: "SELECT generation FROM owners") - #expect(count == 1) - #expect(id == Int64.max) - #expect(generation == Int64.min) - } - } - - @Test - func snapshotPoolRetainsItsViewAcrossACommittedWrite() throws { - let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) - defer { try? FileManager.default.removeItem(at: directory) } - let database = try DatabasePool(path: directory.appendingPathComponent("snapshots.sqlite").path) - defer { try? database.close() } - try database.write { db in - try db.execute(sql: "CREATE VIRTUAL TABLE search USING fts5(body)") - try db.execute(sql: "INSERT INTO search VALUES ('owned runtime')") - } - let snapshot = try database.makeSnapshotPool() - defer { try? snapshot.close() } - try database.write { db in - try db.execute(sql: "INSERT INTO search VALUES ('replacement runtime')") - } - let query = "SELECT count(*) FROM search WHERE search MATCH 'runtime'" - #expect(try snapshot.read { try Int.fetchOne($0, sql: query) } == 1) - #expect(try database.read { try Int.fetchOne($0, sql: query) } == 2) - } -} diff --git a/Tests/WindowsSQLite/Package.swift.template b/Tests/WindowsSQLite/Package.swift.template deleted file mode 100644 index 64331a7..0000000 --- a/Tests/WindowsSQLite/Package.swift.template +++ /dev/null @@ -1,12 +0,0 @@ -// swift-tools-version: 6.2 -import PackageDescription - -let package = Package( - name: "WindowsDatabaseConsumer", - platforms: [.macOS(.v14)], - dependencies: [.package(path: "__GRDB_PATH__")], - targets: [ - .testTarget( - name: "DatabaseTests", - dependencies: [.product(name: "GRDB", package: "GRDB.swift")]) - ]) diff --git a/Tests/WindowsSQLite/verify.c b/Tests/WindowsSQLite/verify.c deleted file mode 100644 index 94f81aa..0000000 --- a/Tests/WindowsSQLite/verify.c +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include - -#define REQUIRE(condition) do { \ - if (!(condition)) { \ - fprintf(stderr, "SQLite verification failed at line %d: %s\n", __LINE__, #condition); \ - return 1; \ - } \ -} while (0) - -static void execute(sqlite3 *database, const char *sql) { - char *message = NULL; - if (sqlite3_exec(database, sql, NULL, NULL, &message) != SQLITE_OK) { - fprintf(stderr, "SQLite SQL verification failed: %s\n", message); - sqlite3_free(message); - exit(1); - } -} - -static int scalar(sqlite3 *database, const char *sql) { - sqlite3_stmt *statement = NULL; - if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) != SQLITE_OK || - sqlite3_step(statement) != SQLITE_ROW) { - fprintf(stderr, "SQLite scalar verification failed: %s\n", sqlite3_errmsg(database)); - exit(1); - } - int result = sqlite3_column_int(statement, 0); - if (sqlite3_finalize(statement) != SQLITE_OK) exit(1); - return result; -} - -int main(int argc, char **argv) { - REQUIRE(argc == 3); - REQUIRE(strcmp(sqlite3_libversion(), argv[2]) == 0); - REQUIRE(sqlite3_threadsafe() == 1); - REQUIRE(sqlite3_compileoption_used("ENABLE_FTS5")); - REQUIRE(sqlite3_compileoption_used("ENABLE_SNAPSHOT")); - REQUIRE(sqlite3_compileoption_used("ENABLE_COLUMN_METADATA")); - - sqlite3 *reader = NULL; - sqlite3 *writer = NULL; - REQUIRE(sqlite3_open(argv[1], &reader) == SQLITE_OK); - execute(reader, "PRAGMA journal_mode=WAL; CREATE TABLE entries(id INTEGER PRIMARY KEY);" - "INSERT INTO entries VALUES (1);"); - REQUIRE(sqlite3_open(argv[1], &writer) == SQLITE_OK); - - execute(reader, "BEGIN;"); - REQUIRE(scalar(reader, "SELECT count(*) FROM entries;") == 1); - sqlite3_snapshot *snapshot = NULL; - REQUIRE(sqlite3_snapshot_get(reader, "main", &snapshot) == SQLITE_OK); - execute(writer, "INSERT INTO entries VALUES (2);"); - execute(reader, "COMMIT; BEGIN;"); - REQUIRE(sqlite3_snapshot_open(reader, "main", snapshot) == SQLITE_OK); - REQUIRE(scalar(reader, "SELECT count(*) FROM entries;") == 1); - execute(reader, "COMMIT;"); - sqlite3_snapshot_free(snapshot); - REQUIRE(scalar(reader, "SELECT count(*) FROM entries;") == 2); - - execute(writer, "BEGIN; INSERT INTO entries VALUES (3); ROLLBACK;"); - REQUIRE(scalar(reader, "SELECT count(*) FROM entries;") == 2); - execute(writer, "CREATE VIRTUAL TABLE search USING fts5(body);" - "INSERT INTO search VALUES ('owned runtime');"); - REQUIRE(scalar(reader, "SELECT count(*) FROM search WHERE search MATCH 'runtime';") == 1); - - const char *type = NULL; - REQUIRE(sqlite3_table_column_metadata(reader, "main", "entries", "id", &type, - NULL, NULL, NULL, NULL) == SQLITE_OK); - REQUIRE(type != NULL && strcmp(type, "INTEGER") == 0); - REQUIRE(sqlite3_close(writer) == SQLITE_OK); - REQUIRE(sqlite3_close(reader) == SQLITE_OK); - REQUIRE(sqlite3_shutdown() == SQLITE_OK); - printf("{\"version\":\"%s\",\"serialized\":true,\"walSnapshot\":true," - "\"rollback\":true,\"fts5\":true,\"columnMetadata\":true}\n", sqlite3_libversion()); - return 0; -} diff --git a/Tests/test_package.py b/Tests/test_package.py index 38095f3..67b1da5 100644 --- a/Tests/test_package.py +++ b/Tests/test_package.py @@ -109,34 +109,12 @@ def test_named_archives_select_exact_platform_and_architecture_without_manifest_ original = manifest.read_bytes() self.assertEqual(package.validate_architectures(manifest, ["arm64"], "macos"), "codex-plugin-macos-arm64.zip") - self.assertEqual(package.validate_architectures(manifest, ["x86_64"], "windows"), - "codex-plugin-windows-x86_64.zip") - for platform, slices in [("windows", ["arm64"]), ("macos", ["x86_64"]), + for platform, slices in [("macos", ["x86_64"]), ("macos", ["arm64", "x86_64"]), ("linux", ["x86_64"])]: with self.subTest(platform=platform, slices=slices), self.assertRaises(ValueError): package.validate_architectures(manifest, slices, platform) self.assertEqual(manifest.read_bytes(), original) - @unittest.skipUnless(os.name == "nt", "Native Windows directory reparse boundary") - def test_windows_junction_is_rejected_before_copying_external_content(self): - import subprocess - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - outside, source = root / "outside", root / "source" - outside.mkdir() - source.mkdir() - (outside / "sentinel").write_bytes(b"user owned") - junction = source / "junction" - subprocess.run(["cmd", "/c", "mklink", "/J", str(junction), str(outside)], - check=True, capture_output=True) - try: - with self.assertRaisesRegex(ValueError, "reparse"): - package.copy_tree(source, root / "payload") - self.assertFalse((root / "payload").exists()) - self.assertEqual((outside / "sentinel").read_bytes(), b"user owned") - finally: - junction.rmdir() - def test_package_rejects_slice_mismatch_and_manifest_mutation_before_publication(self): for failure in ("slice_mismatch", "manifest_mutation", "version_mismatch"): with self.subTest(failure=failure), tempfile.TemporaryDirectory() as directory: @@ -222,8 +200,7 @@ def test_regular_files_preserve_bytes_and_executable_mode(self): target = root / "target" package.copy_file(source, target) self.assertEqual(target.read_bytes(), source.read_bytes()) - if os.name != "nt": - self.assertEqual(target.stat().st_mode & 0o777, 0o755) + self.assertEqual(target.stat().st_mode & 0o777, 0o755) package.validate_payload(root) def test_file_links_and_special_files_are_rejected_without_reading_their_targets(self): diff --git a/Tests/test_package_archive.py b/Tests/test_package_archive.py index 8425bda..cbe57b2 100644 --- a/Tests/test_package_archive.py +++ b/Tests/test_package_archive.py @@ -1,7 +1,6 @@ import hashlib import importlib.util import json -import os from pathlib import Path import stat import sys @@ -47,8 +46,7 @@ def test_exact_archive_relocates_with_manifest_bytes_and_executable_mode(self): self.assertEqual(result["files_verified"], 2) self.assertEqual((root / "relocated/computer-mcp-plugin.toml").read_bytes(), arguments[2].read_bytes()) self.assertEqual((root / "relocated/bin/adapter").read_bytes(), b"executable bytes") - if os.name != "nt": - self.assertEqual((root / "relocated/bin/adapter").stat().st_mode & 0o777, 0o755) + self.assertEqual((root / "relocated/bin/adapter").stat().st_mode & 0o777, 0o755) def test_unsafe_names_links_modes_and_case_aliases_fail_before_extraction(self): cases = [(name, b"content", stat.S_IFREG | 0o644) for name in ( diff --git a/Tests/test_protocol_checkout.py b/Tests/test_protocol_checkout.py index 828ce4e..99b5c15 100644 --- a/Tests/test_protocol_checkout.py +++ b/Tests/test_protocol_checkout.py @@ -7,9 +7,9 @@ class ProtocolCheckoutTests(unittest.TestCase): - def test_windows_checkout_preserves_hash_bound_resource_bytes(self): + def test_checkout_preserves_hash_bound_resource_bytes(self): repository = Path(__file__).resolve().parents[1] - relatives = [Path('Sources/CodexAdapter/Resources/Protocol'), Path('Vendor/SwiftWindowsRuntime')] + relatives = [Path('Sources/CodexAdapter/Resources/Protocol')] originals = {path.relative_to(repository): path.read_bytes() for relative in relatives for path in (repository / relative).rglob('*') if path.is_file()} manifest = Path('computer-mcp-plugin.toml') diff --git a/Tests/test_protocol_cleanup.py b/Tests/test_protocol_cleanup.py deleted file mode 100644 index 4341592..0000000 --- a/Tests/test_protocol_cleanup.py +++ /dev/null @@ -1,59 +0,0 @@ -import importlib.util -import os -from pathlib import Path -import stat -import tempfile -import unittest -from unittest.mock import patch - - -spec = importlib.util.spec_from_file_location( - "native_protocol_check", Path(__file__).parent / "WindowsAdapter/ProtocolCheck.py") -protocol = importlib.util.module_from_spec(spec) -spec.loader.exec_module(protocol) - - -class ProtocolCleanupTests(unittest.TestCase): - def test_read_only_git_objects_are_removed_from_the_private_home(self): - with tempfile.TemporaryDirectory() as parent: - root = Path(parent) / "private" - objects = root / "codex/.tmp/plugins-clone/.git/objects/pack" - objects.mkdir(parents=True) - index = objects / "pack-fixture.idx" - index.write_bytes(b"private fixture") - index.chmod(stat.S_IREAD) - protocol.remove_private_state(root) - self.assertFalse(root.exists()) - - def test_windows_read_only_failure_retries_only_the_owned_regular_file(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - index = root / "pack-fixture.idx" - index.write_bytes(b"private fixture") - index.chmod(stat.S_IREAD) - - def failed_unlink(path, onerror): - self.assertEqual(path, root) - error = PermissionError("Windows read-only object") - onerror(os.unlink, str(index), (PermissionError, error, None)) - - with patch.object(protocol.shutil, "rmtree", side_effect=failed_unlink): - self.assertEqual(protocol.remove_private_state(root), 1) - self.assertFalse(index.exists()) - - def test_an_acl_denial_is_not_reported_as_success_or_made_more_permissive(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - file = root / "writable" - file.write_bytes(b"retained evidence") - mode = file.stat().st_mode - error = PermissionError("ACL denies cleanup") - - def denied(path, onerror): - onerror(os.unlink, str(file), (PermissionError, error, None)) - - with patch.object(protocol.shutil, "rmtree", side_effect=denied): - with self.assertRaises(PermissionError): - protocol.remove_private_state(root) - self.assertEqual(file.stat().st_mode, mode) - self.assertEqual(file.read_bytes(), b"retained evidence") diff --git a/Tests/test_windows_package.py b/Tests/test_windows_package.py deleted file mode 100644 index e09ea4c..0000000 --- a/Tests/test_windows_package.py +++ /dev/null @@ -1,61 +0,0 @@ -import importlib.util -import json -from pathlib import Path -import shutil -import sys -import tempfile -import unittest -from unittest.mock import patch - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "Scripts")) -import windows_package - - -class WindowsPackageNoticesTests(unittest.TestCase): - def test_staging_copies_open_source_closure_and_records_external_runtime_floor(self): - repo = Path(__file__).resolve().parents[1] - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - source, binary, notices, include = [root / name for name in ("runtime", "bin", "notices", "include")] - for path in (source, binary, notices, include): - path.mkdir() - (source / "swiftCore.dll").write_bytes(b"swift") - (source / "VCRUNTIME140.dll").write_bytes(b"microsoft") - (binary / "codex-mcp-adapter.exe").write_bytes(b"adapter") - header = include / "sqlite3.h" - header.write_bytes(b"sqlite header") - rows = [] - for path in source.iterdir(): - rows.append({"name": path.name, "role": "external-msvc-runtime" if path.name.startswith("VC") else "runtime", - "path": str(path), "sha256": windows_package.windows_runtime.digest(path), - "bytes": path.stat().st_size, "architecture": "x86_64", "version": "14.44.35211.0"}) - report = {"architecture": "x86_64", "libraries": rows} - sqlite = {"includeDirectory": str(include), "receipt": {"source": "fixture", - "librarySHA256": "0" * 64, "headerSHA256": windows_package.windows_runtime.digest(header)}} - with patch.dict("os.environ", {"PATH": str(source), "SystemRoot": str(root)}), \ - patch.object(windows_package.shutil, "which", return_value="inspector"), \ - patch.object(windows_package.windows_runtime, "audit", return_value=report): - result = windows_package.stage_runtime(repo, binary, notices, sqlite, "swift", - lambda *_: "Swift version 6.2.3", shutil.copyfile, shutil.copytree) - self.assertEqual([row["file"] for row in result["libraries"]], ["swiftCore.dll"]) - self.assertFalse((binary / "VCRUNTIME140.dll").exists()) - self.assertEqual(result["external_prerequisites"][0]["minimum_version"], "14.44.35211.0") - self.assertEqual(result["system_imports"], []) - - def test_notice_bytes_and_component_coverage_bind_to_the_owned_sources(self): - source = Path(__file__).resolve().parents[1] / "Vendor/SwiftWindowsRuntime" - original = windows_package.notice_sources(source) - self.assertEqual(len(original["runtime_libraries"]), 19) - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) / "notices" - shutil.copytree(source, root) - selected = root / original["sources"][0]["file"] - selected.write_bytes(selected.read_bytes() + b"modified") - with self.assertRaisesRegex(ValueError, "differs from its upstream"): - windows_package.notice_sources(root) - shutil.copyfile(source / original["sources"][0]["file"], selected) - changed = json.loads((root / "sources.json").read_text()) - changed["runtime_libraries"]["swiftcore.dll"]["sources"] = ["missing/LICENSE"] - (root / "sources.json").write_text(json.dumps(changed)) - with self.assertRaisesRegex(ValueError, "incomplete notice"): - windows_package.notice_sources(root) diff --git a/Tests/test_windows_runtime.py b/Tests/test_windows_runtime.py deleted file mode 100644 index 24032b6..0000000 --- a/Tests/test_windows_runtime.py +++ /dev/null @@ -1,152 +0,0 @@ -import importlib.util -import os -from pathlib import Path -import tempfile -import struct -import unittest -from unittest.mock import patch - - -spec = importlib.util.spec_from_file_location( - "windows_runtime", Path(__file__).resolve().parents[1] / "Scripts/windows_runtime.py") -runtime = importlib.util.module_from_spec(spec) -spec.loader.exec_module(runtime) - - -class WindowsRuntimeTests(unittest.TestCase): - def test_recursive_shared_and_cyclic_imports_preserve_all_owners(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - libraries, system = root / "runtime", root / "system" - libraries.mkdir() - system.mkdir() - executable = root / "adapter.exe" - executable.write_bytes(b"adapter") - for name in ("swiftCore.dll", "Foundation.dll"): - (libraries / name).write_bytes(name.encode()) - (system / "KERNEL32.dll").write_bytes(b"os") - (system / "VCRUNTIME140.dll").write_bytes(b"msvc") - graph = { - "adapter.exe": ["Foundation.dll", "swiftCore.dll"], - "Foundation.dll": ["swiftCore.dll", "VCRUNTIME140.dll"], - "swiftCore.dll": ["Foundation.dll", "KERNEL32.dll"], - "VCRUNTIME140.dll": ["api-ms-win-crt-runtime-l1-1-0.dll"], - } - with patch.object(runtime, "imports", side_effect=lambda path, _: ("x86_64", graph[path.name])), \ - patch.object(runtime, "file_version", return_value="14.44.35211.0"): - report = runtime.audit(executable, [libraries], system, Path("inspector")) - rows = {row["name"]: row for row in report["libraries"]} - self.assertEqual(len(rows), 5) - self.assertEqual(set(rows["swiftCore.dll"]["imported_by"]), {"adapter.exe", "Foundation.dll"}) - self.assertEqual(rows["VCRUNTIME140.dll"]["role"], "external-msvc-runtime") - self.assertEqual(rows["KERNEL32.dll"]["role"], "windows-system") - self.assertFalse(report["relocation_verified"]) - - def test_microsoft_runtime_remains_external_when_found_in_toolchain(self): - with tempfile.TemporaryDirectory() as directory: - source = Path(directory) / "VCRUNTIME140.dll" - source.write_bytes(b"toolchain") - self.assertEqual(runtime.resolve(source.name, [{source.name.lower(): source}], {}), - ("external-msvc-runtime", source)) - - def test_prerequisites_reject_missing_old_wrong_architecture_and_bundled_runtime(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - package, system = root / "package", root / "system" - package.mkdir() - system.mkdir() - def pe(path, machine): - header = bytearray(64) - header[:2] = b"MZ" - struct.pack_into(", and many -contributors, see the THANKS file. - -All rights reserved. - -Permission to use, copy, modify, and distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright -notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN -NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of a copyright holder shall not -be used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization of the copyright holder. diff --git a/Vendor/SwiftWindowsRuntime/icu/LICENSE b/Vendor/SwiftWindowsRuntime/icu/LICENSE deleted file mode 100644 index 9f54372..0000000 --- a/Vendor/SwiftWindowsRuntime/icu/LICENSE +++ /dev/null @@ -1,512 +0,0 @@ -UNICODE LICENSE V3 - -COPYRIGHT AND PERMISSION NOTICE - -Copyright © 2016-2023 Unicode, Inc. - -NOTICE TO USER: Carefully read the following legal agreement. BY -DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR -SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE -TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT -DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of data files and any associated documentation (the "Data Files") or -software and any associated documentation (the "Software") to deal in the -Data Files or Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, and/or sell -copies of the Data Files or Software, and to permit persons to whom the -Data Files or Software are furnished to do so, provided that either (a) -this copyright and permission notice appear with all copies of the Data -Files or Software, or (b) this copyright and permission notice appear in -associated Documentation. - -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF -THIRD PARTY RIGHTS. - -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE -BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, -OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, -ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA -FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder shall -not be used in advertising or otherwise to promote the sale, use or other -dealings in these Data Files or Software without prior written -authorization of the copyright holder. - ----------------------------------------------------------------------- - -Third-Party Software Licenses - -This section contains third-party software notices and/or additional -terms for licensed third-party software components included within ICU -libraries. - ----------------------------------------------------------------------- - -ICU License - ICU 1.8.1 to ICU 57.1 - -COPYRIGHT AND PERMISSION NOTICE - -Copyright (c) 1995-2016 International Business Machines Corporation and others -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, provided that the above -copyright notice(s) and this permission notice appear in all copies of -the Software and that both the above copyright notice(s) and this -permission notice appear in supporting documentation. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY -SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, use -or other dealings in this Software without prior written authorization -of the copyright holder. - -All trademarks and registered trademarks mentioned herein are the -property of their respective owners. - ----------------------------------------------------------------------- - -Chinese/Japanese Word Break Dictionary Data (cjdict.txt) - - # The Google Chrome software developed by Google is licensed under - # the BSD license. Other software included in this distribution is - # provided under other licenses, as set forth below. - # - # The BSD License - # http://opensource.org/licenses/bsd-license.php - # Copyright (C) 2006-2008, Google Inc. - # - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are met: - # - # Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. - # Redistributions in binary form must reproduce the above - # copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided with - # the distribution. - # Neither the name of Google Inc. nor the names of its - # contributors may be used to endorse or promote products derived from - # this software without specific prior written permission. - # - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - # - # - # The word list in cjdict.txt are generated by combining three word lists - # listed below with further processing for compound word breaking. The - # frequency is generated with an iterative training against Google web - # corpora. - # - # * Libtabe (Chinese) - # - https://sourceforge.net/project/?group_id=1519 - # - Its license terms and conditions are shown below. - # - # * IPADIC (Japanese) - # - http://chasen.aist-nara.ac.jp/chasen/distribution.html - # - Its license terms and conditions are shown below. - # - # ---------COPYING.libtabe ---- BEGIN-------------------- - # - # /* - # * Copyright (c) 1999 TaBE Project. - # * Copyright (c) 1999 Pai-Hsiang Hsiao. - # * All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the TaBE Project nor the names of its - # * contributors may be used to endorse or promote products derived - # * from this software without specific prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # /* - # * Copyright (c) 1999 Computer Systems and Communication Lab, - # * Institute of Information Science, Academia - # * Sinica. All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the Computer Systems and Communication Lab - # * nor the names of its contributors may be used to endorse or - # * promote products derived from this software without specific - # * prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, - # University of Illinois - # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 - # - # ---------------COPYING.libtabe-----END-------------------------------- - # - # - # ---------------COPYING.ipadic-----BEGIN------------------------------- - # - # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science - # and Technology. All Rights Reserved. - # - # Use, reproduction, and distribution of this software is permitted. - # Any copy of this software, whether in its original form or modified, - # must include both the above copyright notice and the following - # paragraphs. - # - # Nara Institute of Science and Technology (NAIST), - # the copyright holders, disclaims all warranties with regard to this - # software, including all implied warranties of merchantability and - # fitness, in no event shall NAIST be liable for - # any special, indirect or consequential damages or any damages - # whatsoever resulting from loss of use, data or profits, whether in an - # action of contract, negligence or other tortuous action, arising out - # of or in connection with the use or performance of this software. - # - # A large portion of the dictionary entries - # originate from ICOT Free Software. The following conditions for ICOT - # Free Software applies to the current dictionary as well. - # - # Each User may also freely distribute the Program, whether in its - # original form or modified, to any third party or parties, PROVIDED - # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear - # on, or be attached to, the Program, which is distributed substantially - # in the same form as set out herein and that such intended - # distribution, if actually made, will neither violate or otherwise - # contravene any of the laws and regulations of the countries having - # jurisdiction over the User or the intended distribution itself. - # - # NO WARRANTY - # - # The program was produced on an experimental basis in the course of the - # research and development conducted during the project and is provided - # to users as so produced on an experimental basis. Accordingly, the - # program is provided without any warranty whatsoever, whether express, - # implied, statutory or otherwise. The term "warranty" used herein - # includes, but is not limited to, any warranty of the quality, - # performance, merchantability and fitness for a particular purpose of - # the program and the nonexistence of any infringement or violation of - # any right of any third party. - # - # Each user of the program will agree and understand, and be deemed to - # have agreed and understood, that there is no warranty whatsoever for - # the program and, accordingly, the entire risk arising from or - # otherwise connected with the program is assumed by the user. - # - # Therefore, neither ICOT, the copyright holder, or any other - # organization that participated in or was otherwise related to the - # development of the program and their respective officials, directors, - # officers and other employees shall be held liable for any and all - # damages, including, without limitation, general, special, incidental - # and consequential damages, arising out of or otherwise in connection - # with the use or inability to use the program or any product, material - # or result produced or otherwise obtained by using the program, - # regardless of whether they have been advised of, or otherwise had - # knowledge of, the possibility of such damages at any time during the - # project or thereafter. Each user will be deemed to have agreed to the - # foregoing by his or her commencement of use of the program. The term - # "use" as used herein includes, but is not limited to, the use, - # modification, copying and distribution of the program and the - # production of secondary products from the program. - # - # In the case where the program, whether in its original form or - # modified, was distributed or delivered to or received by a user from - # any person, organization or entity other than ICOT, unless it makes or - # grants independently of ICOT any specific warranty to the user in - # writing, such person, organization or entity, will also be exempted - # from and not be held liable to the user for any such damages as noted - # above as far as the program is concerned. - # - # ---------------COPYING.ipadic-----END---------------------------------- - ----------------------------------------------------------------------- - -Lao Word Break Dictionary Data (laodict.txt) - - # Copyright (C) 2016 and later: Unicode, Inc. and others. - # License & terms of use: http://www.unicode.org/copyright.html - # Copyright (c) 2015 International Business Machines Corporation - # and others. All Rights Reserved. - # - # Project: https://github.com/rober42539/lao-dictionary - # Dictionary: https://github.com/rober42539/lao-dictionary/laodict.txt - # License: https://github.com/rober42539/lao-dictionary/LICENSE.txt - # (copied below) - # - # This file is derived from the above dictionary version of Nov 22, 2020 - # ---------------------------------------------------------------------- - # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are met: - # - # Redistributions of source code must retain the above copyright notice, this - # list of conditions and the following disclaimer. Redistributions in binary - # form must reproduce the above copyright notice, this list of conditions and - # the following disclaimer in the documentation and/or other materials - # provided with the distribution. - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # OF THE POSSIBILITY OF SUCH DAMAGE. - # -------------------------------------------------------------------------- - ----------------------------------------------------------------------- - -Burmese Word Break Dictionary Data (burmesedict.txt) - - # Copyright (c) 2014 International Business Machines Corporation - # and others. All Rights Reserved. - # - # This list is part of a project hosted at: - # github.com/kanyawtech/myanmar-karen-word-lists - # - # -------------------------------------------------------------------------- - # Copyright (c) 2013, LeRoy Benjamin Sharon - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions - # are met: Redistributions of source code must retain the above - # copyright notice, this list of conditions and the following - # disclaimer. Redistributions in binary form must reproduce the - # above copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided - # with the distribution. - # - # Neither the name Myanmar Karen Word Lists, nor the names of its - # contributors may be used to endorse or promote products derived - # from this software without specific prior written permission. - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS - # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - # SUCH DAMAGE. - # -------------------------------------------------------------------------- - ----------------------------------------------------------------------- - -Time Zone Database - - ICU uses the public domain data and code derived from Time Zone -Database for its time zone support. The ownership of the TZ database -is explained in BCP 175: Procedure for Maintaining the Time Zone -Database section 7. - - # 7. Database Ownership - # - # The TZ database itself is not an IETF Contribution or an IETF - # document. Rather it is a pre-existing and regularly updated work - # that is in the public domain, and is intended to remain in the - # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do - # not apply to the TZ Database or contributions that individuals make - # to it. Should any claims be made and substantiated against the TZ - # Database, the organization that is providing the IANA - # Considerations defined in this RFC, under the memorandum of - # understanding with the IETF, currently ICANN, may act in accordance - # with all competent court orders. No ownership claims will be made - # by ICANN or the IETF Trust on the database or the code. Any person - # making a contribution to the database or code waives all rights to - # future claims in that contribution or in the TZ Database. - ----------------------------------------------------------------------- - -Google double-conversion - -Copyright 2006-2011, the V8 project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- - -File: aclocal.m4 (only for ICU4C) -Section: pkg.m4 - Macros to locate and utilise pkg-config. - - -Copyright © 2004 Scott James Remnant . -Copyright © 2012-2015 Dan Nicholson - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -02111-1307, USA. - -As a special exception to the GNU General Public License, if you -distribute this file as part of a program that contains a -configuration script generated by Autoconf, you may include it under -the same distribution terms that you use for the rest of that -program. - - -(The condition for the exception is fulfilled because -ICU4C includes a configuration script generated by Autoconf, -namely the `configure` script.) - ----------------------------------------------------------------------- - -File: config.guess (only for ICU4C) - - -This file is free software; you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, see . - -As a special exception to the GNU General Public License, if you -distribute this file as part of a program that contains a -configuration script generated by Autoconf, you may include it under -the same distribution terms that you use for the rest of that -program. This Exception is an additional permission under section 7 -of the GNU General Public License, version 3 ("GPLv3"). - - -(The condition for the exception is fulfilled because -ICU4C includes a configuration script generated by Autoconf, -namely the `configure` script.) - ----------------------------------------------------------------------- - -File: install-sh (only for ICU4C) - - -Copyright 1991 by the Massachusetts Institute of Technology - -Permission to use, copy, modify, distribute, and sell this software and its -documentation for any purpose is hereby granted without fee, provided that -the above copyright notice appear in all copies and that both that -copyright notice and this permission notice appear in supporting -documentation, and that the name of M.I.T. not be used in advertising or -publicity pertaining to distribution of the software without specific, -written prior permission. M.I.T. makes no representations about the -suitability of this software for any purpose. It is provided "as is" -without express or implied warranty. diff --git a/Vendor/SwiftWindowsRuntime/sources.json b/Vendor/SwiftWindowsRuntime/sources.json deleted file mode 100644 index 5930af2..0000000 --- a/Vendor/SwiftWindowsRuntime/sources.json +++ /dev/null @@ -1,239 +0,0 @@ -{ - "schema_version": 1, - "swift_release": "6.2.3", - "swift_source_tag": "swift-6.2.3-RELEASE", - "sources": [ - { - "repository": "swiftlang/swift", - "tag": "swift-6.2.3-RELEASE", - "revision": "484e622d1c0afcae5b12a31c090a74ad0901e44f", - "source_path": "LICENSE.txt", - "url": "https://raw.githubusercontent.com/swiftlang/swift/484e622d1c0afcae5b12a31c090a74ad0901e44f/LICENSE.txt", - "sha256": "770af8291f708538d8ff885a0bbc4e045cd700531741c4f99528d435c14d7f55", - "bytes": 11751, - "file": "swift/LICENSE.txt" - }, - { - "repository": "swiftlang/swift-corelibs-foundation", - "tag": "swift-6.2.3-RELEASE", - "revision": "edfa17dd3623877a38967ae3edd356942fe4f655", - "source_path": "LICENSE", - "url": "https://raw.githubusercontent.com/swiftlang/swift-corelibs-foundation/edfa17dd3623877a38967ae3edd356942fe4f655/LICENSE", - "sha256": "0ca84095d1de77cad1a866ebbad28f8db98a0826c6055d92b0608c5baa1643e2", - "bytes": 11759, - "file": "swift-corelibs-foundation/LICENSE" - }, - { - "repository": "swiftlang/swift-foundation", - "tag": "swift-6.2.3-RELEASE", - "revision": "7242fa9cb8993aaf9a0a72522206b96a45da9c28", - "source_path": "LICENSE.md", - "url": "https://raw.githubusercontent.com/swiftlang/swift-foundation/7242fa9cb8993aaf9a0a72522206b96a45da9c28/LICENSE.md", - "sha256": "2245a990b635558be210fb3eb4f8a6f7a49aebc0fefbf5859146a65ddc7ddcf3", - "bytes": 11750, - "file": "swift-foundation/LICENSE.md" - }, - { - "repository": "swiftlang/swift-foundation", - "tag": "swift-6.2.3-RELEASE", - "revision": "7242fa9cb8993aaf9a0a72522206b96a45da9c28", - "source_path": "NOTICE.txt", - "url": "https://raw.githubusercontent.com/swiftlang/swift-foundation/7242fa9cb8993aaf9a0a72522206b96a45da9c28/NOTICE.txt", - "sha256": "c2e757911b86a76230099804831d8d847a865e88b5912171627492a7a49f0456", - "bytes": 15315, - "file": "swift-foundation/NOTICE.txt" - }, - { - "repository": "swiftlang/swift-foundation-icu", - "tag": "swift-6.2.3-RELEASE", - "revision": "ddd11fa3f970bd34924c418835986167b8e6efeb", - "source_path": "LICENSE.md", - "url": "https://raw.githubusercontent.com/swiftlang/swift-foundation-icu/ddd11fa3f970bd34924c418835986167b8e6efeb/LICENSE.md", - "sha256": "2245a990b635558be210fb3eb4f8a6f7a49aebc0fefbf5859146a65ddc7ddcf3", - "bytes": 11750, - "file": "swift-foundation-icu/LICENSE.md" - }, - { - "repository": "swiftlang/swift-corelibs-libdispatch", - "tag": "swift-6.2.3-RELEASE", - "revision": "2df91f94651f2d924d7506c9d14685929386d779", - "source_path": "LICENSE", - "url": "https://raw.githubusercontent.com/swiftlang/swift-corelibs-libdispatch/2df91f94651f2d924d7506c9d14685929386d779/LICENSE", - "sha256": "0ca84095d1de77cad1a866ebbad28f8db98a0826c6055d92b0608c5baa1643e2", - "bytes": 11759, - "file": "swift-corelibs-libdispatch/LICENSE" - }, - { - "repository": "swiftlang/swift-experimental-string-processing", - "tag": "swift-6.2.3-RELEASE", - "revision": "91177e6225c63e885872b83d48e254b1270fa15a", - "source_path": "LICENSE.txt", - "url": "https://raw.githubusercontent.com/swiftlang/swift-experimental-string-processing/91177e6225c63e885872b83d48e254b1270fa15a/LICENSE.txt", - "sha256": "770af8291f708538d8ff885a0bbc4e045cd700531741c4f99528d435c14d7f55", - "bytes": 11751, - "file": "swift-experimental-string-processing/LICENSE.txt" - }, - { - "repository": "curl/curl", - "tag": "curl-8_9_1", - "revision": "83bedbd730d62b83744cc26fa0433d3f6e2e4cd6", - "source_path": "COPYING", - "url": "https://raw.githubusercontent.com/curl/curl/83bedbd730d62b83744cc26fa0433d3f6e2e4cd6/COPYING", - "sha256": "adb1fc06547fd136244179809f7b7c2d2ae6c4534f160aa513af9b6a12866a32", - "bytes": 1088, - "file": "curl/COPYING" - }, - { - "repository": "madler/zlib", - "tag": "v1.3.1", - "revision": "51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf", - "source_path": "LICENSE", - "url": "https://raw.githubusercontent.com/madler/zlib/51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf/LICENSE", - "sha256": "845efc77857d485d91fb3e0b884aaa929368c717ae8186b66fe1ed2495753243", - "bytes": 1002, - "file": "zlib/LICENSE" - }, - { - "repository": "unicode-org/icu", - "tag": "release-74-1", - "revision": "9edac7b78327a1cb58db29e2714b15f9fa14e4d7", - "source_path": "LICENSE", - "url": "https://raw.githubusercontent.com/unicode-org/icu/9edac7b78327a1cb58db29e2714b15f9fa14e4d7/LICENSE", - "sha256": "17510cf7a58b4879b887ec05a45d72cf1b73544dd9ec7e72f20110ed104229ee", - "bytes": 25185, - "file": "icu/LICENSE", - "relationship": "FoundationICU vendored uvernum.h identifies ICU74.1.0; includes original Unicode/ICU and third-party notices." - }, - { - "repository": "apple-oss-distributions/ICU", - "tag": "ICU-74000.403", - "revision": "c9f69cae28475b0a32288a48fe223d07b1be4fd3", - "source_path": "LICENSE", - "url": "https://raw.githubusercontent.com/apple-oss-distributions/ICU/c9f69cae28475b0a32288a48fe223d07b1be4fd3/LICENSE", - "sha256": "92841ff27a1b2d1030b00e0958af4d1f70353ff42c5dad7041f1950e909ccc5e", - "bytes": 22559, - "file": "apple-icu/LICENSE", - "relationship": "Supplemental Apple ICU74 distribution notices. FoundationICU README identifies Apple OSS ICU extraction, but its exact Apple extraction tag is not established by this notice record." - } - ], - "runtime_libraries": { - "swiftcore.dll": { - "sources": [ - "swift/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "swift_concurrency.dll": { - "sources": [ - "swift/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "swiftsynchronization.dll": { - "sources": [ - "swift/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "swiftwinsdk.dll": { - "sources": [ - "swift/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "swiftcrt.dll": { - "sources": [ - "swift/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "foundation.dll": { - "sources": [ - "swift-corelibs-foundation/LICENSE" - ], - "license_scope": "open-source" - }, - "foundationessentials.dll": { - "sources": [ - "swift-foundation/LICENSE.md", - "swift-foundation/NOTICE.txt" - ], - "license_scope": "open-source" - }, - "foundationinternationalization.dll": { - "sources": [ - "swift-foundation/LICENSE.md", - "swift-foundation/NOTICE.txt" - ], - "license_scope": "open-source" - }, - "foundationnetworking.dll": { - "sources": [ - "swift-corelibs-foundation/LICENSE", - "curl/COPYING", - "zlib/LICENSE" - ], - "license_scope": "open-source" - }, - "dispatch.dll": { - "sources": [ - "swift-corelibs-libdispatch/LICENSE" - ], - "license_scope": "open-source" - }, - "blocksruntime.dll": { - "sources": [ - "swift-corelibs-libdispatch/LICENSE" - ], - "license_scope": "open-source" - }, - "swiftdispatch.dll": { - "sources": [ - "swift/LICENSE.txt", - "swift-corelibs-libdispatch/LICENSE" - ], - "license_scope": "open-source" - }, - "swift_regexparser.dll": { - "sources": [ - "swift-experimental-string-processing/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "swift_stringprocessing.dll": { - "sources": [ - "swift-experimental-string-processing/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "swiftregexbuilder.dll": { - "sources": [ - "swift-experimental-string-processing/LICENSE.txt" - ], - "license_scope": "open-source" - }, - "_foundationicu.dll": { - "sources": [ - "swift-foundation-icu/LICENSE.md", - "icu/LICENSE", - "apple-icu/LICENSE" - ], - "license_scope": "open-source" - }, - "msvcp140.dll": { - "sources": [], - "license_scope": "Microsoft Visual C++ Redistributable", - "redistribution_reference": "https://learn.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files?view=msvc-170" - }, - "vcruntime140.dll": { - "sources": [], - "license_scope": "Microsoft Visual C++ Redistributable", - "redistribution_reference": "https://learn.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files?view=msvc-170" - }, - "vcruntime140_1.dll": { - "sources": [], - "license_scope": "Microsoft Visual C++ Redistributable", - "redistribution_reference": "https://learn.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files?view=msvc-170" - } - } -} diff --git a/Vendor/SwiftWindowsRuntime/swift-corelibs-foundation/LICENSE b/Vendor/SwiftWindowsRuntime/swift-corelibs-foundation/LICENSE deleted file mode 100644 index f92d51a..0000000 --- a/Vendor/SwiftWindowsRuntime/swift-corelibs-foundation/LICENSE +++ /dev/null @@ -1,211 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -### Runtime Library Exception to the Apache 2.0 License: ### - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. diff --git a/Vendor/SwiftWindowsRuntime/swift-corelibs-libdispatch/LICENSE b/Vendor/SwiftWindowsRuntime/swift-corelibs-libdispatch/LICENSE deleted file mode 100644 index f92d51a..0000000 --- a/Vendor/SwiftWindowsRuntime/swift-corelibs-libdispatch/LICENSE +++ /dev/null @@ -1,211 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -### Runtime Library Exception to the Apache 2.0 License: ### - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. diff --git a/Vendor/SwiftWindowsRuntime/swift-experimental-string-processing/LICENSE.txt b/Vendor/SwiftWindowsRuntime/swift-experimental-string-processing/LICENSE.txt deleted file mode 100644 index 61b0c78..0000000 --- a/Vendor/SwiftWindowsRuntime/swift-experimental-string-processing/LICENSE.txt +++ /dev/null @@ -1,211 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. diff --git a/Vendor/SwiftWindowsRuntime/swift-foundation-icu/LICENSE.md b/Vendor/SwiftWindowsRuntime/swift-foundation-icu/LICENSE.md deleted file mode 100644 index 004d67a..0000000 --- a/Vendor/SwiftWindowsRuntime/swift-foundation-icu/LICENSE.md +++ /dev/null @@ -1,211 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. \ No newline at end of file diff --git a/Vendor/SwiftWindowsRuntime/swift-foundation/LICENSE.md b/Vendor/SwiftWindowsRuntime/swift-foundation/LICENSE.md deleted file mode 100644 index 004d67a..0000000 --- a/Vendor/SwiftWindowsRuntime/swift-foundation/LICENSE.md +++ /dev/null @@ -1,211 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. \ No newline at end of file diff --git a/Vendor/SwiftWindowsRuntime/swift-foundation/NOTICE.txt b/Vendor/SwiftWindowsRuntime/swift-foundation/NOTICE.txt deleted file mode 100644 index b8568b2..0000000 --- a/Vendor/SwiftWindowsRuntime/swift-foundation/NOTICE.txt +++ /dev/null @@ -1,279 +0,0 @@ - The Foundation Project - ====================== - -This product contains a derivation of Fabian Fett's 'Base64.swift'. - - * LICENSE (Apache License 2.0): - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - * HOMEPAGE: - * https://github.com/swift-extras/swift-extras-base64 - ---- - -This product contains a derivation of Daniel Lemire's 'chromiumbase64' -implementation in 'fastbase64'. - - * LICENSE: - - Copyright (c) 2015-2016, Wojciech Muła, Alfred Klomp, Daniel Lemire - (Unless otherwise stated in the source code) - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - * HOMEPAGE: - * https://github.com/lemire/fastbase64 - ---- - -This product contains a derivation of Nick Galbreath's 'base64' -implementation in 'stringencoders'. - - * LICENSE (The MIT License): - - The MIT License (MIT) - - Copyright (c) 2016 Nick Galbreath - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - * HOMEPAGE: - * https://github.com/client9/stringencoders diff --git a/Vendor/SwiftWindowsRuntime/swift/LICENSE.txt b/Vendor/SwiftWindowsRuntime/swift/LICENSE.txt deleted file mode 100644 index 61b0c78..0000000 --- a/Vendor/SwiftWindowsRuntime/swift/LICENSE.txt +++ /dev/null @@ -1,211 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. diff --git a/Vendor/SwiftWindowsRuntime/zlib/LICENSE b/Vendor/SwiftWindowsRuntime/zlib/LICENSE deleted file mode 100644 index ab8ee6f..0000000 --- a/Vendor/SwiftWindowsRuntime/zlib/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright notice: - - (C) 1995-2022 Jean-loup Gailly and Mark Adler - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jean-loup Gailly Mark Adler - jloup@gzip.org madler@alumni.caltech.edu diff --git a/computer-mcp-plugin.toml b/computer-mcp-plugin.toml index 6b053d3..80ed9e3 100644 --- a/computer-mcp-plugin.toml +++ b/computer-mcp-plugin.toml @@ -6,20 +6,15 @@ description = 'Codex execution and protocol inspection through standard MCP.' [[mcp]] id = 'app-server' transport = 'stdio' -executable = { platform_paths = { macos = 'bin/codex-mcp-adapter', windows = 'bin/codex-mcp-adapter.exe' } } +executable = { platform_paths = { macos = 'bin/codex-mcp-adapter' } } capabilities = ['tools'] [compatibility] minimum_host = '0.1.0' -platforms = ['macos', 'windows'] +platforms = ['macos'] architectures = ['arm64', 'x86_64'] [[compatibility.artifacts]] name = 'codex-plugin-macos-arm64.zip' platforms = ['macos'] architectures = ['arm64'] - -[[compatibility.artifacts]] -name = 'codex-plugin-windows-x86_64.zip' -platforms = ['windows'] -architectures = ['x86_64']