Skip to content

Latest commit

 

History

History
408 lines (340 loc) · 24.8 KB

local-development.md

File metadata and controls

408 lines (340 loc) · 24.8 KB

Local development

ProofStack has two explicit local profiles. The default in-memory profile is dependency-free and disposable. The PostgreSQL profile persists evidence across API and database restarts and exercises the same migrations, row-level tenant isolation, and least-privilege role boundaries used by the durable adapter.

Both profiles default to development authentication on an explicit loopback listener. The durable profile can also exercise the experimental workload API-key path. The OIDC browser backend is implemented, but exercising it requires a configured provider and an HTTPS endpoint in front of the API; the repository does not yet ship that TLS proxy as a production deployment artifact.

Requirements

  • Node.js 24 or newer. .nvmrc pins the exact CI baseline, currently 24.19.0.
  • pnpm 11.24.0, matching the root package.json.
  • Docker with Compose v2 only when using the PostgreSQL profile or S3 compatibility harness.
  • macOS or Linux for the optional POSIX shell-loading examples. Application commands are otherwise platform-independent.

Install and verify

Use the exact .nvmrc version to reproduce CI. Build and test jobs exchange source-bound outputs and require identical Node.js and pnpm versions. A floating major version can select different cached patch versions on separate runners. Update the runtime pin deliberately and verify the complete static, unit and service-backed gates; retain the exact archive and version checks.

pnpm install --frozen-lockfile
pnpm check

The lockfile must not change during installation. pnpm also enforces a minimum package release age and an explicit native-build allowlist. The repository check guide explains the combined package task graph, uncached validation and separate service-backed gates.

Profile A: disposable memory

Run the API and console without creating an environment file:

pnpm dev

The default endpoints are:

Evidence exists only in API process memory in this profile. Restarting the API intentionally clears all traces.

Profile B: durable PostgreSQL

The committed Compose definition pins PostgreSQL 16.15 by image digest, initializes checksums with a deterministic locale and UTC timezone, publishes only to loopback, and stores database files in a named volume. The credentials in the example are intentionally local-only.

Prepare and load the durable profile before starting Compose so an optional port override applies to both Docker and the application URLs:

cp config/postgres.env.example .env
set -a
. ./.env
set +a
pnpm dev:db:up

Apply every immutable migration with the administrative URL, then create the isolated runtime roles. Provisioning is idempotent and rotates passwords for roles it previously marked as managed. It refuses to adopt an unrelated existing PostgreSQL role.

pnpm db:migrate
pnpm db:provision
pnpm db:status
pnpm dev

The evidence API connects as proofstack_api, not as the database administrator. The separately provisioned proofstack_identity role can execute only the fixed credential lifecycle functions; it cannot read identity base tables or evidence. The proofstack_artifact_maintenance role can inspect and advance artifact lifecycle rows but cannot create reservations or access evidence, identity, outbox, sequences, or schema administration. The API verifies the complete migration ledger at startup and readiness checks. The proofstack_replay_worker role can execute only the fixed claim, heartbeat, reservation, reconciliation, observation, cancellation-acknowledgement, and terminal-transition functions; it cannot read or mutate replay tables directly, publish definitions, create jobs, request cancellation, or use API authority. The proofstack_evaluation_worker role can read the immutable evaluation graph and execute only the narrow result-persistence authority; it cannot publish criteria, sources, evaluator/oracle specifications, runs, or assessments, and it has no direct table or outbox write privilege. The proofstack_policy_author role can read exact immutable release-policy graphs and execute only the public policy-publication and lifecycle-append functions; it cannot call their internal insert helpers, mutate policy tables directly, write the outbox directly, or substitute for another runtime role. Stop the local database without deleting evidence with:

pnpm dev:db:down

Starting it again with pnpm dev:db:up reuses the named volume. Re-running migrations and provisioning is safe and reports the already-current or updated state.

Exercise workload API-key authentication

