Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c0ef747
chore(awareness-service): scaffold api package
coodos May 3, 2026
de745d7
feat(awareness-service): ingest endpoint and webhook delivery engine
coodos May 4, 2026
d1c9a58
feat(awareness-service): polling query and subscription APIs
coodos May 5, 2026
e45f34d
feat(awareness-service): W3DS portal auth, applications and admin API
coodos May 6, 2026
6452511
feat(awareness-service): Neo4j backfill and catch-all seeding
coodos May 7, 2026
31d7e45
feat(evault-core): replace webhook fanout with AaaS ingest
coodos May 8, 2026
bee5fdb
feat(awareness-service): public SvelteKit portal
coodos May 9, 2026
d3ccfc1
chore(awareness-service): init migration, workspace and env wiring
coodos May 17, 2026
5fa4436
docs(awareness-service): Scalar API reference and service docs
coodos May 17, 2026
3983db2
chore(awareness-service): reuse standard NEO4J env vars for backfill
coodos May 17, 2026
03b976a
fix(awareness-service): claim deliveries via query builder returning
coodos May 17, 2026
8aee102
fix(awareness-service): inline portal API base URL with static env
coodos May 17, 2026
2f96ac5
feat(awareness-service): ontology picker, API docs link, admin gating
coodos May 17, 2026
15c5312
feat(awareness-service): trim application form and dark-theme the portal
coodos May 17, 2026
5ac5399
docs(awareness-service): scope API reference to consumer endpoints
coodos May 17, 2026
7e725d1
feat(webhook-inlet-test): print every received webhook
coodos May 17, 2026
d5f2e98
test(evault-core): update webhook spec for AaaS ingest
coodos May 18, 2026
935a85e
fix: tests
coodos May 17, 2026
85c45b5
fix(awareness-service): add svelte-qrcode type declaration
coodos May 18, 2026
2cd99a4
fix(awareness-service): dedupe backfill batch by packet id
coodos May 18, 2026
929a167
feat(awareness-service): report total and page count in packet query
coodos May 18, 2026
4548f75
fix(awareness-service): handle transient DB outages quietly in delive…
coodos May 18, 2026
c70ea59
fix(awareness-service): trim dead-letter list payload, center login QR
coodos May 18, 2026
a60cffd
chore(awareness-service): bump axios floor to a non-vulnerable release
coodos May 18, 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
23 changes: 23 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,26 @@ FILE_MANAGER_JWT_SECRET="secret"
CHARTER_JWT_SECRET="secret"
PICTIQUE_JWT_SECRET="secret"


# Awareness as a Service (AaaS)
# Connection string for the AaaS Postgres database
AWARENESS_DATABASE_URL="postgres://postgres:postgres@localhost:5432/awareness"
AWARENESS_API_PORT=4100
# Public base URL of the AaaS API (used to build W3DS auth callbacks)
AWARENESS_PUBLIC_URL="http://localhost:4100"
# Shared secret evault-core must present on POST /ingest
AWARENESS_INGEST_SECRET="replace-with-a-strong-secret"
# Where evault-core forwards every awareness packet
AWARENESS_SERVICE_URL="http://localhost:4100"
# Comma-separated eNames allowed to act as AaaS portal admins
AAAS_ADMIN_ENAMES=""
# Secret used to sign AaaS portal session JWTs
AAAS_JWT_SECRET="replace-with-a-strong-secret"
# Webhook delivery tuning
AWARENESS_MAX_ATTEMPTS=8
AWARENESS_DELIVERY_POLL_MS=2000
# The one-time Neo4j backfill reuses the standard NEO4J_URI / NEO4J_USER /
# NEO4J_PASSWORD vars at the top of this file - it reads evault-core's graph
# directly, so there are no AaaS-specific Neo4j vars.
# Portal -> API base URL
PUBLIC_AWARENESS_API_URL="http://localhost:4100"
177 changes: 177 additions & 0 deletions docs/docs/Services/Awareness-as-a-Service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
sidebar_position: 1
---

# Awareness as a Service (AaaS)

Awareness as a Service is the single fanout point for MetaEnvelope **awareness
packets**. It replaces the webhook fanout that previously lived inside
evault-core, and adds a queryable history, granular subscriptions, and an
access-controlled public portal.

## Why it exists

Before AaaS, every eVault fanned out webhooks itself: on each MetaEnvelope
create/update it queried the registry for every platform and POSTed the change
to all of them. That design had three problems:

- **Undifferentiated** — every platform received every packet, regardless of
whether it cared about that ontology.
- **Unqueryable** — there was no way to poll history or catch up after
downtime; a missed webhook was simply lost.
- **Ungoverned** — any registered platform received everything; there was no
access gate.

AaaS fixes all three. evault-core now makes **one** POST per change to
`AWARENESS_SERVICE_URL/ingest`, and AaaS owns persistence, polling, subscription
matching, and retrying delivery.

## Architecture

```
┌─────────────────────────────┐
evault-core ──POST───▶ │ AaaS /ingest │
(per change) │ • persist packet │
│ • match subscriptions │
│ • queue deliveries │
└──────────────┬──────────────┘
┌────────────────────────────┼───────────────────────────┐
▼ ▼ ▼
GET /api/packets Delivery engine Portal (SvelteKit)
(poll by ontology / (retry + backoff, • W3DS login
eVault / time range) dead-letter) • apply for access
POST <subscriber>/api/webhook • admin approval
```

The API is **Express + TypeORM + Postgres**; the portal is **SvelteKit +
Tailwind**. Both live in `services/awareness-service/`.

## Awareness packet format

