Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9c3fd17
fix(core): reject malformed Unicode in credential payloads
Sep 19, 2026
6c4fd29
fix(sdk): retain unresolved transactions for reconciliation
Sep 19, 2026
2af6826
fix(web): release wallet state when operation cleanup fails
Sep 19, 2026
d9eeff3
chore(snapshot): preserve local protocol and indexer fixes
Sep 23, 2026
db00aee
chore(snapshot): preserve recovered hosted payload API and migrations
Sep 23, 2026
022bf9d
feat(web): integrate wallet and credential flows with Nuxt UI
Sep 23, 2026
a5f0023
test(db): verify hosted payload upgrades and exact runtime grants
Sep 23, 2026
212ba0c
chore(deploy): wire optional payload hosting and document integration…
Sep 23, 2026
6aa23f5
refactor(web): serve REST API through Nuxt with restricted database p…
Sep 23, 2026
7b8ec7a
docs(ux): draft bilingual role journeys for issue 29
Sep 23, 2026
170c178
feat(db): add application model and recipient-authorized sharing
Sep 23, 2026
ee39ba5
feat(db): add validated forward migration commands
Sep 23, 2026
2818ef2
feat(auth): add XRP Identity sessions and wallet linking
Sep 23, 2026
4e39747
feat(admin): add authenticated organization review portal
Sep 23, 2026
d8c5a2c
feat(issuer): add approved issuer onboarding and credential workspace
Sep 23, 2026
9a31c17
merge: record reconciled authentication and admin histories
Sep 23, 2026
cb6c3d8
fix(ci): build SDK before PostgreSQL tests and normalize plan formatting
Sep 23, 2026
a9777cc
docs: record consolidated PR validation and remaining license gates
Sep 23, 2026
8c6456c
Adapt wallet and role workflows to standalone applications
Sep 24, 2026
9b22913
Preserve prior PR35 history after validated standalone port
Sep 24, 2026
e2404ec
Add recipient and verifier workspaces with revocable sharing
Sep 24, 2026
5eb9bec
Complete signed issuer-to-verifier journeys
Sep 24, 2026
9158eea
Clarify directory discovery before repository searches
Sep 24, 2026
006957f
Decode complete binary ledgers without weakening source quorum
Sep 24, 2026
e1ea40b
Reject fresh wallet signatures after their ledger window expires
Sep 24, 2026
7080dce
Record real Identity and GemWallet Testnet journey qualification
Sep 24, 2026
f2adf36
Clarify path handling and scripted edit verification
Sep 24, 2026
51f1b7d
Simplify issuer, recipient and verifier attestation journeys
Sep 24, 2026
fb50389
Align compiled journey tests with guided interface controls
Sep 24, 2026
c1dee4d
Serialize database test files that share cluster roles
Sep 24, 2026
0de0801
feat: prepare guided journeys for production
Sanka-D Sep 28, 2026
c83080c
docs: format Commons repository links
Sanka-D Sep 28, 2026
da57935
test(db): clean all runtime roles between suites
Sanka-D Sep 28, 2026
a0e329b
Merge origin/main into integration/current-flow
Sanka-D Sep 28, 2026
953abc8
Merge remote-tracking branch 'origin/main' into integration/current-flow
Sanka-D Sep 28, 2026
87d65e6
ci: align role smoke with managed database contract
Sanka-D Sep 28, 2026
e0dbab9
Merge remote-tracking branch 'origin/main' into integration/current-flow
Sanka-D Sep 28, 2026
6343649
Simplify XCS journeys with guided role portals
Sanka-D Sep 29, 2026
b540162
Merge origin/main into integration/current-flow
Sanka-D Sep 29, 2026
d961bd2
Assert decision emails by business content
Sanka-D Sep 29, 2026
88f1b03
Align compiled journeys with guided signing
Sanka-D Oct 9, 2026
f64be67
Remediate newly disclosed web dependency advisories
Sanka-D Oct 9, 2026
c4c5a29
Fix standalone web patch installation
Sanka-D Oct 9, 2026
897a76f
Allow explicit loopback Identity qualification
Sanka-D Oct 9, 2026
7a03ad7
Preserve protected portal return destinations
Sanka-D Oct 9, 2026
fa5c889
Complete notifier portal origin contract
Sanka-D Oct 9, 2026
a49ddc4
Assert protected admin login return
Sanka-D Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
25 changes: 19 additions & 6 deletions .env.compose.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
# apps/web/.env.example and apps/indexer/.env.example.