Bootstrap is an explicit administrative operation; the API never creates a first owner or key from an unauthenticated request. Set the required values in the current shell, using the narrowest useful capabilities and resource scope, then run the one-time command:

export PROOFSTACK_IDENTITY_TENANT_ID=ten_local
export PROOFSTACK_BOOTSTRAP_ACTOR_PRINCIPAL_ID=usr_local_operator
export PROOFSTACK_BOOTSTRAP_KEY_NAME=local-agent
export PROOFSTACK_BOOTSTRAP_KEY_CAPABILITIES=evidence:ingest,evidence:read
export PROOFSTACK_BOOTSTRAP_KEY_RESOURCE_SCOPE='{"mode":"tenant"}'
pnpm db:identity:bootstrap

The JSON response contains value exactly once. Copy that complete value immediately into an untracked secret store; it cannot be recovered from PostgreSQL. Do not commit it, paste it into an issue, or pass it in a URL. Remove the temporary bootstrap variables from the shell afterward.

To start the API in workload-key mode, set PROOFSTACK_AUTH_MODE=api_key. The committed PostgreSQL profile already contains the distinct least-privilege PROOFSTACK_IDENTITY_DATABASE_URL. Set PROOFSTACK_API_KEY only for the example process, then run:

export PROOFSTACK_AUTH_MODE=api_key
export PROOFSTACK_API_KEY='<complete value returned once by the bootstrap command>'
pnpm dev:api

From a second shell with the same PROOFSTACK_API_KEY, pnpm example:basic-agent sends authenticated evidence. The server reads only the Authorization: Bearer header; query parameters, URLs, and cookies are never API-key transports. pnpm db:identity:status reports aggregate active, expired, revoked, and total counts for PROOFSTACK_IDENTITY_TENANT_ID without returning prefixes, hashes, or secret material. The browser console never accepts workload secrets. Its sign-in UI is not yet connected to the OIDC backend, so use the documented browser endpoints directly when validating an OIDC deployment.

Exercise OIDC browser authentication

OIDC mode requires the durable identity database, an exact HTTPS issuer, and an exact HTTPS callback registered with the provider. Put an HTTPS reverse proxy in front of the API and forward the fixed callback path /v1/auth/oidc/callback; ProofStack constructs callback validation from the configured URI and never trusts a request Host header. The proxy and certificate are deployment responsibilities at this checkpoint.

Create a local authorization binding with the migration connection. The issuer and subject must exactly match the validated ID token. Roles, capabilities, tenant, and resource scope come only from this ProofStack record, never from an email, group, domain, or tenant claim:

export PROOFSTACK_IDENTITY_TENANT_ID=ten_local
export PROOFSTACK_BOOTSTRAP_ACTOR_PRINCIPAL_ID=usr_local_operator
export PROOFSTACK_OIDC_BINDING_ID=oidc_local_operator
export PROOFSTACK_OIDC_ISSUER='https://identity.example.test/tenant'
export PROOFSTACK_OIDC_SUBJECT='provider-subject-001'
export PROOFSTACK_OIDC_PRINCIPAL_ID=usr_local_operator
export PROOFSTACK_OIDC_ROLES=owner
export PROOFSTACK_OIDC_CAPABILITIES=project:read,project:manage,evidence:read,identity:read,identity:manage
export PROOFSTACK_OIDC_RESOURCE_SCOPE='{"mode":"tenant"}'
pnpm db:oidc:create

Generate a new transaction key into an untracked secret store, then configure and start the API. Do not reuse this example command's output across installations or commit it:

export PROOFSTACK_OIDC_TRANSACTION_SECRET="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
export PROOFSTACK_AUTH_MODE=combined
export PROOFSTACK_CORS_ORIGIN='https://console.example.test'
export PROOFSTACK_OIDC_CLIENT_ID='proofstack-console'
export PROOFSTACK_OIDC_CLIENT_SECRET='<provider client secret>'
export PROOFSTACK_OIDC_ISSUER='https://identity.example.test/tenant'
export PROOFSTACK_OIDC_REDIRECT_URI='https://proofstack.example.test/v1/auth/oidc/callback'
export PROOFSTACK_OIDC_SCOPES='openid profile email'
pnpm dev:api

