PSA/docs/architecture/storage-system.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

5.6 KiB

Alga Storage Service

The Alga storage service is a multi-tenant, JSON document store designed for lightweight configuration, caching, and coordination data. It is no longer coupled to the extension runtime; every tenant can provision and manage namespaces directly through the public API regardless of whether extensions are in use.

Resource Model

tenant
  └─ namespace (≤ 64 chars, lowercase slug)
       └─ key (≤ 256 chars, UTF-8, `/` not permitted)
            └─ record {
                 revision,
                 value,
                 metadata,
                 ttlExpiresAt,
                 createdAt,
                 updatedAt
               }
  • Namespaces partition records within a tenant. Namespaces can be created implicitly by writing a record.
  • Keys uniquely identify records inside a namespace. Keys are case-sensitive and treated as opaque identifiers.
  • Records store JSON documents in the value field plus optional metadata (flat JSON object) and bookkeeping attributes.

Authentication

All requests require an API key with the storage capability. Supply the key via the x-api-key header. Keys are tenant-scoped; a key cannot access data belonging to another tenant.

x-api-key: <tenant-storage-key>

API Surface

HTTP Method Path Description
PUT /api/v1/storage/namespaces/{namespace}/records/{key} Create or update a record
GET /api/v1/storage/namespaces/{namespace}/records/{key} Retrieve a record
GET /api/v1/storage/namespaces/{namespace}/records List records with optional filters
DELETE /api/v1/storage/namespaces/{namespace}/records/{key} Delete a record

Bulk write and archival operations remain in private preview. Contact the platform team for access.

Record Representation

PUT and GET operations exchange JSON payloads with the following structure:

{
  "namespace": "settings",
  "key": "invoice-defaults",
  "revision": 12,
  "value": { "currency": "USD", "netTerms": 30 },
  "metadata": { "contentType": "application/json" },
  "ttlExpiresAt": "2025-01-01T12:00:00Z",
  "createdAt": "2024-12-31T12:00:00Z",
  "updatedAt": "2024-12-31T12:00:00Z"
}
  • revision increases by one on every successful write.
  • ttlExpiresAt is null when no TTL is applied.
  • Timestamps are returned in RFC 3339 format.

Revisions & Concurrency

Revisions allow optimistic concurrency control without global locks.

  • Conditional write: Provide ifRevision in the PUT body. The write only succeeds when the stored revision matches.
  • Conditional fetch: Provide If-Revision-Match header on GET. A 412 Precondition Failed response indicates the revision changed.
  • Conflict handling: When a conditional write fails, the service returns a REVISION_MISMATCH error with the current revision so clients can refresh and retry.

This mechanism is intended for collaborative scenarios where multiple workers coordinate on the same configuration entry.

Time-To-Live (TTL)

Use the ttlSeconds field on PUT requests to attach an expiration. TTLs:

  • Are measured in seconds from the time the write is processed.
  • Can be updated or cleared by issuing another PUT without ttlSeconds.
  • Trigger record removal by a background sweeper shortly after the expiry timestamp.
  • Treat expired records as NOT_FOUND for all read operations.

The current maximum TTL is 30 days. For longer retention, omit ttlSeconds and prune data explicitly.

List Filtering

GET /records accepts the following query parameters:

  • limit (default 25, max 100)
  • cursor (opaque token for pagination)
  • keyPrefix (string prefix filter on keys)
  • includeValues=true|false
  • includeMetadata=true|false

The response includes items and optionally nextCursor when more data is available.

Example Usage

Curl

curl -X PUT \
  -H "x-api-key: $ALGA_STORAGE_KEY" \
  -H "Content-Type: application/json" \
  https://algapsa.com/api/v1/storage/namespaces/settings/records/invoice-defaults \
  -d '{
    "value": { "currency": "USD", "netTerms": 30 },
    "metadata": { "contentType": "application/json" },
    "ttlSeconds": 86400
  }'

Node.js Fetch

const response = await fetch(`${baseUrl}/api/v1/storage/namespaces/${namespace}/records/${key}`, {
  method: "PUT",
  headers: {
    "x-api-key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    value,
    metadata: { contentType: "application/json" },
    ttlSeconds: 3600,
    ifRevision,
  }),
});

Quotas & Limits

  • Maximum record size: 64 KiB serialized JSON.
  • Namespaces per tenant: 128.
  • Requests per second: burst 50, sustained 20 (per tenant). Rate limit headers mirror the standard REST API format.
  • Metadata must be a flat JSON object with string keys and scalar/JSON literal values.

Migration Notes

  • Existing extension storage data has been migrated to tenant-owned namespaces using the pattern <extension_slug>.<namespace>.
  • Extension runners must now request storage access via the tenant storage API key instead of implicit extension credentials.
  • You can remove extension-specific storage configuration from manifests; storage scopes are now defined at the tenant level.

For authoritative updates, bookmark this document or reach out to the platform team in the #alga-storage Slack channel.