# --- Passwords (local development values; Compose composes the connection URLs) ---
# Only the administrator password is an input. The three runtime roles
# (xcs_indexer, xcs_api and the dedicated pg_monitor identity xcs_monitor) are
# created by db/compose-initdb/01-runtime-roles.sql when the postgres volume is
# Only the administrator password is an input. All eight runtime users are
# created by db/compose-initdb/01-runtime-roles.sql when the PostgreSQL volume is
# first initialised, with published non-secret local-development passwords that
# docker-compose.yml uses verbatim. In a deployment those users are created by
# the managed database service instead; db:bootstrap only assigns privileges and
Expand Down Expand Up @@ -62,10 +61,9 @@ XCS_PINNING_IP_HASH_SECRET=
# file named by XCS_NETWORK_PROFILE above, so there is nothing to set here.
# Public, browser-reachable endpoint. Never put credentials or a private indexer URL here.
XCS_PUBLIC_RPC_URL=wss://testnet.xrpl-labs.com/
# Optional Xaman OAuth configuration. The API key is public. Register the exact redirect URL
# in the Xaman Developer Console; it must be this deployment's origin with a trailing slash.
# Optional Xaman OAuth configuration. The API key is public. Register the site origin
# in the Xaman Developer Console; the wallet SDK owns the redirect callback.
XCS_PUBLIC_XAMAN_API_KEY=
XCS_PUBLIC_XAMAN_REDIRECT_URL=http://localhost:3000/
XCS_PUBLIC_WALLET_CONNECT_PROJECT_ID=

# --- Host bindings ---
Expand Down Expand Up @@ -114,3 +112,18 @@ XCS_GRAFANA_MEMORY_LIMIT=512m
XCS_GRAFANA_CPU_LIMIT=0.5
XCS_EXPORTER_MEMORY_LIMIT=128m
XCS_EXPORTER_CPU_LIMIT=0.25


# --- Optional application overlay ---
# Providing this complete group enables auth, Commons review and issuer journeys.
XCS_AUTH_ORIGIN=https://localhost:3445
XCS_IDENTITY_CLIENT_ID=
XCS_IDENTITY_CLIENT_SECRET=
XCS_ADMIN_DOCUMENT_KEY=
XCS_MAILPIT_PORT=8025
XCS_NOTIFIER_IMAGE=xcs-notifier:local

# --- Optional hosted-payload overlay ---
XCS_PUBLIC_PAYLOAD_BASE_URL=
XCS_PAYLOAD_STORAGE_IP_HASH_SECRET=
XCS_HOSTED_PAYLOAD_NETWORKS=
46 changes: 41 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# The vendored-copy check reads a retired source (the former
# packages/db) with `git show <sha>:<path>`, which the default
# depth-1 clone does not contain.
fetch-depth: 0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
with:
version: 10.34.4
Expand Down Expand Up @@ -73,6 +69,8 @@ jobs:
# the production (security) configuration.
- name: Run browser pilot flows
run: pnpm --dir apps/web test:e2e
- name: Run authentication browser flows
run: pnpm --dir apps/web test:e2e:auth
- name: Run browser security flows
run: pnpm --dir apps/web test:e2e:security
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
Expand All @@ -99,8 +97,22 @@ jobs:
--health-interval 5s
--health-timeout 3s
--health-retries 20
mailpit:
image: axllent/mailpit:v1.29.2
ports:
- 1025:1025
- 8025:8025
options: >-
--health-cmd "/mailpit readyz"
--health-interval 5s
--health-timeout 3s
--health-retries 20
env:
XCS_TEST_DATABASE_URL: postgres://postgres:postgres-integration-only-password-0001@127.0.0.1:5432/postgres
XCS_TEST_MAILPIT_URL: http://127.0.0.1:8025
XCS_TEST_SMTP_PORT: '1025'
XCS_ISSUER_RUNTIME_MAILPIT_ORIGIN: http://127.0.0.1:8025
XCS_ISSUER_RUNTIME_SMTP_PORT: '1025'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -122,7 +134,31 @@ jobs:
# it runs against this job's service rather than a second database.
- run: pnpm --dir apps/web install --ignore-workspace --frozen-lockfile
- run: pnpm --dir apps/web test:postgres
- name: Verify generated database baseline is committed
- name: Install Chromium for compiled authenticated flows
run: pnpm --dir apps/web exec playwright install --with-deps chromium
- name: Exercise compiled Nitro with restricted PostgreSQL pools
run: pnpm test:runtime
- name: Verify generated database migrations are committed
run: |
pnpm --dir apps/indexer db:generate
git diff --exit-code -- db/migrations
test -z "$(git ls-files --others --exclude-standard -- db/migrations)"

