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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
# ============================================================
build-macos:
runs-on: macos-26
env:
KEYSTATS_SYNC_SERVICE_URL: ${{ vars.KEYSTATS_SYNC_PRODUCTION_URL }}
outputs:
version: ${{ steps.version.outputs.VERSION }}
sha256: ${{ steps.sha256.outputs.SHA256 }}
Expand Down Expand Up @@ -46,7 +48,12 @@ jobs:
run: ./scripts/check_vendored_helper.sh

- name: Build DMG
run: ./scripts/build_dmg.sh
run: |
if [[ ! "$KEYSTATS_SYNC_SERVICE_URL" =~ ^https://[A-Za-z0-9.-]+\.workers\.dev/?$ ]]; then
echo "KEYSTATS_SYNC_PRODUCTION_URL must be a production workers.dev URL."
exit 1
fi
./scripts/build_dmg.sh

- name: Validate Sparkle private key
env:
Expand Down Expand Up @@ -93,7 +100,8 @@ jobs:
archive \
CODE_SIGN_IDENTITY="-" \
ARCHS="arm64 x86_64" \
ONLY_ACTIVE_ARCH=NO
ONLY_ACTIVE_ARCH=NO \
KEYSTATS_SYNC_SERVICE_URL="$KEYSTATS_SYNC_SERVICE_URL"

APP_PATH="$ARCHIVE_PATH/Products/Applications/$APP_NAME.app"
cp -R "$APP_PATH" "$STAGING_DIR/"
Expand Down Expand Up @@ -139,6 +147,8 @@ jobs:
# ============================================================
build-windows:
runs-on: windows-latest
env:
KEYSTATS_SYNC_SERVICE_URL: ${{ vars.KEYSTATS_SYNC_PRODUCTION_URL }}
outputs:
version: ${{ steps.version.outputs.VERSION }}

Expand Down Expand Up @@ -168,7 +178,10 @@ jobs:
shell: pwsh
working-directory: KeyStats.Windows
run: |
.\build.ps1 -Configuration Release
if ($env:KEYSTATS_SYNC_SERVICE_URL -notmatch '^https://[A-Za-z0-9.-]+\.workers\.dev/?$') {
throw 'KEYSTATS_SYNC_PRODUCTION_URL must be a production workers.dev URL.'
}
.\build.ps1 -Configuration Release -SyncServiceBaseUrl $env:KEYSTATS_SYNC_SERVICE_URL

- name: Upload Windows artifact
uses: actions/upload-artifact@v4
Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/sync-worker-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Sync Worker CI

on:
pull_request:
paths:
- 'contracts/sync/v1/**'
- 'services/sync-worker/**'
- '.github/workflows/sync-worker-*.yml'
push:
branches-ignore: [main]
paths:
- 'contracts/sync/v1/**'
- 'services/sync-worker/**'
- '.github/workflows/sync-worker-*.yml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: sync-worker-ci-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
working-directory: services/sync-worker

jobs:
validate:
name: Contract, migration, type and Worker tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: services/sync-worker/package-lock.json
- run: npm ci
- name: Validate schemas, OpenAPI and golden vectors
run: npm run contracts:check
- name: Apply D1 migrations to an isolated local database
run: npx wrangler d1 migrations apply DB --local
- name: Type-check Worker
run: npm run types:check && npm run typecheck
- name: Run Miniflare and unit tests
run: npm test
68 changes: 68 additions & 0 deletions .github/workflows/sync-worker-deploy-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Deploy Sync Worker Production

on:
workflow_dispatch:
inputs:
confirmation:
description: 'Type deploy-production after staging verification'
required: true
type: string

permissions:
contents: read

concurrency:
group: sync-worker-production
cancel-in-progress: false

defaults:
run:
working-directory: services/sync-worker

jobs:
authorize:
name: Validate explicit confirmation
runs-on: ubuntu-latest
steps:
- name: Require exact production confirmation
if: ${{ inputs.confirmation != 'deploy-production' }}
run: exit 1

deploy:
name: Validate and deploy production
needs: authorize
runs-on: ubuntu-latest
timeout-minutes: 20
environment: sync-production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_D1_DATABASE_ID: ${{ vars.CLOUDFLARE_D1_DATABASE_ID }}
TOKEN_HASH_KEY: ${{ secrets.TOKEN_HASH_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: services/sync-worker/package-lock.json
- run: npm ci
- run: npm run check
- name: Validate deployment secrets
run: |
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then
echo "Cloudflare deployment credentials are not configured."
exit 1
fi
if [ "${#TOKEN_HASH_KEY}" -lt 32 ]; then
echo "TOKEN_HASH_KEY must contain at least 32 characters."
exit 1
fi
- name: Prepare bound production configuration
run: node scripts/write-deploy-config.mjs production wrangler.deploy.json
- name: Apply production D1 migrations
run: npx wrangler d1 migrations apply DB --env production --remote --config wrangler.deploy.json
- name: Configure token hashing secret
run: printf '%s' "$TOKEN_HASH_KEY" | npx wrangler secret put TOKEN_HASH_KEY --env production --config wrangler.deploy.json
- name: Deploy production Worker
run: npx wrangler deploy --env production --config wrangler.deploy.json
59 changes: 59 additions & 0 deletions .github/workflows/sync-worker-deploy-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy Sync Worker Staging

on:
push:
branches: [main]
paths:
- 'contracts/sync/v1/**'
- 'services/sync-worker/**'
- '.github/workflows/sync-worker-*.yml'

permissions:
contents: read

concurrency:
group: sync-worker-staging
cancel-in-progress: false

defaults:
run:
working-directory: services/sync-worker

jobs:
deploy:
name: Validate and deploy staging
runs-on: ubuntu-latest
timeout-minutes: 20
environment: sync-staging
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_D1_DATABASE_ID: ${{ vars.CLOUDFLARE_D1_DATABASE_ID }}
TOKEN_HASH_KEY: ${{ secrets.TOKEN_HASH_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: services/sync-worker/package-lock.json
- run: npm ci
- run: npm run check
- name: Validate deployment secrets
run: |
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then
echo "Cloudflare deployment credentials are not configured."
exit 1
fi
if [ "${#TOKEN_HASH_KEY}" -lt 32 ]; then
echo "TOKEN_HASH_KEY must contain at least 32 characters."
exit 1
fi
- name: Prepare bound staging configuration
run: node scripts/write-deploy-config.mjs staging wrangler.deploy.json
- name: Apply staging D1 migrations
run: npx wrangler d1 migrations apply DB --env staging --remote --config wrangler.deploy.json
- name: Configure token hashing secret
run: printf '%s' "$TOKEN_HASH_KEY" | npx wrangler secret put TOKEN_HASH_KEY --env staging --config wrangler.deploy.json
- name: Deploy staging Worker
run: npx wrangler deploy --env staging --config wrangler.deploy.json
35 changes: 35 additions & 0 deletions .github/workflows/windows-sync-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Windows Sync Tests

on:
pull_request:
paths:
- 'KeyStats.Windows/**'
- 'contracts/sync/v1/**'
- '.github/workflows/windows-sync-tests.yml'
push:
branches: [main]
paths:
- 'KeyStats.Windows/**'
- 'contracts/sync/v1/**'
- '.github/workflows/windows-sync-tests.yml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: windows-sync-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: .NET Framework sync tests
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Run Windows sync tests
run: dotnet test KeyStats.Windows/KeyStats.Sync.Tests/KeyStats.Sync.Tests.csproj --configuration Release --verbosity normal
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ Issue type?
- ✅ Implement Codable for data structures needing persistence
- ✅ Batch UI updates to reduce main thread blocking

### 🟣 Automatic Commit Policy

- ✅ After a feature or module is fully implemented and its relevant checks pass, automatically create an atomic Git commit without waiting for an additional user request
- ✅ Before committing, inspect `git status` again and include only files changed for the completed feature or module
- ✅ Do not automatically commit incomplete work, failed verification, or unrelated existing changes
- ✅ Follow the repository's commit message and Git safety rules for every automatic commit

### ☁️ Sync Worker Staging Deployment

- ✅ After changing the staging sync service (`services/sync-worker/**`, `contracts/sync/v1/**`, or its staging workflow) and passing the relevant checks, deploy the staging Worker directly without waiting for additional confirmation
- ✅ Apply pending staging D1 migrations before deploying, and always target the explicit `staging` Wrangler environment
- ✅ Never deploy the production Worker without explicit user authorization in the current conversation

---

## Build & Development Commands
Expand Down
3 changes: 3 additions & 0 deletions KeyStats.Windows/KeyStats.Sync.Tests/AssemblyAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

[assembly: DoNotParallelize]
28 changes: 28 additions & 0 deletions KeyStats.Windows/KeyStats.Sync.Tests/KeyStats.Sync.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageReference Include="MSTest.TestAdapter" Version="4.3.0" />
<PackageReference Include="MSTest.TestFramework" Version="4.3.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\KeyStats\KeyStats.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\contracts\sync\v1\fixtures\crypto-vectors.json"
Link="Fixtures\crypto-vectors.json"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

</Project>
Loading
Loading