Open /v1/auth/oidc/login?returnTo=/ through the public HTTPS API endpoint. ProofStack uses Authorization Code + PKCE S256, nonce, one-time state, and a short-lived browser-bound interaction cookie. A successful callback creates a random server-side session and redirects only to the local path stored in the encrypted transaction. GET /v1/auth/session returns current authorization. Cookie-authenticated state changes, including POST /v1/auth/oidc/logout, require the exact allowed Origin, an X-ProofStack-CSRF header, and the matching readable __Host-proofstack_csrf cookie.

To change authorization without changing provider identity, set the binding, actor, tenant, roles, capabilities, and scope variables and run pnpm db:oidc:update. To terminate future logins and make existing sessions fail their authoritative binding check, set a bounded reason and run:

export PROOFSTACK_OIDC_DISABLE_REASON='access removed'
pnpm db:oidc:disable

S3-compatible adapter harness

The optional object-storage profile runs the digest-pinned SeaweedFS version used by CI. It exposes only the S3 port on loopback, disables upstream telemetry and the administrative UI, uses a named volume, and has fixed credentials that are safe only for local testing. This harness verifies the adapter by itself; it does not wire artifact routes or continuously scheduled maintenance workers into the API. The additive profile and guarded bucket setup used to wire it into the durable replay reference are documented in the durable replay guide. Scoped one-shot maintenance commands are documented in the operations guide.

Start the service:

pnpm dev:object-storage:up

Run the real-service object-store contract from another terminal:

export PROOFSTACK_TEST_S3_ACCESS_KEY_ID=proofstack-local
export PROOFSTACK_TEST_S3_SECRET_ACCESS_KEY=proofstack-local-secret
export PROOFSTACK_TEST_S3_ENDPOINT=http://127.0.0.1:8333
export PROOFSTACK_TEST_S3_REGION=us-east-1
pnpm test:integration:s3

The test creates one random proofstack-test-* bucket, runs the immutable object-store contract, and deletes that bucket. Stop the service without deleting its named volume with:

pnpm dev:object-storage:stop

The combined pnpm test:integration:artifacts acceptance requires both PostgreSQL and the S3 service. It provisions isolated runtime roles, creates a random bucket, and proves interrupted activation, expiration, abandoned cleanup, purge retry, and key-reference status through the real adapters. It is destructive to its random test scope and must never target production services.

Production bucket, policy, versioning, TLS, credential, compatibility, monitoring, and recovery requirements are defined in the artifact object-storage operations guide.

Verify the running system

From another terminal, load the same profile if you created one, then check liveness, readiness, and send a real SDK trace:

set -a
test ! -f .env || . ./.env
set +a
curl --fail-with-body http://127.0.0.1:4318/health/live
curl --fail-with-body http://127.0.0.1:4318/health/ready
pnpm example:basic-agent
pnpm example:incident-to-regression
pnpm example:interaction-capture

The example sends an agent.run event and its child tool.execute event, waits for delivery, and prints the generated trace ID and console URL. It uses fail-closed delivery so an unavailable API cannot produce a false successful demonstration.

The incident-to-regression example emits a separate failed run, freezes its exact observed events as an immutable evidence-only fixture, publishes and reads an exact dataset version, and verifies both definition digests. It uses development authentication and therefore refuses non-loopback endpoints. The incident-to-regression guide documents the browser and workload authentication split, idempotent retry contract, failure semantics, and why this checkpoint is not executable replay.