The packet evault-core POSTs to `/ingest` — and the body AaaS delivers to
webhook subscribers — is unchanged from the legacy evault-core webhook, so
existing receivers need no changes:

```json
{
"id": "<MetaEnvelope id>",
"w3id": "<owner eName>",
"evaultPublicKey": "<eVault public key>",
"data": { "...": "the MetaEnvelope payload" },
"schemaId": "<ontology>"
}
```

`/ingest` additionally accepts a `requestingPlatform` field, used only to skip
delivering a packet back to its origin (the ping-pong guard the old fanout
enforced). It is never persisted or delivered.

## Capabilities

### 1. Polling query API

`GET /api/packets` lets an approved consumer query the awareness history,
filtered by `ontology` (comma-separated), `evault`, and a `from`/`to` time
range. Results are ordered by receive time and paged with an opaque cursor:

```
GET /api/packets?ontology=<schemaId>&from=2026-05-01T00:00:00Z&limit=100
Authorization: Bearer aaas_<api-key>
```

The response carries `packets`, `hasMore`, and `nextCursor` — pass `nextCursor`
back as `cursor` to page forward.

### 2. Dynamic webhook subscriptions

`POST /api/subscriptions` registers a webhook subscription scoped by ontology
and eVault. Empty filter arrays mean "everything":

```json
{
"targetUrl": "https://my-platform.example/api/webhook",
"ontologyFilter": ["<ontology-A>", "<ontology-B>"],
"evaultFilter": ["<eName-or-public-key>"]
}
```

A consumer manages only its own subscriptions (`GET`, `PATCH`, `DELETE`). If a
subscription has a `secret`, each delivery carries an `x-aaas-signature` header
(HMAC-SHA256 of the body).

### 3. Retrying delivery + dead-letters

A background engine drains the delivery queue. Failed deliveries are retried
with exponential backoff (30s → 1m → 2m → 5m → 15m → 1h → 6h → 24h). After
`AWARENESS_MAX_ATTEMPTS` attempts the delivery is moved to a **dead-letter**
table, visible to admins in the portal, where it can be replayed.

### 4. Public access portal

Platforms log in with **W3DS** (scan a `w3ds://auth` deeplink with the eID
wallet), submit an access application, and wait for an admin to approve it.
Admins are identified by an env-var allowlist of eNames (`AAAS_ADMIN_ENAMES`).
Once approved, a consumer issues API keys from its dashboard and manages
subscriptions and delivery status there.

## Authentication

| Surface | Credential |
| --- | --- |
| `/ingest` | `x-ingest-secret` header (shared with evault-core) |
| `/api/packets`, `/api/subscriptions`, `/api/me/*` | `Authorization: Bearer` — an issued API key (`aaas_…`) **or** a W3DS portal session JWT |
| `/api/applications/*` | W3DS portal session JWT |
| `/api/admin/*` | W3DS portal session JWT whose eName is in `AAAS_ADMIN_ENAMES` |

API keys are stored only as SHA-256 hashes; the plaintext is shown exactly once
on creation.

## API reference

The API serves an interactive **Scalar** reference and a raw OpenAPI document:

- `GET /docs` — Scalar API reference UI
- `GET /openapi.json` — the OpenAPI 3.1 document

## Migration from the old fanout

AaaS is designed to be dropped in with **zero receiver-side changes**:

1. **Backfill.** AaaS runs on the same node as evault-core's Neo4j. The
`backfill` script reads existing MetaEnvelopes straight from the graph and
seeds the `packets` table (history only — it does not queue deliveries).
2. **Catch-all seeding.** On every launch, AaaS ensures each platform currently
in the registry has an approved consumer and a catch-all subscription
pointing at `<platform>/api/webhook`. Existing platforms therefore keep
receiving every packet exactly as before, and can later narrow their
subscriptions to specific ontologies or eVaults.
3. **evault-core switch.** evault-core's `deliverWebhooks`/`getActivePlatforms`
are removed; a single `notifyAwareness` POST forwards each packet to AaaS.

## Configuration

| Variable | Purpose |
| --- | --- |
| `AWARENESS_DATABASE_URL` | Postgres connection string for AaaS |
| `AWARENESS_API_PORT` | API listen port (default 4100) |
| `AWARENESS_PUBLIC_URL` | Public base URL, used for W3DS auth callbacks |
| `AWARENESS_INGEST_SECRET` | Shared secret for `/ingest` |
| `AWARENESS_SERVICE_URL` | (evault-core) where to POST packets |
| `AAAS_ADMIN_ENAMES` | Comma-separated admin eNames |
| `AAAS_JWT_SECRET` | Signs portal session JWTs |
| `AWARENESS_MAX_ATTEMPTS` | Delivery attempts before dead-lettering (default 8) |
| `AWARENESS_DELIVERY_POLL_MS` | Delivery engine poll interval (default 2000) |
| `NEO4J_URI` / `NEO4J_USER` / `NEO4J_PASSWORD` | Standard eVault Neo4j vars — reused by the one-time backfill |
| `PUBLIC_AWARENESS_API_URL` | (portal) AaaS API base URL |

## Running locally

```sh
# Create the Postgres database, then:
pnpm --filter awareness-service-api build
pnpm --filter awareness-service-api migration:run
pnpm --filter awareness-service-api backfill # one-time, from Neo4j
pnpm --filter awareness-service-api dev # API (seeds catch-all on launch)
pnpm --filter awareness-portal dev # portal
```
4 changes: 4 additions & 0 deletions docs/docs/Services/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "Services",
"position": 6
}
Loading
Loading