From 5152f4b0158c5af5b5464ac7e6719fa881783ee6 Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Sat, 21 Feb 2026 09:44:29 -0800 Subject: [PATCH 1/2] new sidecar --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cbd6bd3..11afba2 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,69 @@ npm install @predicatesystems/authority ### Sidecar Prerequisite -This SDK requires the Predicate Authority sidecar running locally. Install and start it: +This SDK requires the Predicate Authority sidecar running locally. You can use either the Python or Rust sidecar: + +**Option A: Python sidecar (requires Python 3.11+)** ```bash -# Install via pip (requires Python 3.11+) pip install predicate-authority -# Start the sidecar -predicate-authorityd --port 8787 +# Local mode +predicate-authorityd --port 8787 --mode local_only --policy-file policy.json +``` + +**Option B: Rust sidecar (recommended for production)** + +Download from [rust-predicate-authorityd releases](https://github.com/PredicateSystems/predicate-authority-sidecar/releases): + +```bash +# Local mode +./predicate-authorityd run --port 8787 --mode local_only --policy-file policy.json +``` + +### Cloud-connected sidecar (control-plane sync) + +Connect the sidecar to Predicate Authority control-plane for policy sync, revocation push, and audit forwarding: + +```bash +export PREDICATE_API_KEY="your-api-key" + +# Python sidecar +predicate-authorityd \ + --host 127.0.0.1 \ + --port 8787 \ + --mode cloud_connected \ + --control-plane-url https://api.predicatesystems.dev \ + --tenant-id your-tenant \ + --project-id your-project \ + --predicate-api-key $PREDICATE_API_KEY \ + --sync-enabled + +# Or Rust sidecar +./predicate-authorityd run \ + --mode cloud_connected \ + --control-plane-url https://api.predicatesystems.dev \ + --tenant-id your-tenant \ + --project-id your-project \ + --predicate-api-key $PREDICATE_API_KEY \ + --sync-enabled +``` + +### Local IdP mode (development/air-gapped) + +For development or air-gapped environments without external IdP: + +```bash +export LOCAL_IDP_SIGNING_KEY="replace-with-strong-secret" + +predicate-authorityd \ + --host 127.0.0.1 \ + --port 8787 \ + --mode local_only \ + --policy-file policy.json \ + --identity-mode local-idp \ + --local-idp-issuer "http://localhost/predicate-local-idp" \ + --local-idp-audience "api://predicate-authority" ``` ## Quick Start From a8393886afd4d7adcafd10ba3c77991c8a2f28df Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Sat, 21 Feb 2026 10:21:32 -0800 Subject: [PATCH 2/2] receive sidecar from rust repo --- .github/workflows/update-sidecar-binaries.yml | 184 ++++++++++++++ README.md | 59 ++--- packages/authorityd-darwin-arm64/bin/.gitkeep | 1 + packages/authorityd-darwin-arm64/package.json | 18 ++ packages/authorityd-darwin-x64/bin/.gitkeep | 1 + packages/authorityd-darwin-x64/package.json | 18 ++ packages/authorityd-linux-arm64/bin/.gitkeep | 1 + packages/authorityd-linux-arm64/package.json | 18 ++ packages/authorityd-linux-x64/bin/.gitkeep | 1 + packages/authorityd-linux-x64/package.json | 18 ++ packages/authorityd-win32-x64/bin/.gitkeep | 1 + packages/authorityd-win32-x64/package.json | 18 ++ packages/authorityd/README.md | 152 ++++++++++++ packages/authorityd/package.json | 48 ++++ packages/authorityd/scripts/postinstall.js | 123 ++++++++++ packages/authorityd/src/index.ts | 227 ++++++++++++++++++ packages/authorityd/tsconfig.json | 20 ++ 17 files changed, 867 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/update-sidecar-binaries.yml create mode 100644 packages/authorityd-darwin-arm64/bin/.gitkeep create mode 100644 packages/authorityd-darwin-arm64/package.json create mode 100644 packages/authorityd-darwin-x64/bin/.gitkeep create mode 100644 packages/authorityd-darwin-x64/package.json create mode 100644 packages/authorityd-linux-arm64/bin/.gitkeep create mode 100644 packages/authorityd-linux-arm64/package.json create mode 100644 packages/authorityd-linux-x64/bin/.gitkeep create mode 100644 packages/authorityd-linux-x64/package.json create mode 100644 packages/authorityd-win32-x64/bin/.gitkeep create mode 100644 packages/authorityd-win32-x64/package.json create mode 100644 packages/authorityd/README.md create mode 100644 packages/authorityd/package.json create mode 100644 packages/authorityd/scripts/postinstall.js create mode 100644 packages/authorityd/src/index.ts create mode 100644 packages/authorityd/tsconfig.json diff --git a/.github/workflows/update-sidecar-binaries.yml b/.github/workflows/update-sidecar-binaries.yml new file mode 100644 index 0000000..19229cc --- /dev/null +++ b/.github/workflows/update-sidecar-binaries.yml @@ -0,0 +1,184 @@ +name: Update Sidecar Binaries + +on: + repository_dispatch: + types: [sidecar-release] + workflow_dispatch: + inputs: + version: + description: 'Sidecar version tag (e.g., v0.1.0)' + required: true + type: string + +env: + SIDECAR_REPO: PredicateSystems/predicate-authority-sidecar + +jobs: + update-binaries: + name: Update ${{ matrix.platform }} binary + runs-on: ubuntu-latest + strategy: + matrix: + include: + - platform: darwin-arm64 + artifact: predicate-authorityd-darwin-arm64.tar.gz + binary_name: predicate-authorityd + - platform: darwin-x64 + artifact: predicate-authorityd-darwin-x64.tar.gz + binary_name: predicate-authorityd + - platform: linux-x64 + artifact: predicate-authorityd-linux-x64.tar.gz + binary_name: predicate-authorityd + - platform: linux-arm64 + artifact: predicate-authorityd-linux-arm64.tar.gz + binary_name: predicate-authorityd + - platform: win32-x64 + artifact: predicate-authorityd-windows-x64.zip + binary_name: predicate-authorityd.exe + + steps: + - uses: actions/checkout@v4 + + - name: Get version + id: version + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + VERSION="${{ github.event.client_payload.version }}" + else + VERSION="${{ inputs.version }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Using sidecar version: $VERSION" + + - name: Download sidecar binary + run: | + VERSION="${{ steps.version.outputs.version }}" + ARTIFACT="${{ matrix.artifact }}" + DOWNLOAD_URL="https://github.com/${{ env.SIDECAR_REPO }}/releases/download/${VERSION}/${ARTIFACT}" + + echo "Downloading from: $DOWNLOAD_URL" + curl -fsSL -o "$ARTIFACT" "$DOWNLOAD_URL" || { + echo "Failed to download $ARTIFACT" + exit 1 + } + + - name: Extract and place binary + run: | + ARTIFACT="${{ matrix.artifact }}" + BINARY="${{ matrix.binary_name }}" + PLATFORM="${{ matrix.platform }}" + TARGET_DIR="packages/authorityd-${PLATFORM}/bin" + + mkdir -p "$TARGET_DIR" + + if [[ "$ARTIFACT" == *.tar.gz ]]; then + tar -xzf "$ARTIFACT" + else + unzip -o "$ARTIFACT" + fi + + mv "$BINARY" "$TARGET_DIR/" + chmod +x "$TARGET_DIR/$BINARY" || true + + echo "Binary placed at: $TARGET_DIR/$BINARY" + ls -la "$TARGET_DIR/" + + - name: Upload platform artifact + uses: actions/upload-artifact@v4 + with: + name: authorityd-${{ matrix.platform }} + path: packages/authorityd-${{ matrix.platform }}/bin/ + + publish-packages: + name: Publish npm packages + needs: update-binaries + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Download all platform binaries + uses: actions/download-artifact@v4 + with: + path: downloaded-artifacts + + - name: Place binaries in packages + run: | + for platform in darwin-arm64 darwin-x64 linux-x64 linux-arm64 win32-x64; do + mkdir -p "packages/authorityd-${platform}/bin" + cp -r "downloaded-artifacts/authorityd-${platform}/"* "packages/authorityd-${platform}/bin/" || true + ls -la "packages/authorityd-${platform}/bin/" || true + done + + - name: Get version + id: version + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + VERSION="${{ github.event.client_payload.version }}" + else + VERSION="${{ inputs.version }}" + fi + # Remove 'v' prefix if present + VERSION="${VERSION#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Update package versions + run: | + VERSION="${{ steps.version.outputs.version }}" + + # Update all authorityd package versions + for pkg in packages/authorityd packages/authorityd-darwin-arm64 packages/authorityd-darwin-x64 packages/authorityd-linux-x64 packages/authorityd-linux-arm64 packages/authorityd-win32-x64; do + if [ -f "$pkg/package.json" ]; then + jq --arg v "$VERSION" '.version = $v' "$pkg/package.json" > tmp.json && mv tmp.json "$pkg/package.json" + echo "Updated $pkg to version $VERSION" + fi + done + + # Update optionalDependencies versions in main authorityd package + jq --arg v "$VERSION" ' + .optionalDependencies |= with_entries(.value = $v) + ' packages/authorityd/package.json > tmp.json && mv tmp.json packages/authorityd/package.json + + - name: Build main authorityd package + working-directory: packages/authorityd + run: | + npm install + npm run build + + - name: Publish platform packages + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + for platform in darwin-arm64 darwin-x64 linux-x64 linux-arm64 win32-x64; do + pkg="packages/authorityd-${platform}" + if [ -f "$pkg/package.json" ]; then + echo "Publishing $pkg..." + cd "$pkg" + npm publish --access public --provenance || echo "Failed to publish $pkg (may already exist)" + cd ../.. + fi + done + + - name: Publish main authorityd package + working-directory: packages/authorityd + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --access public --provenance + + - name: Create commit with updated versions + run: | + VERSION="${{ steps.version.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add packages/*/package.json + git commit -m "chore: update sidecar packages to v${VERSION}" || echo "No changes to commit" + git push diff --git a/README.md b/README.md index 11afba2..003f2be 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ [![License](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue.svg)](LICENSE) [![npm](https://img.shields.io/npm/v/@predicatesystems/authority.svg)](https://www.npmjs.com/package/@predicatesystems/authority) -`@predicatesystems/authority` is the TypeScript SDK companion to the Python -`predicate-authorityd` sidecar from [predicate-authority (Python)](https://github.com/PredicateSystems/predicate-authority). It keeps authority +`@predicatesystems/authority` is the TypeScript SDK for Predicate Authority. It keeps authority decisions in the sidecar and gives Node/TS runtimes a thin, typed client for fail-closed pre-execution checks. @@ -33,21 +32,7 @@ This TS repository currently focuses on: Out of scope for this package: - re-implementing policy engine or mandate logic in TypeScript, -- replacing Python sidecar/control-plane authority logic. - -## Known Python Parity Baseline - -This package targets compatibility with the current Python authority baseline in -[predicate-authority (Python)](https://github.com/PredicateSystems/predicate-authority): - -- sidecar authorize route: `POST /v1/authorize` (`/authorize` compat alias), -- mandate/token baseline: ES256-default signing + standard JWT claim envelope, -- revocation baseline: explicit cascade semantics and global kill-switch runtime behavior, -- control-plane baseline: long-poll policy/revocation sync (runtime baseline), -- control-plane write hardening: replay freshness headers/signature support on Python client paths. - -The TS SDK should preserve compatibility with these runtime behaviors before -adding TS-specific extensions. +- replacing sidecar/control-plane authority logic. ## Installation @@ -57,23 +42,25 @@ npm install @predicatesystems/authority ### Sidecar Prerequisite -This SDK requires the Predicate Authority sidecar running locally. You can use either the Python or Rust sidecar: - -**Option A: Python sidecar (requires Python 3.11+)** +This SDK requires the **Predicate Authority Sidecar** daemon to be running. The sidecar is a lightweight Rust binary that handles policy evaluation and mandate signing. -```bash -pip install predicate-authority +| Resource | Link | +|----------|------| +| Sidecar Repository | [rust-predicate-authorityd](https://github.com/PredicateSystems/predicate-authority-sidecar) | +| Download Binaries | [Latest Releases](https://github.com/PredicateSystems/predicate-authority-sidecar/releases) | +| License | MIT / Apache 2.0 | -# Local mode -predicate-authorityd --port 8787 --mode local_only --policy-file policy.json -``` +### Quick Sidecar Setup -**Option B: Rust sidecar (recommended for production)** +```bash +# Download the latest release for your platform +# Linux x64, macOS x64/ARM64, Windows x64 available -Download from [rust-predicate-authorityd releases](https://github.com/PredicateSystems/predicate-authority-sidecar/releases): +# Extract and run +tar -xzf predicate-authorityd-*.tar.gz +chmod +x predicate-authorityd -```bash -# Local mode +# Start with a policy file (local mode) ./predicate-authorityd run --port 8787 --mode local_only --policy-file policy.json ``` @@ -84,8 +71,7 @@ Connect the sidecar to Predicate Authority control-plane for policy sync, revoca ```bash export PREDICATE_API_KEY="your-api-key" -# Python sidecar -predicate-authorityd \ +./predicate-authorityd run \ --host 127.0.0.1 \ --port 8787 \ --mode cloud_connected \ @@ -94,15 +80,6 @@ predicate-authorityd \ --project-id your-project \ --predicate-api-key $PREDICATE_API_KEY \ --sync-enabled - -# Or Rust sidecar -./predicate-authorityd run \ - --mode cloud_connected \ - --control-plane-url https://api.predicatesystems.dev \ - --tenant-id your-tenant \ - --project-id your-project \ - --predicate-api-key $PREDICATE_API_KEY \ - --sync-enabled ``` ### Local IdP mode (development/air-gapped) @@ -112,7 +89,7 @@ For development or air-gapped environments without external IdP: ```bash export LOCAL_IDP_SIGNING_KEY="replace-with-strong-secret" -predicate-authorityd \ +./predicate-authorityd run \ --host 127.0.0.1 \ --port 8787 \ --mode local_only \ diff --git a/packages/authorityd-darwin-arm64/bin/.gitkeep b/packages/authorityd-darwin-arm64/bin/.gitkeep new file mode 100644 index 0000000..4e9b849 --- /dev/null +++ b/packages/authorityd-darwin-arm64/bin/.gitkeep @@ -0,0 +1 @@ +# Binary placeholder - populated by CI from rust-predicate-authorityd releases diff --git a/packages/authorityd-darwin-arm64/package.json b/packages/authorityd-darwin-arm64/package.json new file mode 100644 index 0000000..ad9bcd3 --- /dev/null +++ b/packages/authorityd-darwin-arm64/package.json @@ -0,0 +1,18 @@ +{ + "name": "@predicatesystems/authorityd-darwin-arm64", + "version": "0.1.0", + "description": "Predicate Authority Sidecar binary for macOS ARM64 (Apple Silicon)", + "license": "(MIT OR Apache-2.0)", + "repository": { + "type": "git", + "url": "https://github.com/PredicateSystems/ts-predicate-authority.git", + "directory": "packages/authorityd-darwin-arm64" + }, + "os": ["darwin"], + "cpu": ["arm64"], + "files": ["bin"], + "preferUnplugged": true, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/authorityd-darwin-x64/bin/.gitkeep b/packages/authorityd-darwin-x64/bin/.gitkeep new file mode 100644 index 0000000..4e9b849 --- /dev/null +++ b/packages/authorityd-darwin-x64/bin/.gitkeep @@ -0,0 +1 @@ +# Binary placeholder - populated by CI from rust-predicate-authorityd releases diff --git a/packages/authorityd-darwin-x64/package.json b/packages/authorityd-darwin-x64/package.json new file mode 100644 index 0000000..74d4b5a --- /dev/null +++ b/packages/authorityd-darwin-x64/package.json @@ -0,0 +1,18 @@ +{ + "name": "@predicatesystems/authorityd-darwin-x64", + "version": "0.1.0", + "description": "Predicate Authority Sidecar binary for macOS x64 (Intel)", + "license": "(MIT OR Apache-2.0)", + "repository": { + "type": "git", + "url": "https://github.com/PredicateSystems/ts-predicate-authority.git", + "directory": "packages/authorityd-darwin-x64" + }, + "os": ["darwin"], + "cpu": ["x64"], + "files": ["bin"], + "preferUnplugged": true, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/authorityd-linux-arm64/bin/.gitkeep b/packages/authorityd-linux-arm64/bin/.gitkeep new file mode 100644 index 0000000..4e9b849 --- /dev/null +++ b/packages/authorityd-linux-arm64/bin/.gitkeep @@ -0,0 +1 @@ +# Binary placeholder - populated by CI from rust-predicate-authorityd releases diff --git a/packages/authorityd-linux-arm64/package.json b/packages/authorityd-linux-arm64/package.json new file mode 100644 index 0000000..b62d18f --- /dev/null +++ b/packages/authorityd-linux-arm64/package.json @@ -0,0 +1,18 @@ +{ + "name": "@predicatesystems/authorityd-linux-arm64", + "version": "0.1.0", + "description": "Predicate Authority Sidecar binary for Linux ARM64", + "license": "(MIT OR Apache-2.0)", + "repository": { + "type": "git", + "url": "https://github.com/PredicateSystems/ts-predicate-authority.git", + "directory": "packages/authorityd-linux-arm64" + }, + "os": ["linux"], + "cpu": ["arm64"], + "files": ["bin"], + "preferUnplugged": true, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/authorityd-linux-x64/bin/.gitkeep b/packages/authorityd-linux-x64/bin/.gitkeep new file mode 100644 index 0000000..4e9b849 --- /dev/null +++ b/packages/authorityd-linux-x64/bin/.gitkeep @@ -0,0 +1 @@ +# Binary placeholder - populated by CI from rust-predicate-authorityd releases diff --git a/packages/authorityd-linux-x64/package.json b/packages/authorityd-linux-x64/package.json new file mode 100644 index 0000000..a5f63e3 --- /dev/null +++ b/packages/authorityd-linux-x64/package.json @@ -0,0 +1,18 @@ +{ + "name": "@predicatesystems/authorityd-linux-x64", + "version": "0.1.0", + "description": "Predicate Authority Sidecar binary for Linux x64", + "license": "(MIT OR Apache-2.0)", + "repository": { + "type": "git", + "url": "https://github.com/PredicateSystems/ts-predicate-authority.git", + "directory": "packages/authorityd-linux-x64" + }, + "os": ["linux"], + "cpu": ["x64"], + "files": ["bin"], + "preferUnplugged": true, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/authorityd-win32-x64/bin/.gitkeep b/packages/authorityd-win32-x64/bin/.gitkeep new file mode 100644 index 0000000..4e9b849 --- /dev/null +++ b/packages/authorityd-win32-x64/bin/.gitkeep @@ -0,0 +1 @@ +# Binary placeholder - populated by CI from rust-predicate-authorityd releases diff --git a/packages/authorityd-win32-x64/package.json b/packages/authorityd-win32-x64/package.json new file mode 100644 index 0000000..5272de7 --- /dev/null +++ b/packages/authorityd-win32-x64/package.json @@ -0,0 +1,18 @@ +{ + "name": "@predicatesystems/authorityd-win32-x64", + "version": "0.1.0", + "description": "Predicate Authority Sidecar binary for Windows x64", + "license": "(MIT OR Apache-2.0)", + "repository": { + "type": "git", + "url": "https://github.com/PredicateSystems/ts-predicate-authority.git", + "directory": "packages/authorityd-win32-x64" + }, + "os": ["win32"], + "cpu": ["x64"], + "files": ["bin"], + "preferUnplugged": true, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/authorityd/README.md b/packages/authorityd/README.md new file mode 100644 index 0000000..a5bad5d --- /dev/null +++ b/packages/authorityd/README.md @@ -0,0 +1,152 @@ +# @predicatesystems/authorityd + +Predicate Authority Sidecar binary distribution for Node.js. + +This package provides the `predicate-authorityd` binary for your platform, automatically selecting the correct architecture during installation. + +## Installation + +```bash +npm install @predicatesystems/authorityd +``` + +The appropriate binary for your platform will be downloaded automatically via optional dependencies. + +## Supported Platforms + +| Platform | Architecture | Package | +|----------|--------------|---------| +| macOS | Apple Silicon (arm64) | `@predicatesystems/authorityd-darwin-arm64` | +| macOS | Intel (x64) | `@predicatesystems/authorityd-darwin-x64` | +| Linux | x64 | `@predicatesystems/authorityd-linux-x64` | +| Linux | arm64 | `@predicatesystems/authorityd-linux-arm64` | +| Windows | x64 | `@predicatesystems/authorityd-win32-x64` | + +## Usage + +### CLI + +```bash +# Start sidecar in local mode +npx predicate-authorityd run --port 8787 --policy-file policy.json + +# Start with control-plane sync +npx predicate-authorityd run \ + --mode cloud_connected \ + --control-plane-url https://api.predicatesystems.dev \ + --tenant-id your-tenant \ + --project-id your-project \ + --predicate-api-key $PREDICATE_API_KEY \ + --sync-enabled + +# Show help +npx predicate-authorityd --help +``` + +### Programmatic API + +```typescript +import { spawnSidecar, getSidecarPath, isSidecarAvailable } from '@predicatesystems/authorityd'; + +// Check if binary is available +if (!isSidecarAvailable()) { + console.error('Sidecar binary not found'); + process.exit(1); +} + +// Spawn sidecar process +const sidecar = spawnSidecar({ + port: 8787, + mode: 'local_only', + policyFile: './policy.json', + logLevel: 'info', +}); + +// Handle shutdown +process.on('SIGTERM', () => { + sidecar.kill('SIGTERM'); +}); + +sidecar.on('close', (code) => { + console.log(`Sidecar exited with code ${code}`); +}); +``` + +### With @predicatesystems/authority SDK + +```typescript +import { AuthorityClient } from '@predicatesystems/authority'; +import { spawnSidecar } from '@predicatesystems/authorityd'; + +// Start sidecar +const sidecar = spawnSidecar({ + port: 8787, + policyFile: './policy.json', +}); + +// Wait for sidecar to be ready +await new Promise(resolve => setTimeout(resolve, 1000)); + +// Create client +const client = new AuthorityClient({ + baseUrl: 'http://127.0.0.1:8787', +}); + +// Use client +const decision = await client.authorize({ + principal: 'agent:example', + action: 'http.get', + resource: 'https://api.example.com/data', + intent_hash: 'hash123', +}); + +console.log('Decision:', decision.allowed); +``` + +## API Reference + +### `getSidecarPath(): string` + +Returns the absolute path to the sidecar binary. Throws if not found. + +### `isSidecarAvailable(): boolean` + +Returns `true` if the sidecar binary is available. + +### `spawnSidecar(options?: SidecarOptions): ChildProcess` + +Spawns the sidecar process with the given options. + +**Options:** + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `host` | `string` | `127.0.0.1` | Host to bind to | +| `port` | `number` | `8787` | Port to bind to | +| `mode` | `'local_only' \| 'cloud_connected'` | `local_only` | Operating mode | +| `policyFile` | `string` | - | Path to policy JSON file | +| `identityFile` | `string` | - | Path to local identity registry | +| `logLevel` | `string` | `info` | Log level | +| `controlPlaneUrl` | `string` | - | Control-plane URL | +| `tenantId` | `string` | - | Tenant ID | +| `projectId` | `string` | - | Project ID | +| `apiKey` | `string` | - | API key (prefer env var) | +| `syncEnabled` | `boolean` | `false` | Enable control-plane sync | +| `failOpen` | `boolean` | `false` | Fail open if control-plane unreachable | +| `spawnOptions` | `SpawnOptions` | - | Additional Node.js spawn options | + +### `getSidecarVersion(): Promise` + +Returns the sidecar version string. + +## Manual Installation + +If automatic installation fails, download the binary manually: + +1. Go to [releases](https://github.com/PredicateSystems/predicate-authority-sidecar/releases) +2. Download the binary for your platform +3. Place it in your PATH or use `getSidecarPath()` to locate it + +## License + +MIT / Apache-2.0 diff --git a/packages/authorityd/package.json b/packages/authorityd/package.json new file mode 100644 index 0000000..45bdcab --- /dev/null +++ b/packages/authorityd/package.json @@ -0,0 +1,48 @@ +{ + "name": "@predicatesystems/authorityd", + "version": "0.1.0", + "description": "Predicate Authority Sidecar binary distribution for Node.js", + "license": "(MIT OR Apache-2.0)", + "repository": { + "type": "git", + "url": "https://github.com/PredicateSystems/ts-predicate-authority.git", + "directory": "packages/authorityd" + }, + "homepage": "https://github.com/PredicateSystems/predicate-authority-sidecar", + "keywords": [ + "predicate", + "authority", + "sidecar", + "ai-agents", + "security", + "authorization" + ], + "bin": { + "predicate-authorityd": "./bin/predicate-authorityd" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "bin", + "dist", + "scripts" + ], + "scripts": { + "postinstall": "node scripts/postinstall.js", + "build": "tsc", + "typecheck": "tsc --noEmit" + }, + "optionalDependencies": { + "@predicatesystems/authorityd-darwin-arm64": "0.1.0", + "@predicatesystems/authorityd-darwin-x64": "0.1.0", + "@predicatesystems/authorityd-linux-x64": "0.1.0", + "@predicatesystems/authorityd-linux-arm64": "0.1.0", + "@predicatesystems/authorityd-win32-x64": "0.1.0" + }, + "engines": { + "node": ">=18" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/authorityd/scripts/postinstall.js b/packages/authorityd/scripts/postinstall.js new file mode 100644 index 0000000..7a5dc93 --- /dev/null +++ b/packages/authorityd/scripts/postinstall.js @@ -0,0 +1,123 @@ +#!/usr/bin/env node + +/** + * Post-install script for @predicatesystems/authorityd + * + * This script runs after npm install and sets up the appropriate binary + * symlink based on the current platform. + */ + +const fs = require("fs"); +const path = require("path"); +const os = require("os"); + +const PLATFORM_PACKAGES = { + "darwin-arm64": "@predicatesystems/authorityd-darwin-arm64", + "darwin-x64": "@predicatesystems/authorityd-darwin-x64", + "linux-x64": "@predicatesystems/authorityd-linux-x64", + "linux-arm64": "@predicatesystems/authorityd-linux-arm64", + "win32-x64": "@predicatesystems/authorityd-win32-x64", +}; + +function getPlatformKey() { + const platform = os.platform(); + const arch = os.arch(); + + // Normalize architecture + let normalizedArch = arch; + if (arch === "x86_64" || arch === "amd64") { + normalizedArch = "x64"; + } else if (arch === "aarch64") { + normalizedArch = "arm64"; + } + + return `${platform}-${normalizedArch}`; +} + +function getBinaryName() { + return os.platform() === "win32" ? "predicate-authorityd.exe" : "predicate-authorityd"; +} + +function findPlatformPackage(platformKey) { + const packageName = PLATFORM_PACKAGES[platformKey]; + if (!packageName) { + return null; + } + + // Try to find the platform-specific package + const possiblePaths = [ + // Hoisted to node_modules root + path.join(__dirname, "..", "..", "..", packageName), + // Inside this package's node_modules + path.join(__dirname, "..", "node_modules", packageName), + // npm workspaces / pnpm + path.join(__dirname, "..", "..", packageName), + ]; + + for (const p of possiblePaths) { + if (fs.existsSync(p)) { + return p; + } + } + + return null; +} + +function main() { + const platformKey = getPlatformKey(); + const binaryName = getBinaryName(); + + console.log(`[authorityd] Platform: ${platformKey}`); + + const platformPackagePath = findPlatformPackage(platformKey); + + if (!platformPackagePath) { + console.log(`[authorityd] No platform-specific package found for ${platformKey}`); + console.log(`[authorityd] You can manually download the binary from:`); + console.log(`[authorityd] https://github.com/PredicateSystems/predicate-authority-sidecar/releases`); + return; + } + + const sourceBinary = path.join(platformPackagePath, "bin", binaryName); + const targetBinary = path.join(__dirname, "..", "bin", "predicate-authorityd"); + + if (!fs.existsSync(sourceBinary)) { + console.error(`[authorityd] Binary not found at ${sourceBinary}`); + process.exit(1); + } + + // Ensure bin directory exists + const binDir = path.dirname(targetBinary); + if (!fs.existsSync(binDir)) { + fs.mkdirSync(binDir, { recursive: true }); + } + + // Create symlink or copy (symlink preferred, copy for Windows) + try { + // Remove existing file/symlink if present + if (fs.existsSync(targetBinary)) { + fs.unlinkSync(targetBinary); + } + + if (os.platform() === "win32") { + // Windows: copy the binary + fs.copyFileSync(sourceBinary, targetBinary); + } else { + // Unix: create symlink + fs.symlinkSync(sourceBinary, targetBinary); + } + + // Make executable on Unix + if (os.platform() !== "win32") { + fs.chmodSync(targetBinary, 0o755); + } + + console.log(`[authorityd] Binary installed successfully`); + console.log(`[authorityd] Run with: npx predicate-authorityd run --help`); + } catch (err) { + console.error(`[authorityd] Failed to install binary: ${err.message}`); + process.exit(1); + } +} + +main(); diff --git a/packages/authorityd/src/index.ts b/packages/authorityd/src/index.ts new file mode 100644 index 0000000..bad2a87 --- /dev/null +++ b/packages/authorityd/src/index.ts @@ -0,0 +1,227 @@ +/** + * @predicatesystems/authorityd + * + * Predicate Authority Sidecar binary distribution for Node.js. + * + * This package provides the `predicate-authorityd` binary for your platform. + * The binary is installed automatically via postinstall script based on + * platform-specific optional dependencies. + * + * Usage: + * npx predicate-authorityd run --port 8787 --policy-file policy.json + * + * Or programmatically: + * import { getSidecarPath, spawnSidecar } from '@predicatesystems/authorityd'; + * const sidecar = spawnSidecar({ port: 8787, policyFile: 'policy.json' }); + */ + +import { spawn, ChildProcess, SpawnOptions } from "child_process"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; + +/** + * Platform key for binary resolution + */ +export type PlatformKey = + | "darwin-arm64" + | "darwin-x64" + | "linux-x64" + | "linux-arm64" + | "win32-x64"; + +/** + * Sidecar spawn options + */ +export interface SidecarOptions { + /** Host to bind to (default: 127.0.0.1) */ + host?: string; + /** Port to bind to (default: 8787) */ + port?: number; + /** Operating mode: local_only or cloud_connected */ + mode?: "local_only" | "cloud_connected"; + /** Path to policy JSON file */ + policyFile?: string; + /** Path to local identity registry JSON file */ + identityFile?: string; + /** Log level */ + logLevel?: "trace" | "debug" | "info" | "warn" | "error"; + /** Control-plane URL */ + controlPlaneUrl?: string; + /** Tenant ID for control-plane */ + tenantId?: string; + /** Project ID for control-plane */ + projectId?: string; + /** API key for control-plane (prefer PREDICATE_API_KEY env var) */ + apiKey?: string; + /** Enable control-plane sync */ + syncEnabled?: boolean; + /** Fail open if control-plane unreachable */ + failOpen?: boolean; + /** Additional spawn options */ + spawnOptions?: SpawnOptions; +} + +/** + * Get the current platform key + */ +export function getPlatformKey(): PlatformKey { + const platform = os.platform(); + let arch = os.arch(); + + // Normalize architecture + if (arch === "x86_64" || arch === "amd64") { + arch = "x64"; + } else if (arch === "aarch64") { + arch = "arm64"; + } + + return `${platform}-${arch}` as PlatformKey; +} + +/** + * Get the path to the sidecar binary + * @throws Error if binary not found + */ +export function getSidecarPath(): string { + const binName = os.platform() === "win32" ? "predicate-authorityd.exe" : "predicate-authorityd"; + + // Check the bin directory (set up by postinstall) + const binPath = path.join(__dirname, "..", "bin", binName); + if (fs.existsSync(binPath)) { + return binPath; + } + + // Fallback: check if binary is in PATH + const pathDirs = (process.env.PATH || "").split(path.delimiter); + for (const dir of pathDirs) { + const candidate = path.join(dir, binName); + if (fs.existsSync(candidate)) { + return candidate; + } + } + + throw new Error( + `Sidecar binary not found. Please ensure @predicatesystems/authorityd is installed correctly, ` + + `or download manually from https://github.com/PredicateSystems/predicate-authority-sidecar/releases` + ); +} + +/** + * Check if the sidecar binary is available + */ +export function isSidecarAvailable(): boolean { + try { + getSidecarPath(); + return true; + } catch { + return false; + } +} + +/** + * Build command-line arguments from options + */ +function buildArgs(options: SidecarOptions): string[] { + const args: string[] = ["run"]; + + if (options.host) { + args.push("--host", options.host); + } + if (options.port !== undefined) { + args.push("--port", String(options.port)); + } + if (options.mode) { + args.push("--mode", options.mode); + } + if (options.policyFile) { + args.push("--policy-file", options.policyFile); + } + if (options.identityFile) { + args.push("--identity-file", options.identityFile); + } + if (options.logLevel) { + args.push("--log-level", options.logLevel); + } + if (options.controlPlaneUrl) { + args.push("--control-plane-url", options.controlPlaneUrl); + } + if (options.tenantId) { + args.push("--tenant-id", options.tenantId); + } + if (options.projectId) { + args.push("--project-id", options.projectId); + } + if (options.apiKey) { + args.push("--predicate-api-key", options.apiKey); + } + if (options.syncEnabled) { + args.push("--sync-enabled"); + } + if (options.failOpen) { + args.push("--fail-open"); + } + + return args; +} + +/** + * Spawn the sidecar process + * + * @example + * ```ts + * import { spawnSidecar } from '@predicatesystems/authorityd'; + * + * const sidecar = spawnSidecar({ + * port: 8787, + * mode: 'local_only', + * policyFile: './policy.json', + * }); + * + * sidecar.on('close', (code) => { + * console.log(`Sidecar exited with code ${code}`); + * }); + * + * // Graceful shutdown + * process.on('SIGTERM', () => { + * sidecar.kill('SIGTERM'); + * }); + * ``` + */ +export function spawnSidecar(options: SidecarOptions = {}): ChildProcess { + const binaryPath = getSidecarPath(); + const args = buildArgs(options); + + const spawnOpts: SpawnOptions = { + stdio: "inherit", + ...options.spawnOptions, + }; + + return spawn(binaryPath, args, spawnOpts); +} + +/** + * Get the sidecar version + */ +export async function getSidecarVersion(): Promise { + const binaryPath = getSidecarPath(); + + return new Promise((resolve, reject) => { + const proc = spawn(binaryPath, ["--version"], { stdio: "pipe" }); + let output = ""; + + proc.stdout?.on("data", (data) => { + output += data.toString(); + }); + + proc.on("close", (code) => { + if (code === 0) { + resolve(output.trim()); + } else { + reject(new Error(`Failed to get version, exit code: ${code}`)); + } + }); + + proc.on("error", reject); + }); +} diff --git a/packages/authorityd/tsconfig.json b/packages/authorityd/tsconfig.json new file mode 100644 index 0000000..248e3f4 --- /dev/null +++ b/packages/authorityd/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "moduleResolution": "node" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}