The interaction-capture example emits a failed model/tool trace, reserves and uploads eleven fixture-dedicated classified artifacts, publishes one exact recorded-interaction successor, verifies metadata and explicitly acknowledged content exports, runs one exact recorded-boundary flow and one forced mismatch outside the API process, and then revokes and purges the complete content set. The default memory profile is sufficient for this disposable demonstration; stopping the API removes its remaining metadata. The interaction-capture guide documents capture authority, secret inspection, retry, interoperability, and revocation. The recorded-boundary replay guide documents exact matching, denied fallback, cooperative runtime inputs, and same-process isolation limits.

The durable replay example additionally requires PostgreSQL, the object-storage profile, the additive untracked config/durable-replay.env.example profile, and the dedicated replay-worker database role. It publishes immutable releases and plans, creates durable jobs, launches separate worker and target processes, and proves success, running cancellation, stale-fence rejection, lease recovery, budget reconciliation, and durable result artifacts. Follow the complete start, verification, stop, authority, and limitation procedure in the durable replay guide; do not run it against the disposable memory profile.

The complete retained Workflow 1 acceptance has a separate single-command clean-checkout guide. Its runner owns isolated PostgreSQL and object-storage services on random loopback ports and removes their volumes after the test, so it should not be mixed with this guide's long-lived development profiles.

Verify OTLP/HTTP ingestion

The API also accepts OTLP 1.11 trace requests at POST /v1/traces. The default loopback development profile needs the project and environment headers but no credential. A production authentication mode additionally requires a workload API key with evidence:ingest and access to the requested scope. Follow the exact curl request, exporter variables, mapping, partial-success, redaction, and retry guidance in the OTLP/HTTP ingestion operations guide.

Configuration reference