deployment-images:
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
component: [web, indexer, notifier, db-bootstrap]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build ${{ matrix.component }} deployment image
run: >-
docker build
--file apps/${{ matrix.component }}/Dockerfile
--tag xcs-${{ matrix.component }}:ci
--build-arg XCS_REVISION=${{ github.sha }}
.
141 changes: 61 additions & 80 deletions .github/workflows/operations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,17 @@ jobs:
env:
COMPOSE_PROJECT_NAME: xcs-ci-${{ github.run_id }}-${{ github.run_attempt }}
XCS_POSTGRES_ADMIN_PASSWORD: compose-ci-admin-password-00000001
# The three runtime roles are created by db/compose-initdb/01-runtime-roles.sql
# with published local-development passwords; they are not inputs.
XCS_AUTH_ORIGIN: https://compose.invalid
XCS_IDENTITY_CLIENT_ID: compose-ci-client
XCS_IDENTITY_CLIENT_SECRET: compose-ci-identity-secret-000001
XCS_ADMIN_DOCUMENT_KEY: compose-ci-document-signing-key-000001
XCS_METRICS_TOKEN: compose-ci-metrics-token-000000000001
XCS_RPC_URL_PRIMARY: wss://primary.invalid.example
XCS_RPC_URL_SECONDARY: wss://secondary.invalid.example
# XCS_NETWORK_PROFILE is deliberately unset. The only committed profile,
# config/networks/testnet.example.json, is an invalid template, and both
# services now read this file: the indexer (not started here) would refuse
# it at preflight and the web service, which derives the browser-visible
# profile identifier from it, would refuse to start.
XCS_PUBLIC_RPC_URL: wss://submission.invalid.example
XCS_INDEXER_IMAGE: xcs-indexer:ci
XCS_WEB_IMAGE: xcs-web:ci
XCS_NOTIFIER_IMAGE: xcs-notifier:ci
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -49,48 +47,8 @@ jobs:
chmod 644 ops/secrets/xcs_metrics_token
test -s ops/secrets/xcs_metrics_token

- name: Render and validate every Compose profile
shell: bash
run: |
set -euo pipefail
docker compose config --quiet
docker compose --profile monitoring --profile demo-pinning config --quiet
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile monitoring config --quiet

mapfile -t DEFAULT_SERVICES < <(docker compose config --services | sort)
EXPECTED=(db-bootstrap indexer postgres web)
if [[ "${DEFAULT_SERVICES[*]}" != "${EXPECTED[*]}" ]]; then
printf 'Unexpected default services: %s\n' "${DEFAULT_SERVICES[*]}" >&2
exit 1
fi

docker compose --profile monitoring --profile demo-pinning config --format json \
> "$RUNNER_TEMP/xcs-compose.json"
jq -e '
(.services.postgres.ports == null) and
(.services.prometheus.ports == null) and
(.services.grafana.ports == null) and
(.services["postgres-exporter"].ports == null) and
(.services["node-exporter"].ports == null) and
(.services.ipfs.ports == null) and
(.services.prometheus.profiles | index("monitoring") != null) and
(.services.grafana.profiles | index("monitoring") != null) and
(.networks.database.internal == true) and
(.networks.monitoring.internal == true) and
([.services[] | select(.image | endswith(":latest"))] | length == 0)
' "$RUNNER_TEMP/xcs-compose.json"

docker compose -f docker-compose.yml -f docker-compose.dev.yml \
--profile monitoring --profile demo-pinning config --format json \
> "$RUNNER_TEMP/xcs-compose-dev.json"
jq -e '
[
.services.postgres.ports[],
.services.prometheus.ports[],
.services.grafana.ports[],
.services.ipfs.ports[]
] | all(.host_ip == "127.0.0.1")
' "$RUNNER_TEMP/xcs-compose-dev.json"
- name: Render and validate every Compose overlay and profile
run: node ops/ci/check-compose-overlays.mjs

