Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4fc9234
rust api: pca2 route behind RUST_API_ROUTES (first slice) (#2948)
colinmegill Oct 6, 2026
a99f79b
client-report and alpha: recorded behaviour of the riskiest Delphi vi…
colinmegill Oct 6, 2026
8c537bc
clients: job provenance helpers tested against the recorded Delphi st…
colinmegill Oct 6, 2026
de56600
characterization: recording suites pin the schema they read, not the …
colinmegill Oct 6, 2026
4d5b868
cdk: the database-backup function is packaged without Docker (#2970)
colinmegill Oct 6, 2026
02ec340
server: the Delphi job table (foundation migration, flag off; DynamoD…
colinmegill Oct 6, 2026
0bf9f9e
server: the large worker class on the Postgres queue (migration 00002…
colinmegill Oct 6, 2026
2881639
polis-jobs: the large worker class and the math rebuild stage (no dep…
colinmegill Oct 6, 2026
81ac0c6
cdk: one worker group per queue class; demand and alarms read from th…
colinmegill Oct 6, 2026
a2cf772
math poller: oversized conversations are jobs on the Postgres queue (…
colinmegill Oct 6, 2026
bd1709b
compose: math-backfill-state has one labels block again (#2976)
colinmegill Oct 6, 2026
7221469
apply wrapper: 000024 (polis-queue/3) gets its seal and preflight (#2…
colinmegill Oct 6, 2026
07dfb25
math poller: the capacity line reports large_parked (scale-in reads i…
colinmegill Oct 6, 2026
be8d912
deploy hooks: delphi-large and delphi-worker restart only polis-jobs.…
colinmegill Oct 6, 2026
7987554
delphi image: carries the polis-jobs daemon (queue-rs), --version, im…
colinmegill Oct 6, 2026
b3a82ce
server: translation never delays the next statement (#2992)
colinmegill Oct 8, 2026
b060e40
Promote edge to stable (2026-10-08: translation never blocks; queue s…
colinmegill Oct 8, 2026
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
6 changes: 4 additions & 2 deletions .github/workflows/collective-statement-recordings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ jobs:
npm ci --prefix server --no-audit --no-fund
python -m pip install --quiet boto3==1.34.70 botocore==1.34.162 s3transfer==0.10.4

- name: The harness's own rules (expected differences, refusal of foreign stores)
- name: The harness's own rules (expected differences, refusal of foreign stores, the schema pin against the real migrations)
working-directory: server
run: node --test characterization/collective-statement/expected.test.cjs characterization/collective-statement/safety.test.cjs
env:
CSREC_PG_ADMIN_URL: postgres://postgres:generatedlocal@127.0.0.1:5481/postgres
run: node --test characterization/collective-statement/expected.test.cjs characterization/collective-statement/safety.test.cjs characterization/collective-statement/schema.test.cjs

- name: Replay every recorded case against the current routes
working-directory: server
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/job-provenance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Job provenance
on:
pull_request:
paths: ['client-report/src/data/provenance.js', 'client-participation-alpha/src/lib/provenance.ts', 'server/characterization/jobviews/**', 'server/characterization/delphi/recordings/**', '.github/workflows/job-provenance.yml']
push:
branches: [edge]
paths: ['client-report/src/data/provenance.js', 'client-participation-alpha/src/lib/provenance.ts', 'server/characterization/jobviews/**', 'server/characterization/delphi/recordings/**', '.github/workflows/job-provenance.yml']
permissions:
contents: read
jobs:
provenance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: {node-version: 22, cache: npm, cache-dependency-path: server/package-lock.json}
- run: npm ci --no-audit --no-fund
working-directory: server
- run: sh server/characterization/jobviews/check-provenance.sh
27 changes: 27 additions & 0 deletions .github/workflows/job-views.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Job view recordings
on:
pull_request:
paths: ['client-report/**', 'client-participation-alpha/**', 'server/characterization/jobviews/**', 'server/characterization/delphi/recordings/**', '.github/workflows/job-views.yml']
push:
branches: [edge]
paths: ['client-report/**', 'client-participation-alpha/**', 'server/characterization/jobviews/**', 'server/characterization/delphi/recordings/**', '.github/workflows/job-views.yml']
permissions:
contents: read
jobs:
views:
runs-on: ubuntu-latest
env:
TZ: UTC
steps:
- uses: actions/checkout@v4
with: {fetch-depth: 0}
- uses: actions/setup-node@v4
with: {node-version: 22}
- run: npm ci --no-audit --no-fund
working-directory: server
- run: npm ci --no-audit --no-fund
working-directory: client-report
- if: github.event_name == 'pull_request'
run: node server/characterization/jobviews/view-guard.cjs '${{ github.event.pull_request.base.sha }}'
- run: npm run test:jobviews
working-directory: client-report
56 changes: 52 additions & 4 deletions .github/workflows/queue-rs-ci.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
name: queue-rs

# Build, lint and test the queue-rs crate (the polis-queue/1 adapter and the
# polis-jobs daemon). The integration tests run against a throwaway
# PostgreSQL 17 service container with the repository's migration chain and
# the draft 000023 vendored under queue-rs/tests/fixtures.
# Build, lint and test the queue-rs workspace: the polis-queue/1 adapter and
# the polis-jobs daemon (job `queue-rs`), and the polis-api route server with
# its nginx routing switch (job `polis-api`). The integration tests run against a throwaway
# PostgreSQL 17 service container with the repository's migration chain,
# 000023 (the Delphi job table) included.
on:
pull_request:
paths:
- 'queue-rs/**'
- 'file-server/nginx/**'
- 'file-server/nginx.Dockerfile'
- 'server/postgres/migrations/**'
- '.github/workflows/queue-rs-ci.yml'
push:
branches: [edge, stable]
paths:
- 'queue-rs/**'
- 'file-server/nginx/**'
- 'file-server/nginx.Dockerfile'
- 'server/postgres/migrations/**'
- '.github/workflows/queue-rs-ci.yml'
workflow_dispatch:
Expand Down Expand Up @@ -62,3 +67,46 @@ jobs:
POLIS_JOBS_TEST_DATABASE_URL: postgresql://postgres@127.0.0.1:5432/queue_acceptance
POLIS_JOBS_TEST_PYTHON: python3
run: cargo test --locked --features jobs-integration --test jobs_integration -- --test-threads 4

polis-api:
name: polis-api build, clippy, tests, image and nginx routing
runs-on: ubuntu-24.04
timeout-minutes: 40
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: postgres
POSTGRES_DB: polis_api_test
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d polis_api_test"
--health-interval 2s --health-timeout 3s --health-retries 30
defaults:
run:
working-directory: queue-rs
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Toolchain (pinned by queue-rs/rust-toolchain.toml)
run: rustup show active-toolchain
- name: Format
run: cargo fmt -p polis-api --check
- name: Clippy
run: |
cargo clippy --locked -p polis-api --all-targets -- -D warnings
cargo clippy --locked -p polis-api --all-targets --features db-tests -- -D warnings
- name: Unit tests and the three queries against PostgreSQL 17
env:
POLIS_API_TEST_DATABASE_URL: postgresql://postgres@127.0.0.1:5432/polis_api_test
run: cargo test --locked -p polis-api --features db-tests
- name: Release build
run: cargo build --locked --release -p polis-api
- name: Docker image
run: docker build -t polis-api:ci -f polis-api/Dockerfile .
- name: nginx routing switch (off, absent/up/502/dead, bad names and settings, method and body gate, truncation limit)
working-directory: .
run: queue-rs/polis-api/conformance/nginx-routing.sh
13 changes: 13 additions & 0 deletions cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ This code defines the infrastructure for running the Pol.is application on AWS u
* **Container Registry:** Docker images for the web server and math worker are stored in Amazon Elastic Container Registry (ECR).
* **SSM Parameters:** Used to store database connection details like host, port, and the secret ARN, as well as the application's Docker image tag.

## The database-backup function's package

`lambda/lambda.ts` packages the weekly database-backup function (`lambda/handler/dbBackuplambda.py`, Python 3.12 on x86_64) on the machine running `cdk synth`, `cdk diff` or `cdk deploy`, without Docker: pip installs the wheel pinned by version and sha256 in `lambda/handler/requirements.txt` for the Lambda's platform (`--platform manylinux2014_x86_64 --python-version 3.12 --abi cp312`), and the handler source is copied beside it. Nothing is vendored into the repository. What a synth needs is `python3` with pip on PATH (any Python version; set `CDK_PYTHON` to use another interpreter) and either network access or a pip cache that already holds the wheel; a container runtime is never consulted, and a failed pip run fails the synth with its own message. The same package can be produced by hand, for inspection:

```
python3 -m pip install --no-compile --require-hashes --only-binary=:all: \
--platform manylinux2014_x86_64 --implementation cp --python-version 3.12 --abi cp312 \
--target /tmp/db-backup-package -r lambda/handler/requirements.txt
cp lambda/handler/* /tmp/db-backup-package/
```

The asset key is hashed from the handler directory and the bundling options, so moving the pin in `requirements.txt` (the file says how to find a wheel's hash) is what makes the next deploy upload a new package. `npm test` covers the function's shape with bundling skipped and, in `test/dbBackupLambda.test.ts`, runs pip once for real.

## Deployment Process

The deployment process is orchestrated via GitHub Actions:
Expand Down
59 changes: 38 additions & 21 deletions cdk/autoscaling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';
import * as cdk from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as cloudwatch_actions from 'aws-cdk-lib/aws-cloudwatch-actions';
import { ByClass, WorkerClassesSettings, mathLargeRow } from './workerClasses';

export default (
self: Construct,
Expand All @@ -20,8 +21,9 @@ export default (
ollamaNamespace: string,
alarmTopic: cdk.aws_sns.Topic,
enableOllama: boolean = false,
// P-073 large memory class (-c enableLargeClass=true); see the AsgDelphiLarge comment.
largeClassEnabled: boolean = false
// Queue worker classes (-c enableLargeClass=true, cdk/workerClasses.ts): one group per row
// of the class table; see the AsgDelphiLarge comment. Undefined: no worker group.
workers?: { settings: WorkerClassesSettings; launchTemplates: ByClass<cdk.aws_ec2.LaunchTemplate> }
) => {
const commonAsgProps = { vpc, role: instanceRole };

Expand Down Expand Up @@ -87,27 +89,33 @@ export default (
healthCheck: autoscaling.HealthCheck.ec2({ grace: cdk.Duration.minutes(5) }),
});

// Delphi Large ASG
// With -c enableLargeClass=true (P-073) this group is repurposed as the large memory class for
// the Python math poller: its boxes have service type `delphi-large` and run only
// math-python-large. It is already in the CodeDeploy deployment group, the Postgres ingress, the
// log/secret/DB dependencies and the Delphi security group, and it has been at 0 since
// 2026-07-31, so repurposing it gives up nothing that runs: the large Delphi report tier (jobs
// job_poller.py routes to the `large` size) has had no box since then and keeps having none.
// min 0 / max 1 and NO desired count: the step policies in largeClass.ts own the count, and a
// cdk deploy must never reset a running worker. No CPU target tracking: a busy large worker
// uses one or two of eight vCPUs, so CPU tracking would scale it in mid-job.
const asgDelphiLarge = largeClassEnabled
? new autoscaling.AutoScalingGroup(self, 'AsgDelphiLarge', {
// Worker class groups (cdk/workerClasses.ts): one per row of the class table. The row's
// min/max and NO desired count: the step policies in workerClasses.ts own the count, and a
// cdk deploy must never reset a running worker. No CPU target tracking: a busy worker uses
// one or two of eight vCPUs, so CPU tracking would scale it in mid-job.
const workerGroup = (name: string, id: string) => {
const spec = workers!.settings.classes.find((c) => c.name === name)!;
return new autoscaling.AutoScalingGroup(self, id, {
vpc,
launchTemplate: delphiLargeLaunchTemplate,
minCapacity: 0,
maxCapacity: 1,
launchTemplate: workers!.launchTemplates[name],
minCapacity: spec.minCapacity,
maxCapacity: spec.maxCapacity,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
healthCheck: autoscaling.HealthCheck.ec2({ grace: cdk.Duration.minutes(10) }),
// GroupInServiceInstances feeds the LongRunning cost alarm.
// GroupInServiceInstances feeds the LongRunning and MissingWorker alarms.
groupMetrics: [new autoscaling.GroupMetrics(autoscaling.GroupMetric.IN_SERVICE_INSTANCES)],
})
});
};

// Delphi Large ASG
// With -c enableLargeClass=true this group is the math-large worker class: its boxes have
// service type `delphi-large` and run only the large worker. It is already in the CodeDeploy
// deployment group, the Postgres ingress, the log/secret/DB dependencies and the Delphi security
// group, and it has been at 0 since 2026-07-31, so repurposing it gives up nothing that runs:
// the large Delphi report tier (jobs job_poller.py routes to the `large` size) has had no box
// since then and keeps having none.
const asgDelphiLarge = workers
? workerGroup(mathLargeRow(workers.settings).name, 'AsgDelphiLarge')
: new autoscaling.AutoScalingGroup(self, 'AsgDelphiLarge', {
vpc,
launchTemplate: delphiLargeLaunchTemplate,
Expand All @@ -120,6 +128,14 @@ export default (
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
healthCheck: autoscaling.HealthCheck.ec2({ grace: cdk.Duration.minutes(5) }),
});
const workerGroups: ByClass<autoscaling.AutoScalingGroup> = {};
if (workers) {
const large = mathLargeRow(workers.settings);
workerGroups[large.name] = asgDelphiLarge;
for (const spec of workers.settings.classes) {
if (spec.name !== large.name) workerGroups[spec.name] = workerGroup(spec.name, `Asg${spec.id}`);
}
}


// --- Scaling Policies & Alarms
Expand Down Expand Up @@ -168,8 +184,8 @@ export default (
return cpuMetric;
};
const delphiSmallCpuMetric = createDelphiCpuScaling(asgDelphiSmall, 'DelphiSmall', 60); // Target 60% CPU
// Target 60% CPU; no tracking for the large memory class (see AsgDelphiLarge), the alarm stays.
const delphiLargeCpuMetric = createDelphiCpuScaling(asgDelphiLarge, 'DelphiLarge', 60, !largeClassEnabled);
// Target 60% CPU; no tracking for a worker class (see AsgDelphiLarge), the alarm stays.
const delphiLargeCpuMetric = createDelphiCpuScaling(asgDelphiLarge, 'DelphiLarge', 60, !workers);

// Add Ollama GPU Scaling Policy (only when the GPU stack is enabled)
if (enableOllama && asgOllama) {
Expand All @@ -195,6 +211,7 @@ export default (
asgMathWorker,
asgDelphiSmall,
asgDelphiLarge,
workerGroups,
commonAsgProps
}
}
12 changes: 8 additions & 4 deletions cdk/codedeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ export default (
asgDelphiSmall: cdk.aws_autoscaling.AutoScalingGroup,
asgDelphiLarge: cdk.aws_autoscaling.AutoScalingGroup,
codeDeployRole: cdk.aws_iam.Role,
// P-073: the delphi-large box's own role fetches revisions too.
delphiLargeRole?: cdk.aws_iam.IRole
// Queue worker classes (cdk/workerClasses.ts): each class role fetches revisions too. Only
// asgDelphiLarge (service type delphi-large, which scripts/after_install.sh handles) is in
// the deployment group. The other worker groups stay out until the deploy hooks have a
// branch for their service type: today an unknown type makes after_install.sh start every
// service on the box.
workers?: { roles: cdk.aws_iam.IRole[]; extraGroups: cdk.aws_autoscaling.AutoScalingGroup[] }
) => {
const application = new codedeploy.ServerApplication(self, 'CodeDeployApplication', {
applicationName: 'PolisApplication',
Expand All @@ -27,8 +31,8 @@ export default (
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});
deploymentBucket.grantRead(instanceRole);
if (delphiLargeRole) {
deploymentBucket.grantRead(delphiLargeRole);
for (const role of workers?.roles ?? []) {
deploymentBucket.grantRead(role);
}

// Deployment Group
Expand Down
73 changes: 73 additions & 0 deletions cdk/config/amazon-cloudwatch-agent-worker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "root"
},
"metrics": {
"append_dimensions": {
"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
"ImageId": "${aws:ImageId}",
"InstanceId": "${aws:InstanceId}",
"InstanceType": "${aws:InstanceType}"
},
"metrics_collected": {
"nvidia_gpu": {
"measurement": [
{
"name": "utilization_gpu",
"unit": "Percent"
},
{
"name": "utilization_memory",
"unit": "Percent"
},
{
"name": "memory_total",
"unit": "Megabytes"
},
{
"name": "memory_used",
"unit": "Megabytes"
},
{
"name": "memory_free",
"unit": "Megabytes"
},
{
"name": "power_draw",
"unit": "Watts"
},
{
"name": "temperature_gpu",
"unit": "Count"
}
],
"metrics_collection_interval": 60,
"nvidia_smi_path": "/usr/bin/nvidia-smi",
"metrics_aggregation_interval": 60,
"namespace": "OllamaMetrics"
},
"disk": {
"measurement": [
"used_percent",
"free"
],
"metrics_collection_interval": 60,
"resources": [
"/"
]
},
"mem": {
"measurement": [
"mem_used_percent"
],
"metrics_collection_interval": 60
}
},
"aggregation_dimensions": [
[
"AutoScalingGroupName"
]
]
}
}
10 changes: 9 additions & 1 deletion cdk/lambda/handler/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
psycopg2-binary
# Installed by lambda/lambda.ts (packageHandler) with pip for the Lambda's own
# platform: CPython 3.12, x86_64, manylinux2014. The hash is the wheel's, so
# the exact file is verified; it is the version the deployed function carries.
# To move the pin:
# python3 -m pip download --no-deps --only-binary=:all: --platform manylinux2014_x86_64 \
# --implementation cp --python-version 3.12 --abi cp312 -d /tmp/wheel 'psycopg2-binary==X.Y.Z'
# shasum -a 256 /tmp/wheel/*.whl
psycopg2-binary==2.9.10 \
--hash=sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e
Loading
Loading