Variable Default Owner Purpose
PROOFSTACK_ENV development API/database CLI Runtime safety mode
PROOFSTACK_AUTH_MODE development API development, api_key, oidc, or combined
PROOFSTACK_HOST 127.0.0.1 API Listen address; development auth requires explicit loopback
PROOFSTACK_PORT 4318 API Listen port
PROOFSTACK_LOG_LEVEL info API Structured log level
PROOFSTACK_CORS_ORIGIN unset API Exact allowed browser origin when cross-origin access is needed
PROOFSTACK_STORAGE_MODE memory API memory or postgres evidence adapter
PROOFSTACK_DATABASE_URL unset API Least-privilege runtime database URL in PostgreSQL mode
PROOFSTACK_POLICY_AUTHOR_DATABASE_URL unset API Distinct policy-author URL required in PostgreSQL mode; never substitute the general API URL
PROOFSTACK_POLICY_EVALUATION_CONTROL_DATABASE_URL unset API Required dedicated policy control URL in PostgreSQL mode; separate from evidence, author, identity and worker authority
PROOFSTACK_IDENTITY_DATABASE_URL unset API Distinct least-privilege identity URL required outside development auth
PROOFSTACK_ARTIFACT_DATABASE_URL unset artifact maintenance Dedicated lifecycle-worker database URL
PROOFSTACK_REPLAY_WORKER_DATABASE_URL unset replay example/worker Dedicated least-privilege worker URL; never substitute the migration or API URL
PROOFSTACK_ARTIFACT_STORAGE_MODE disabled API Use s3_local_keyring with PostgreSQL for persistent classified content; memory mode always uses disposable in-process storage
PROOFSTACK_ARTIFACT_ACTIVE_KEY_ID unset API Exact active local artifact-key identifier in the experimental persistent profile
PROOFSTACK_ARTIFACT_KEYS unset API Strict JSON object of key IDs to canonical base64url 32-byte keys; experimental and forbidden in production
PROOFSTACK_ARTIFACT_S3_BUCKET unset API Existing dedicated artifact bucket for the persistent profile
PROOFSTACK_ARTIFACT_S3_ENDPOINT AWS default API Optional S3-compatible endpoint; plaintext is allowed only on loopback outside production
PROOFSTACK_ARTIFACT_S3_EXPECTED_BUCKET_OWNER unset API Optional exact AWS account owner assertion
PROOFSTACK_ARTIFACT_S3_FORCE_PATH_STYLE false API Explicit path-style addressing toggle for compatible local services
PROOFSTACK_ARTIFACT_S3_REGION unset API Required S3 region for persistent artifact storage
PROOFSTACK_OIDC_CLIENT_ID unset API Provider client identifier required by OIDC modes
PROOFSTACK_OIDC_CLIENT_SECRET unset API Provider client secret required by OIDC modes
PROOFSTACK_OIDC_ISSUER unset API/identity CLI Exact HTTPS issuer; also selects the binding during create
PROOFSTACK_OIDC_REDIRECT_URI unset API Exact registered HTTPS callback without query or fragment
PROOFSTACK_OIDC_SCOPES openid profile email API Unique printable scopes including openid
PROOFSTACK_OIDC_TRANSACTION_SECRET unset API Canonical base64url encoding of a dedicated random 32-byte key
PROOFSTACK_OTLP_COMPRESSED_BODY_LIMIT_BYTES 1048576 API Maximum OTLP HTTP body bytes before decompression; hard maximum 64 MiB
PROOFSTACK_OTLP_DECOMPRESSED_BODY_LIMIT_BYTES 1048576 API Maximum OTLP body bytes after gzip expansion; hard maximum 64 MiB
PROOFSTACK_OIDC_BINDING_ID unset identity CLI Opaque binding identifier for create, update, or disable
PROOFSTACK_OIDC_SUBJECT unset identity CLI Exact provider subject used only during binding create
PROOFSTACK_OIDC_PRINCIPAL_ID unset identity CLI Stable ProofStack user principal used during binding create
PROOFSTACK_OIDC_ROLES unset identity CLI Unique comma-separated authorization roles
PROOFSTACK_OIDC_CAPABILITIES unset identity CLI Unique comma-separated authorization capabilities; may be empty on update
PROOFSTACK_OIDC_RESOURCE_SCOPE unset identity CLI Strict JSON tenant or restricted project/environment scope
PROOFSTACK_OIDC_DISABLE_REASON unset identity CLI Bounded audited reason required to disable a binding
PROOFSTACK_MIGRATION_DATABASE_URL unset database CLI Administrative migration and provisioning URL
PROOFSTACK_API_DATABASE_ROLE proofstack_api database CLI Managed API role name
PROOFSTACK_ARTIFACT_DATABASE_ROLE proofstack_artifact_maintenance database CLI Managed artifact lifecycle role name
PROOFSTACK_IDENTITY_DATABASE_ROLE proofstack_identity database CLI Managed identity role name
PROOFSTACK_PUBLISHER_DATABASE_ROLE proofstack_publisher database CLI Managed outbox publisher role name
PROOFSTACK_CONSUMER_DATABASE_ROLE proofstack_consumer database CLI Managed consumer role name
PROOFSTACK_EVALUATION_WORKER_DATABASE_ROLE proofstack_evaluation_worker database CLI Managed evaluation result worker role name
PROOFSTACK_MODEL_EVALUATION_WORKER_DATABASE_ROLE proofstack_model_evaluation_worker database CLI Managed model-evaluation result worker role name
PROOFSTACK_POLICY_AUTHOR_DATABASE_ROLE proofstack_policy_author database CLI Managed release-policy author role name
PROOFSTACK_HUMAN_REVIEWER_DATABASE_ROLE proofstack_human_reviewer database CLI Managed human-review record writer role name
PROOFSTACK_REPLAY_WORKER_DATABASE_ROLE proofstack_replay_worker database CLI Managed replay worker role name
PROOFSTACK_API_DATABASE_PASSWORD unset database CLI API role password used only during provisioning
PROOFSTACK_ARTIFACT_DATABASE_PASSWORD unset database CLI Artifact lifecycle role password used only during provisioning
PROOFSTACK_IDENTITY_DATABASE_PASSWORD unset database CLI Identity role password used only during provisioning
PROOFSTACK_PUBLISHER_DATABASE_PASSWORD unset database CLI Publisher role password used only during provisioning
PROOFSTACK_CONSUMER_DATABASE_PASSWORD unset database CLI Consumer role password used only during provisioning
PROOFSTACK_EVALUATION_WORKER_DATABASE_PASSWORD unset database CLI Evaluation worker role password used only during provisioning
PROOFSTACK_MODEL_EVALUATION_WORKER_DATABASE_PASSWORD unset database CLI Model-evaluation worker role password used only during provisioning
PROOFSTACK_POLICY_AUTHOR_DATABASE_PASSWORD unset database CLI Release-policy author password used only during provisioning
PROOFSTACK_HUMAN_REVIEWER_DATABASE_PASSWORD unset database CLI Human reviewer role password used only during provisioning
PROOFSTACK_REPLAY_WORKER_DATABASE_PASSWORD unset database CLI Replay worker role password used only during provisioning
PROOFSTACK_IDENTITY_TENANT_ID unset identity CLI Explicit tenant for bootstrap and aggregate status
PROOFSTACK_BOOTSTRAP_ACTOR_PRINCIPAL_ID unset identity CLI Audited local operator identifier for bootstrap
PROOFSTACK_BOOTSTRAP_KEY_NAME unset identity CLI Printable display name for the initial workload key
PROOFSTACK_BOOTSTRAP_KEY_CAPABILITIES unset identity CLI Unique comma-separated delegable capabilities
PROOFSTACK_BOOTSTRAP_KEY_RESOURCE_SCOPE unset identity CLI Strict JSON tenant or restricted project/environment scope
PROOFSTACK_BOOTSTRAP_KEY_EXPIRES_AT 90 days identity CLI Optional ISO 8601 expiry, bounded to 1 minute–365 days
PROOFSTACK_POSTGRES_PORT 5432 Compose Loopback host port for the local database
PROOFSTACK_S3_PORT 8333 Compose Loopback host port for the local S3-compatible harness
PROOFSTACK_API_URL http://127.0.0.1:4318 Web/example API base URL
PROOFSTACK_API_KEY unset SDK example Complete one-time-issued key; never used by the API server or browser console
PROOFSTACK_PROJECT_ID prj_local Web/example Local project scope
PROOFSTACK_ENVIRONMENT_ID env_local Web/example Local environment scope
PROOFSTACK_SOURCE_REVISION current Git HEAD durable replay example Optional exact 40- or 64-character lowercase source object ID