- name: Validate monitoring configuration with Prometheus
shell: bash
Expand All @@ -108,13 +66,13 @@ jobs:
jq empty ops/monitoring/grafana/dashboards/*.json

- name: Build production-only core images
run: docker compose build web indexer
run: docker compose -f docker-compose.yml -f docker-compose.application.yml build web indexer admin-notifier

- name: Assert runtime images exclude development tools
shell: bash
run: |
set -euo pipefail
for IMAGE in "$XCS_WEB_IMAGE" "$XCS_INDEXER_IMAGE"; do
for IMAGE in "$XCS_WEB_IMAGE" "$XCS_INDEXER_IMAGE" "$XCS_NOTIFIER_IMAGE"; do
docker run --rm --entrypoint sh "$IMAGE" -c \
'test ! -e node_modules/typescript && test ! -e node_modules/vitest && test ! -e node_modules/tsx'
done
Expand Down Expand Up @@ -157,51 +115,45 @@ jobs:
fi

# The bootstrap is deliberately restart-safe. Run it again after the
# dependency chain has completed to prove repeated initialization and
# role provisioning do not require a clean volume.
# dependency chain has completed to prove repeated migrations and
# privilege grants do not require a clean volume.
"${COMPOSE[@]}" run --rm --no-deps db-bootstrap

curl --fail --silent --show-error http://127.0.0.1:3000/health/live > /dev/null
READY_STATUS="$(curl --silent --output /dev/null --write-out '%{http_code}' http://127.0.0.1:3000/health/ready)"
test "$READY_STATUS" = '503'
# The metrics routes are enabled by the presence of a valid
# XCS_METRICS_TOKEN, which this job supplies to the web service, so they
# are registered here and must be gated rather than absent: an
# unauthenticated request is rejected and the job's own token is
# accepted. The routes deliberately stay 200 while the database is
# unavailable, which is the state this stack is in.
for METRICS_PATH in /internal/metrics /internal/metrics/prometheus; do
METRICS_STATUS="$(curl --silent --output /dev/null --write-out '%{http_code}' \
"http://127.0.0.1:3000${METRICS_PATH}")"
test "$METRICS_STATUS" = '401'
WRONG_TOKEN_STATUS="$(curl --silent --output /dev/null --write-out '%{http_code}' \
--header 'Authorization: Bearer compose-ci-wrong-token-0000000000001' \
"http://127.0.0.1:3000${METRICS_PATH}")"
test "$WRONG_TOKEN_STATUS" = '401'
AUTHENTICATED_STATUS="$(curl --silent --output /dev/null --write-out '%{http_code}' \
--header "Authorization: Bearer $XCS_METRICS_TOKEN" \
"http://127.0.0.1:3000${METRICS_PATH}")"
test "$AUTHENTICATED_STATUS" = '200'
done
UNAUTHENTICATED_STATUS="$(curl --silent --output /dev/null --write-out '%{http_code}' http://127.0.0.1:3000/internal/metrics/prometheus)"
test "$UNAUTHENTICATED_STATUS" = '401'
curl --fail --silent --show-error \
--header "Authorization: Bearer $XCS_METRICS_TOKEN" \
http://127.0.0.1:3000/internal/metrics/prometheus \
| grep --fixed-strings 'xcs_database_available'

if "${COMPOSE[@]}" ps --services --status running | grep --extended-regexp '^(prometheus|grafana|postgres-exporter|node-exporter|ipfs)$'; then
echo 'A non-core profile service started unexpectedly' >&2
exit 1
fi
if PUBLISHED_POSTGRES="$("${COMPOSE[@]}" port postgres 5432 2>/dev/null)" && [[ -n "$PUBLISHED_POSTGRES" ]]; then
echo "PostgreSQL was unexpectedly published at $PUBLISHED_POSTGRES" >&2
POSTGRES_CONTAINER="$("${COMPOSE[@]}" ps --quiet postgres)"
# Compose versions can report "invalid IP:0" for an exposed but
# unpublished port. Inspect the actual Engine bindings instead.
if ! docker inspect --format '{{json .NetworkSettings.Ports}}' "$POSTGRES_CONTAINER" \
| jq --exit-status '[.[]? | select(. != null) | .[]?] | length == 0' > /dev/null; then
echo 'PostgreSQL unexpectedly has published host ports' >&2
exit 1
fi

"${COMPOSE[@]}" exec -T postgres psql -U xcs_admin -d xcs --no-psqlrc --tuples-only --no-align \
--command "
SELECT
pg_has_role('xcs_monitor', 'pg_monitor', 'MEMBER')
AND (SELECT bool_and(rolcanlogin) FROM pg_roles
WHERE rolname IN ('xcs_indexer', 'xcs_api', 'xcs_monitor'))
AND (SELECT rolconnlimit FROM pg_roles WHERE rolname = 'xcs_indexer') = 12
AND (SELECT rolconnlimit FROM pg_roles WHERE rolname = 'xcs_api') = 12
AND (SELECT rolconnlimit FROM pg_roles WHERE rolname = 'xcs_monitor') = 3
AND NOT EXISTS (
SELECT 1 FROM pg_roles
WHERE rolname IN ('xcs_indexer', 'xcs_api', 'xcs_monitor')
AND (
NOT rolcanlogin OR rolsuper OR rolcreaterole OR rolcreatedb
OR rolreplication OR rolbypassrls
)
)
AND NOT has_schema_privilege('xcs_monitor', 'public', 'CREATE')
AND NOT EXISTS (
SELECT 1
Expand Down Expand Up @@ -231,6 +183,35 @@ jobs:
);
" | grep --fixed-strings --line-regexp t

"${COMPOSE[@]}" exec -T postgres psql -U xcs_admin -d xcs --no-psqlrc --tuples-only --no-align \
--command "
SELECT
has_table_privilege('xcs_payload_writer', 'hosted_payloads', 'INSERT')
AND has_table_privilege('xcs_issuer', 'app_issuer_payloads', 'INSERT')
AND has_table_privilege('xcs_app', 'app_sessions', 'INSERT')
AND has_column_privilege('xcs_admin_app', 'app_organization_applications', 'status', 'UPDATE')
AND has_column_privilege('xcs_notifier', 'app_admin_notifications', 'status', 'UPDATE')
AND NOT EXISTS (
SELECT 1 FROM pg_roles
WHERE rolname IN ('xcs_app', 'xcs_admin_app', 'xcs_notifier', 'xcs_issuer', 'xcs_payload_writer')
AND (
NOT rolcanlogin OR rolsuper OR rolcreaterole OR rolcreatedb
OR rolreplication OR rolbypassrls
)
)
AND NOT EXISTS (
SELECT 1 FROM pg_class relation
JOIN pg_namespace namespace ON namespace.oid = relation.relnamespace
CROSS JOIN (VALUES ('xcs_api'), ('xcs_indexer'), ('xcs_payload_writer')) AS runtime(role_name)
WHERE namespace.nspname = 'public' AND relation.relname LIKE 'app_%'
AND relation.relkind IN ('r', 'p')
AND has_table_privilege(runtime.role_name, relation.oid, 'SELECT,INSERT,UPDATE,DELETE')
)
AND NOT has_table_privilege('xcs_admin_app', 'app_issuer_payloads', 'SELECT')
AND NOT has_table_privilege('xcs_notifier', 'app_issuer_payloads', 'SELECT')
AND NOT has_table_privilege('xcs_app', 'app_issuer_payloads', 'SELECT');
" | grep --fixed-strings --line-regexp t

"${COMPOSE[@]}" --profile monitoring up --no-build --detach --wait postgres-exporter prometheus
PROMETHEUS_CONTAINER="$("${COMPOSE[@]}" --profile monitoring ps --quiet prometheus)"
PROMETHEUS_STATE="$(docker inspect --format '{{.State.Status}}' "$PROMETHEUS_CONTAINER")"
Expand Down Expand Up @@ -274,7 +255,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
for DOCKERFILE in apps/web/Dockerfile apps/indexer/Dockerfile; do
for DOCKERFILE in apps/web/Dockerfile apps/indexer/Dockerfile apps/notifier/Dockerfile apps/db-bootstrap/Dockerfile; do
grep --fixed-strings -- 'USER node' "$DOCKERFILE"
grep --fixed-strings -- 'pnpm install --frozen-lockfile' "$DOCKERFILE"
if grep --extended-regexp 'RUN --mount|<<[A-Z]' "$DOCKERFILE"; then
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ coverage/
.nuxt/
.output/
.data/
.do/
.env
.env.*
!.env.compose.example
Expand Down
Loading
Loading