If PROOFSTACK_POSTGRES_PORT changes, update every database URL in .env to the same port. When rotating a runtime password, update its runtime URL and matching provisioning variable, then run pnpm db:provision before restarting the API. Never commit .env, a bootstrap response, or a complete API key, and never place production credentials in an example file.

Reset and troubleshooting

Use pnpm db:status to distinguish missing or pending migrations from an API problem. The command returns nonzero until the bundled migration ledger is current. A PostgreSQL readiness failure is intentional when the database is unavailable or its migration history is incomplete or corrupted.

If port 5432 is occupied, change PROOFSTACK_POSTGRES_PORT and both database URLs in .env. If port 8333 is occupied, set PROOFSTACK_S3_PORT and use the same port in PROOFSTACK_TEST_S3_ENDPOINT. Then start the relevant Compose profile again. If development authentication starts in production or on a non-loopback listener, report it as a security issue; both configurations must cause immediate startup refusal.

For a routine stop, use pnpm dev:db:down for PostgreSQL or pnpm dev:object-storage:stop for the S3-compatible harness; both named volumes are retained. The following command is destructive and permanently deletes all local ProofStack PostgreSQL evidence and object-store data in this Compose project:

docker compose down --volumes

After destructive reset, repeat pnpm dev:db:up, pnpm db:migrate, and pnpm db:provision before starting the durable API, and restart the object-storage harness before running its integration test.