privacy: scrub personal identifiers from docs and examples
Some checks are pending
check / secretapi (push) Waiting to run
Some checks are pending
check / secretapi (push) Waiting to run
This commit is contained in:
parent
322474b359
commit
cbcf027d97
@ -1,11 +1,11 @@
|
|||||||
# Secret API Backend (`secretapi`)
|
# Secret API Backend (`secretapi`)
|
||||||
|
|
||||||
Deterministic backend for wallet-first designation, EDUT ID activation, and governance install authorization.
|
Deterministic backend for wallet-first designation, EDUT ID activation metadata, and governance install authorization.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /Users/vsg/Documents/VSG\ Codex/web/backend/secretapi
|
cd <home>/Documents/VSG\ Codex/web/backend/secretapi
|
||||||
go run .
|
go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ Default listen address is `:8080`.
|
|||||||
## Test
|
## Test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /Users/vsg/Documents/VSG\ Codex/web/backend/secretapi
|
cd <home>/Documents/VSG\ Codex/web/backend/secretapi
|
||||||
go test ./...
|
go test ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -37,6 +37,8 @@ Copy `.env.example` in this folder and set contract/runtime values before deploy
|
|||||||
- `POST /secret/id/quote` (alias to membership quote)
|
- `POST /secret/id/quote` (alias to membership quote)
|
||||||
- `POST /secret/id/confirm` (alias to membership confirm)
|
- `POST /secret/id/confirm` (alias to membership confirm)
|
||||||
- `GET /secret/id/status` (alias to membership status)
|
- `GET /secret/id/status` (alias to membership status)
|
||||||
|
- `GET /secret/setup/health` (deterministic setup readiness checks for wallet/session/membership/assurance/principal state)
|
||||||
|
- `POST /secret/id/mint-payload` (direct wallet mint payload, no quote dependency)
|
||||||
|
|
||||||
### Marketplace
|
### Marketplace
|
||||||
|
|
||||||
@ -62,6 +64,17 @@ Copy `.env.example` in this folder and set contract/runtime values before deploy
|
|||||||
- `POST /member/channel/events/{event_id}/ack`
|
- `POST /member/channel/events/{event_id}/ack`
|
||||||
- `POST /member/channel/support/ticket`
|
- `POST /member/channel/support/ticket`
|
||||||
|
|
||||||
|
Member channel feed includes deterministic anti-noise throttling:
|
||||||
|
|
||||||
|
1. when event volume exceeds configured burst limits in a window, direct events are suppressed
|
||||||
|
2. a single `channel_digest` card is inserted per window with grouped context and aggregated `suppressed_count`
|
||||||
|
3. throttling is fail-closed deterministic and does not require external services
|
||||||
|
|
||||||
|
`GET /member/channel/events` now also returns digest summary fields:
|
||||||
|
|
||||||
|
1. `digest_active`
|
||||||
|
2. `digest_suppressed_count`
|
||||||
|
|
||||||
## Wallet Session Hardening
|
## Wallet Session Hardening
|
||||||
|
|
||||||
`POST /secret/wallet/verify` now issues a wallet session token:
|
`POST /secret/wallet/verify` now issues a wallet session token:
|
||||||
@ -73,6 +86,9 @@ When `SECRET_API_REQUIRE_WALLET_SESSION=true`, wallet-scoped control-plane endpo
|
|||||||
|
|
||||||
1. `Authorization: Bearer <session_token>`
|
1. `Authorization: Bearer <session_token>`
|
||||||
2. `X-Edut-Session: <session_token>`
|
2. `X-Edut-Session: <session_token>`
|
||||||
|
3. Optional stronger replay binding: `X-Edut-Device-Binding: <stable-device-secret>`
|
||||||
|
|
||||||
|
If a session was issued with a device binding (or user-agent fallback binding), requests from a different binding context are rejected with `wallet_session_context_mismatch`.
|
||||||
|
|
||||||
Covered endpoints include marketplace checkout/entitlements, governance install/lease actions, and member-channel calls.
|
Covered endpoints include marketplace checkout/entitlements, governance install/lease actions, and member-channel calls.
|
||||||
|
|
||||||
@ -118,7 +134,8 @@ Assurance levels:
|
|||||||
Policy gates:
|
Policy gates:
|
||||||
|
|
||||||
1. Store checkout requires active membership.
|
1. Store checkout requires active membership.
|
||||||
2. Workspace admin install/support actions require `onramp_attested` assurance.
|
2. Workspace admin install/support actions require active membership and org-root-owner role.
|
||||||
|
3. Governance admin controls (install token issuance, lease heartbeat/offline renew, owner support tickets) require `org_root_owner` role and additionally require `onramp_attested` identity assurance (`identity_assurance_insufficient` when unmet).
|
||||||
|
|
||||||
## Quote Cost Envelope
|
## Quote Cost Envelope
|
||||||
|
|
||||||
@ -130,6 +147,33 @@ The envelope is pre-execution pricing metadata and is authoritative for checkout
|
|||||||
2. `provider_fee_policy=edut_absorbed` means on-ramp processing fees are absorbed by EDUT.
|
2. `provider_fee_policy=edut_absorbed` means on-ramp processing fees are absorbed by EDUT.
|
||||||
3. `network_fee_policy=payer_wallet_pays_chain_gas` means chain gas remains wallet-dependent and separate from checkout total.
|
3. `network_fee_policy=payer_wallet_pays_chain_gas` means chain gas remains wallet-dependent and separate from checkout total.
|
||||||
|
|
||||||
|
Quote endpoints accept optional `payment_path`:
|
||||||
|
|
||||||
|
1. `crypto_direct` (default)
|
||||||
|
2. `fiat_onramp`
|
||||||
|
|
||||||
|
When the on-ramp dependency edge is degraded, `fiat_onramp` fails closed with `dependency.onramp_unavailable` while `crypto_direct` remains available.
|
||||||
|
|
||||||
|
Financial approval thresholds:
|
||||||
|
|
||||||
|
1. when `SECRET_API_FINANCIAL_APPROVAL_THRESHOLD_ATOMIC > 0`, quote responses include `approval_required=true` and deterministic `approval_reason` when `total_amount_atomic` exceeds the threshold.
|
||||||
|
2. threshold-gated confirms fail closed with `approval_required` unless both `approval_token` and `approval_actor` are supplied.
|
||||||
|
3. confirm responses persist only `approval_token_ref` (hash reference), never raw approval token material.
|
||||||
|
|
||||||
|
Chain-settlement confirmations (`/secret/membership/confirm`, `/marketplace/checkout/confirm`) also fail closed when chain-adjacent dependency edges are degraded:
|
||||||
|
|
||||||
|
1. `dependency.dns_unavailable`
|
||||||
|
2. `dependency.tls_unavailable`
|
||||||
|
3. `dependency.chain_unavailable`
|
||||||
|
|
||||||
|
Error envelope contract:
|
||||||
|
|
||||||
|
1. all non-2xx responses return deterministic `code` + `error` + `correlation_id`.
|
||||||
|
2. responses now also include deterministic `next_step` guidance for remediation/retry.
|
||||||
|
3. `/marketplace/checkout/confirm` now enforces setup readiness before high-impact entitlement activation:
|
||||||
|
- if membership is not active and the quote is not a bundled membership activation quote, confirm fails closed with `setup_incomplete`.
|
||||||
|
- recovery path is deterministic via `GET /secret/setup/health?wallet=...`.
|
||||||
|
|
||||||
## Key Environment Variables
|
## Key Environment Variables
|
||||||
|
|
||||||
### Core
|
### Core
|
||||||
@ -138,7 +182,16 @@ The envelope is pre-execution pricing metadata and is authoritative for checkout
|
|||||||
- `SECRET_API_DB_PATH` (default `./secret.db`)
|
- `SECRET_API_DB_PATH` (default `./secret.db`)
|
||||||
- `SECRET_API_ALLOWED_ORIGIN` (default `https://edut.ai`)
|
- `SECRET_API_ALLOWED_ORIGIN` (default `https://edut.ai`)
|
||||||
- `SECRET_API_DEPLOYMENT_CLASS` (`development|staging|production`; default `development`)
|
- `SECRET_API_DEPLOYMENT_CLASS` (`development|staging|production`; default `development`)
|
||||||
|
- `SECRET_API_DEPENDENCY_RECOVERY_STABILITY_SECONDS` (default `60`; hold window before degraded-edge recovery)
|
||||||
|
- `SECRET_API_DEPENDENCY_CHAIN_STATE` (`auto|healthy|degraded`; default `auto`)
|
||||||
|
- `SECRET_API_DEPENDENCY_TLS_STATE` (`auto|healthy|degraded`; default `auto`)
|
||||||
|
- `SECRET_API_DEPENDENCY_DNS_STATE` (`auto|healthy|degraded`; default `auto`)
|
||||||
|
- `SECRET_API_DEPENDENCY_ONRAMP_STATE` (`auto|healthy|degraded`; default `auto`)
|
||||||
|
- `SECRET_API_DEPENDENCY_CLOUD_STATE` (`auto|healthy|degraded`; default `auto`)
|
||||||
|
- `SECRET_API_DEPENDENCY_MODEL_STATE` (`auto|healthy|degraded`; default `auto`)
|
||||||
- `SECRET_API_MEMBER_POLL_INTERVAL_SECONDS` (default `30`)
|
- `SECRET_API_MEMBER_POLL_INTERVAL_SECONDS` (default `30`)
|
||||||
|
- `SECRET_API_MEMBER_CHANNEL_EVENT_BURST_LIMIT` (default `25`; set `0` to disable channel event throttling)
|
||||||
|
- `SECRET_API_MEMBER_CHANNEL_EVENT_BURST_WINDOW_SECONDS` (default `3600`; required positive when burst limit is enabled)
|
||||||
- `SECRET_API_CHAIN_ID` (default `84532`)
|
- `SECRET_API_CHAIN_ID` (default `84532`)
|
||||||
- `SECRET_API_CHAIN_RPC_URL` (optional, enables on-chain tx receipt verification)
|
- `SECRET_API_CHAIN_RPC_URL` (optional, enables on-chain tx receipt verification)
|
||||||
- `SECRET_API_REQUIRE_ONCHAIN_TX_VERIFICATION`:
|
- `SECRET_API_REQUIRE_ONCHAIN_TX_VERIFICATION`:
|
||||||
@ -157,9 +210,10 @@ The envelope is pre-execution pricing metadata and is authoritative for checkout
|
|||||||
- `SECRET_API_DOMAIN_NAME`
|
- `SECRET_API_DOMAIN_NAME`
|
||||||
- `SECRET_API_VERIFYING_CONTRACT`
|
- `SECRET_API_VERIFYING_CONTRACT`
|
||||||
- `SECRET_API_MEMBERSHIP_CONTRACT`
|
- `SECRET_API_MEMBERSHIP_CONTRACT`
|
||||||
- `SECRET_API_MINT_CURRENCY` (`USDC` for launch; `ETH` allowed for Sepolia/test harness)
|
- `SECRET_API_MINT_CURRENCY` (`ETH` default for gas-only EDUT ID; `USDC` optional)
|
||||||
- `SECRET_API_MINT_AMOUNT_ATOMIC` (default `100000000`)
|
- `SECRET_API_MINT_AMOUNT_ATOMIC` (default `0` for gas-only EDUT ID mint)
|
||||||
- `SECRET_API_MINT_DECIMALS` (must be `6` for `USDC`, `18` for `ETH`)
|
- `SECRET_API_MINT_DECIMALS` (must be `6` for `USDC`, `18` for `ETH`)
|
||||||
|
- `SECRET_API_FINANCIAL_APPROVAL_THRESHOLD_ATOMIC` (default `0`; when greater than `0`, marketplace checkout confirmations above threshold require explicit `approval_token` and `approval_actor`)
|
||||||
|
|
||||||
### Marketplace
|
### Marketplace
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ Content-Type: application/json
|
|||||||
{
|
{
|
||||||
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"principal_role": "org_root_owner",
|
"principal_role": "org_root_owner",
|
||||||
"device_id": "desktop-7f6f3a9b",
|
"device_id": "desktop-7f6f3a9b",
|
||||||
"launcher_version": "0.2.0",
|
"launcher_version": "0.2.0",
|
||||||
@ -90,7 +90,7 @@ Authorization: Bearer <wallet-session>
|
|||||||
{
|
{
|
||||||
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"principal_role": "org_root_owner",
|
"principal_role": "org_root_owner",
|
||||||
"membership_status": "active",
|
"membership_status": "active",
|
||||||
"identity_assurance_level": "onramp_attested",
|
"identity_assurance_level": "onramp_attested",
|
||||||
@ -121,7 +121,7 @@ Request:
|
|||||||
{
|
{
|
||||||
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"device_id": "desktop-7f6f3a9b"
|
"device_id": "desktop-7f6f3a9b"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -144,7 +144,7 @@ Request:
|
|||||||
{
|
{
|
||||||
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"renewal_bundle": {
|
"renewal_bundle": {
|
||||||
"bundle_id": "rb_01J9B6D4R0E4W8N6H2A1Q9V7PT",
|
"bundle_id": "rb_01J9B6D4R0E4W8N6H2A1Q9V7PT",
|
||||||
"signed_payload": "base64:ZXhhbXBsZQ=="
|
"signed_payload": "base64:ZXhhbXBsZQ=="
|
||||||
|
|||||||
@ -46,7 +46,7 @@ Request:
|
|||||||
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
||||||
"offer_id": "edut.workspace.core",
|
"offer_id": "edut.workspace.core",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"principal_role": "org_root_owner",
|
"principal_role": "org_root_owner",
|
||||||
"workspace_id": "workspace.work.acme.exec",
|
"workspace_id": "workspace.work.acme.exec",
|
||||||
"ownership_proof": "0x9f20..."
|
"ownership_proof": "0x9f20..."
|
||||||
@ -63,7 +63,7 @@ Success (`200`):
|
|||||||
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
||||||
"offer_id": "edut.workspace.core",
|
"offer_id": "edut.workspace.core",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"principal_role": "org_root_owner",
|
"principal_role": "org_root_owner",
|
||||||
"currency": "USDC",
|
"currency": "USDC",
|
||||||
"amount": "1000.00",
|
"amount": "1000.00",
|
||||||
@ -144,7 +144,7 @@ Request:
|
|||||||
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
||||||
"offer_id": "edut.workspace.core",
|
"offer_id": "edut.workspace.core",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"principal_role": "org_root_owner",
|
"principal_role": "org_root_owner",
|
||||||
"workspace_id": "workspace.work.acme.exec",
|
"workspace_id": "workspace.work.acme.exec",
|
||||||
"tx_hash": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
"tx_hash": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
@ -161,7 +161,7 @@ Success (`200`):
|
|||||||
"regulatory_profile_id": "us_general_2026",
|
"regulatory_profile_id": "us_general_2026",
|
||||||
"offer_id": "edut.workspace.core",
|
"offer_id": "edut.workspace.core",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"principal_role": "org_root_owner",
|
"principal_role": "org_root_owner",
|
||||||
"wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5",
|
"wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5",
|
||||||
"tx_hash": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
"tx_hash": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
|||||||
@ -15,7 +15,7 @@ Content-Type: application/json
|
|||||||
"device_id": "desktop-7f6f3a9b",
|
"device_id": "desktop-7f6f3a9b",
|
||||||
"platform": "desktop",
|
"platform": "desktop",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"principal_role": "org_root_owner",
|
"principal_role": "org_root_owner",
|
||||||
"app_version": "0.1.0",
|
"app_version": "0.1.0",
|
||||||
"push_provider": "none"
|
"push_provider": "none"
|
||||||
@ -49,7 +49,7 @@ Authorization: Bearer <wallet-session>
|
|||||||
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
||||||
"device_id": "desktop-7f6f3a9b",
|
"device_id": "desktop-7f6f3a9b",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"membership_status": "active",
|
"membership_status": "active",
|
||||||
"identity_assurance_level": "onramp_attested",
|
"identity_assurance_level": "onramp_attested",
|
||||||
"events": [
|
"events": [
|
||||||
@ -122,7 +122,7 @@ Content-Type: application/json
|
|||||||
{
|
{
|
||||||
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
|
||||||
"org_root_id": "org.acme.root",
|
"org_root_id": "org.acme.root",
|
||||||
"principal_id": "human.joshua",
|
"principal_id": "human.operator",
|
||||||
"category": "health_diagnostic",
|
"category": "health_diagnostic",
|
||||||
"summary": "Availability state has remained in continuity for 26 hours."
|
"summary": "Availability state has remained in continuity for 26 hours."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"membership_contract": "0x3EEb3342751D1Cfc0F90C9393e0B1cd5AcE6FfD8",
|
"membership_contract": "0x3EEb3342751D1Cfc0F90C9393e0B1cd5AcE6FfD8",
|
||||||
"entitlement_contract": "0xA1c06066206d0ea63a77A093FD38327Fd5663a43",
|
"entitlement_contract": "0xA1c06066206d0ea63a77A093FD38327Fd5663a43",
|
||||||
"offer_registry_contract": "0xA1c06066206d0ea63a77A093FD38327Fd5663a43",
|
"offer_registry_contract": "0xA1c06066206d0ea63a77A093FD38327Fd5663a43",
|
||||||
"treasury_wallet": "0xD148d4dFA882007e5226C90287622b3Af6eB56D7",
|
"treasury_wallet": "0x1111111111111111111111111111111111111111",
|
||||||
"mint_currency_mode": "ETH_TEST",
|
"mint_currency_mode": "ETH_TEST",
|
||||||
"mint_amount_atomic": "1",
|
"mint_amount_atomic": "1",
|
||||||
"usdc_contract": "0x0000000000000000000000000000000000000000",
|
"usdc_contract": "0x0000000000000000000000000000000000000000",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# EDUT Dev Infra Cutover Checklist
|
# EDUT Dev Infra Cutover Checklist
|
||||||
|
|
||||||
This checklist migrates EDUT infrastructure from `git.workvsg.com` to `git.edut.dev` with deterministic validation gates.
|
This checklist migrates EDUT infrastructure from `git.edut.dev` to `git.edut.dev` with deterministic validation gates.
|
||||||
|
|
||||||
Server target:
|
Server target:
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ Server target:
|
|||||||
- `git.edut.dev`
|
- `git.edut.dev`
|
||||||
2. SSH key access as root is available.
|
2. SSH key access as root is available.
|
||||||
3. Cloudflare proxy mode and SSL mode are configured to allow origin TLS.
|
3. Cloudflare proxy mode and SSL mode are configured to allow origin TLS.
|
||||||
4. Local private keys for `joshua`, `claude-code`, `codex` are available for validation.
|
4. Local private keys for `operator`, `claude-code`, `codex` are available for validation.
|
||||||
|
|
||||||
## Phase 1 - Server Setup
|
## Phase 1 - Server Setup
|
||||||
|
|
||||||
@ -40,18 +40,18 @@ Create users:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
||||||
"useradd -m -s /bin/bash joshua || true; useradd -m -s /bin/bash claude-code || true; useradd -m -s /bin/bash codex || true"
|
"useradd -m -s /bin/bash operator || true; useradd -m -s /bin/bash claude-code || true; useradd -m -s /bin/bash codex || true"
|
||||||
```
|
```
|
||||||
|
|
||||||
Install authorized keys:
|
Install authorized keys:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "install -d -m 700 /home/joshua/.ssh /home/claude-code/.ssh /home/codex/.ssh"
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "install -d -m 700 /home/operator/.ssh /home/claude-code/.ssh /home/codex/.ssh"
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "printf '%s\n' '<JOSHUA_PUBKEY>' > /home/joshua/.ssh/authorized_keys"
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "printf '%s\n' '<OWNER_PUBKEY>' > /home/operator/.ssh/authorized_keys"
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "printf '%s\n' '<CLAUDE_CODE_PUBKEY>' > /home/claude-code/.ssh/authorized_keys"
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "printf '%s\n' '<CLAUDE_CODE_PUBKEY>' > /home/claude-code/.ssh/authorized_keys"
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "printf '%s\n' '<CODEX_PUBKEY>' > /home/codex/.ssh/authorized_keys"
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 "printf '%s\n' '<CODEX_PUBKEY>' > /home/codex/.ssh/authorized_keys"
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
||||||
"chmod 600 /home/*/.ssh/authorized_keys; chown -R joshua:joshua /home/joshua/.ssh; chown -R claude-code:claude-code /home/claude-code/.ssh; chown -R codex:codex /home/codex/.ssh"
|
"chmod 600 /home/*/.ssh/authorized_keys; chown -R operator:operator /home/operator/.ssh; chown -R claude-code:claude-code /home/claude-code/.ssh; chown -R codex:codex /home/codex/.ssh"
|
||||||
```
|
```
|
||||||
|
|
||||||
Gate:
|
Gate:
|
||||||
@ -63,7 +63,7 @@ Gate:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
||||||
"usermod -aG sudo joshua; usermod -aG sudo claude-code; usermod -aG sudo codex"
|
"usermod -aG sudo operator; usermod -aG sudo claude-code; usermod -aG sudo codex"
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
||||||
"printf '%s\n' 'claude-code ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-claude-code"
|
"printf '%s\n' 'claude-code ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-claude-code"
|
||||||
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
ssh -i ~/.ssh/edut_codex root@5.78.148.229 \
|
||||||
@ -76,7 +76,7 @@ Gate:
|
|||||||
|
|
||||||
1. `visudo -cf` passes.
|
1. `visudo -cf` passes.
|
||||||
2. `claude-code` and `codex` have passwordless sudo.
|
2. `claude-code` and `codex` have passwordless sudo.
|
||||||
3. `joshua` remains standard sudo (password required).
|
3. `operator` remains standard sudo (password required).
|
||||||
|
|
||||||
### 4. SSH hardening + disable root login
|
### 4. SSH hardening + disable root login
|
||||||
|
|
||||||
@ -96,14 +96,14 @@ Validation:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/edut_codex codex@5.78.148.229 "sudo -n true && echo codex-sudo-ok"
|
ssh -i ~/.ssh/edut_codex codex@5.78.148.229 "sudo -n true && echo codex-sudo-ok"
|
||||||
ssh -i ~/.ssh/edut_joshua joshua@5.78.148.229 "id"
|
ssh -i ~/.ssh/edut_owner operator@5.78.148.229 "id"
|
||||||
ssh -i ~/.ssh/edut_codex -o BatchMode=yes root@5.78.148.229 "echo should-fail"
|
ssh -i ~/.ssh/edut_codex -o BatchMode=yes root@5.78.148.229 "echo should-fail"
|
||||||
```
|
```
|
||||||
|
|
||||||
Gate:
|
Gate:
|
||||||
|
|
||||||
1. `codex` login works and sudo works.
|
1. `codex` login works and sudo works.
|
||||||
2. `joshua` key login works.
|
2. `operator` key login works.
|
||||||
3. root login is denied.
|
3. root login is denied.
|
||||||
|
|
||||||
### 5. Firewall, fail2ban, unattended upgrades
|
### 5. Firewall, fail2ban, unattended upgrades
|
||||||
@ -228,15 +228,15 @@ Gate:
|
|||||||
Create admin and org:
|
Create admin and org:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/edut_codex codex@5.78.148.229 "sudo docker exec -u git gitea-gitea-1 gitea admin user create --username joshua --password 'CHANGE_ME_ADMIN_PASSWORD' --email j@edut.dev --admin --must-change-password=false"
|
ssh -i ~/.ssh/edut_codex codex@5.78.148.229 "sudo docker exec -u git gitea-gitea-1 gitea admin user create --username operator --password 'CHANGE_ME_ADMIN_PASSWORD' --email j@edut.dev --admin --must-change-password=false"
|
||||||
ssh -i ~/.ssh/edut_codex codex@5.78.148.229 "sudo docker exec -u git gitea-gitea-1 gitea admin org create --name edut --username joshua || true"
|
ssh -i ~/.ssh/edut_codex codex@5.78.148.229 "sudo docker exec -u git gitea-gitea-1 gitea admin org create --name edut --username operator || true"
|
||||||
```
|
```
|
||||||
|
|
||||||
Create repos:
|
Create repos:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
for r in web launcher contracts governance kernel platform-docs; do
|
for r in web launcher contracts governance kernel platform-docs; do
|
||||||
curl -u "joshua:CHANGE_ME_ADMIN_PASSWORD" \
|
curl -u "operator:CHANGE_ME_ADMIN_PASSWORD" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-X POST "https://git.edut.dev/api/v1/orgs/edut/repos" \
|
-X POST "https://git.edut.dev/api/v1/orgs/edut/repos" \
|
||||||
-d "{\"name\":\"$r\",\"private\":true}"
|
-d "{\"name\":\"$r\",\"private\":true}"
|
||||||
@ -253,9 +253,9 @@ Gate:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
for r in web launcher contracts governance kernel; do
|
for r in web launcher contracts governance kernel; do
|
||||||
git -C "/Users/vsg/Documents/VSG Codex/$r" push --mirror "https://git.edut.dev/edut/$r.git"
|
git -C "<workspace-root>/$r" push --mirror "https://git.edut.dev/edut/$r.git"
|
||||||
done
|
done
|
||||||
git -C "/Users/vsg/Documents/VSG Codex/platform-docs" push --mirror "https://git.edut.dev/edut/platform-docs.git"
|
git -C "<workspace-root>/platform-docs" push --mirror "https://git.edut.dev/edut/platform-docs.git"
|
||||||
```
|
```
|
||||||
|
|
||||||
Gate:
|
Gate:
|
||||||
@ -266,7 +266,7 @@ Gate:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
for r in web launcher contracts governance kernel platform-docs; do
|
for r in web launcher contracts governance kernel platform-docs; do
|
||||||
git -C "/Users/vsg/Documents/VSG Codex/$r" remote set-url origin "https://git.edut.dev/edut/$r.git"
|
git -C "<workspace-root>/$r" remote set-url origin "https://git.edut.dev/edut/$r.git"
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -279,14 +279,14 @@ Gate:
|
|||||||
Search:
|
Search:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rg -n "git\\.workvsg\\.com" /Users/vsg/Documents/VSG\ Codex
|
rg -n "git\\.edut\\.dev" <workspace-root>
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace references in scripts/docs/default flags/manifests.
|
Replace references in scripts/docs/default flags/manifests.
|
||||||
|
|
||||||
Gate:
|
Gate:
|
||||||
|
|
||||||
1. No remaining production references to `git.workvsg.com`.
|
1. No remaining production references to `git.edut.dev`.
|
||||||
|
|
||||||
### 12. Freeze old host read-only
|
### 12. Freeze old host read-only
|
||||||
|
|
||||||
@ -341,7 +341,7 @@ Completed on `edut-prod` (`5.78.148.229`):
|
|||||||
- `api.edut.dev`: active `secretapi`
|
- `api.edut.dev`: active `secretapi`
|
||||||
- `edut.dev`, `www.edut.dev`: placeholder response only
|
- `edut.dev`, `www.edut.dev`: placeholder response only
|
||||||
7. `api.edut.dev/healthz` verified over HTTP and HTTPS.
|
7. `api.edut.dev/healthz` verified over HTTP and HTTPS.
|
||||||
8. Hardcoded `git.workvsg.com` references removed from active scripts/manifests/docs (migration checklist references intentionally retained as historical context).
|
8. Hardcoded `git.edut.dev` references removed from active scripts/manifests/docs (migration checklist references intentionally retained as historical context).
|
||||||
|
|
||||||
Remaining explicit follow-through:
|
Remaining explicit follow-through:
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ This smoke path validates wallet intent, membership, member channel, and governa
|
|||||||
## 1) Start Secret API
|
## 1) Start Secret API
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd "/Users/vsg/Documents/VSG Codex/web/backend/secretapi"
|
cd "<workspace-root>/web/backend/secretapi"
|
||||||
cp .env.example .env.local
|
cp .env.example .env.local
|
||||||
go run .
|
go run .
|
||||||
```
|
```
|
||||||
@ -15,7 +15,7 @@ Default bind is `http://127.0.0.1:8080`.
|
|||||||
## 2) Start Launcher Harness
|
## 2) Start Launcher Harness
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd "/Users/vsg/Documents/VSG Codex/launcher"
|
cd "<workspace-root>/launcher"
|
||||||
make serve
|
make serve
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -28,14 +28,14 @@ Current test-mode settings:
|
|||||||
1. Native binary:
|
1. Native binary:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /Users/vsg/Documents/VSG\ Codex/web/backend/secretapi
|
cd <home>/Documents/VSG\ Codex/web/backend/secretapi
|
||||||
go build -o secretapi .
|
go build -o secretapi .
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Container image:
|
2. Container image:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /Users/vsg/Documents/VSG\ Codex/web/backend/secretapi
|
cd <home>/Documents/VSG\ Codex/web/backend/secretapi
|
||||||
docker build -t edut/secretapi:latest .
|
docker build -t edut/secretapi:latest .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ This checklist defines backend requirements for app-native member communication.
|
|||||||
|
|
||||||
Implementation status:
|
Implementation status:
|
||||||
|
|
||||||
1. Local and deployed reference implementation exists in `/Users/vsg/Documents/VSG Codex/web/backend/secretapi` (sqlite-backed) for register/unregister/events/ack/support.
|
1. Local and deployed reference implementation exists in `<workspace-root>/web/backend/secretapi` (sqlite-backed) for register/unregister/events/ack/support.
|
||||||
2. Wallet-session hardening is implemented via session tokens from `/secret/wallet/verify`; launch should set `SECRET_API_REQUIRE_WALLET_SESSION=true` to enforce fail-closed behavior.
|
2. Wallet-session hardening is implemented via session tokens from `/secret/wallet/verify`; launch should set `SECRET_API_REQUIRE_WALLET_SESSION=true` to enforce fail-closed behavior.
|
||||||
|
|
||||||
## Required Endpoints
|
## Required Endpoints
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
## Sources Referenced
|
## Sources Referenced
|
||||||
|
|
||||||
- `/Users/vsg/Documents/VSG Codex/kernel/docs/architecture/PRODUCT_ARCHITECTURE.md`
|
- `<workspace-root>/kernel/docs/architecture/PRODUCT_ARCHITECTURE.md`
|
||||||
- `/Users/vsg/Documents/VSG Codex/kernel/docs/architecture/REPO_TOPOLOGY_25Y.md`
|
- `<workspace-root>/kernel/docs/architecture/REPO_TOPOLOGY_25Y.md`
|
||||||
- `/Users/vsg/Documents/VSG Codex/platform-docs/business/38_license_model.md` (noted as potentially legacy relative to latest direction)
|
- `<workspace-root>/platform-docs/business/38_license_model.md` (noted as potentially legacy relative to latest direction)
|
||||||
|
|
||||||
## Alignment Summary
|
## Alignment Summary
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,9 @@ Use this runbook after valid Gitea credentials are available (PAT or git credent
|
|||||||
|
|
||||||
## Local Seed Repos (already initialized)
|
## Local Seed Repos (already initialized)
|
||||||
|
|
||||||
1. `launcher` at `/Users/vsg/Documents/VSG Codex/launcher` (commit `ac871d7`)
|
1. `launcher` at `<workspace-root>/launcher` (commit `ac871d7`)
|
||||||
2. `governance` at `/Users/vsg/Documents/VSG Codex/governance` (commit `80eaca7`)
|
2. `governance` at `<workspace-root>/governance` (commit `80eaca7`)
|
||||||
3. `contracts` at `/Users/vsg/Documents/VSG Codex/contracts` (commit `dbac2f0`)
|
3. `contracts` at `<workspace-root>/contracts` (commit `dbac2f0`)
|
||||||
|
|
||||||
## Create Remote Repos
|
## Create Remote Repos
|
||||||
|
|
||||||
@ -27,29 +27,29 @@ Repeat for `governance` and `contracts`.
|
|||||||
Or run the helper with PAT:
|
Or run the helper with PAT:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd "/Users/vsg/Documents/VSG Codex/web"
|
cd "<workspace-root>/web"
|
||||||
./scripts/publish_split_repos.sh <gitea_pat>
|
./scripts/publish_split_repos.sh <gitea_pat>
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run it without arguments to use git credential helper auth for `git.edut.dev`:
|
Or run it without arguments to use git credential helper auth for `git.edut.dev`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd "/Users/vsg/Documents/VSG Codex/web"
|
cd "<workspace-root>/web"
|
||||||
./scripts/publish_split_repos.sh
|
./scripts/publish_split_repos.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Push Local Seed Repos
|
## Push Local Seed Repos
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd "/Users/vsg/Documents/VSG Codex/launcher"
|
cd "<workspace-root>/launcher"
|
||||||
git remote add origin https://git.edut.dev/edut/launcher.git
|
git remote add origin https://git.edut.dev/edut/launcher.git
|
||||||
git push -u origin main
|
git push -u origin main
|
||||||
|
|
||||||
cd "/Users/vsg/Documents/VSG Codex/governance"
|
cd "<workspace-root>/governance"
|
||||||
git remote add origin https://git.edut.dev/edut/governance.git
|
git remote add origin https://git.edut.dev/edut/governance.git
|
||||||
git push -u origin main
|
git push -u origin main
|
||||||
|
|
||||||
cd "/Users/vsg/Documents/VSG Codex/contracts"
|
cd "<workspace-root>/contracts"
|
||||||
git remote add origin https://git.edut.dev/edut/contracts.git
|
git remote add origin https://git.edut.dev/edut/contracts.git
|
||||||
git push -u origin main
|
git push -u origin main
|
||||||
```
|
```
|
||||||
|
|||||||
@ -62,12 +62,23 @@ Implemented now:
|
|||||||
33. Marketplace/member/governance OpenAPI contracts now declare wallet-session usage for launcher/app-channel calls.
|
33. Marketplace/member/governance OpenAPI contracts now declare wallet-session usage for launcher/app-channel calls.
|
||||||
34. Offer catalogs and marketplace responses now carry execution pacing profiles (`governed_human_pace` vs `local_hardware_speed`) for connector/runtime policy alignment.
|
34. Offer catalogs and marketplace responses now carry execution pacing profiles (`governed_human_pace` vs `local_hardware_speed`) for connector/runtime policy alignment.
|
||||||
35. Membership and checkout confirm handlers now reject tx-hash replay across different designations/quotes (`tx_hash_replay`) with deterministic tests and live validation.
|
35. Membership and checkout confirm handlers now reject tx-hash replay across different designations/quotes (`tx_hash_replay`) with deterministic tests and live validation.
|
||||||
|
36. Wallet sessions now bind to client context (`X-Edut-Device-Binding` with user-agent fallback) and reject foreign-context replay (`wallet_session_context_mismatch`) with deterministic tests.
|
||||||
|
37. Dependency-edge degraded-mode controls are now enforced in `secretapi`: chain edge blocks settlement confirms (`dependency.chain_unavailable`), on-ramp degradation blocks `fiat_onramp` while preserving `crypto_direct`, cloud edge blocks remote channel/support mutations, model edge blocks AI-layer checkout activation, and recovery-window hold semantics are tested (`AB6-*` coverage in `backend/secretapi/app_test.go` and `backend/secretapi/dependency_edges_test.go`).
|
||||||
|
38. Member-channel anti-fatigue controls are now enforced in `secretapi`: deterministic event burst throttling with digest fallback (`channel_digest`) is configurable via `SECRET_API_MEMBER_CHANNEL_EVENT_BURST_LIMIT` and `SECRET_API_MEMBER_CHANNEL_EVENT_BURST_WINDOW_SECONDS`, digest payloads track aggregated `suppressed_count` within each throttle window, and `GET /member/channel/events` now returns digest summary fields (`digest_active`, `digest_suppressed_count`) (`backend/secretapi/app_test.go`).
|
||||||
|
39. Member-channel trust calibration signals are now exposed in `secretapi`: each event includes deterministic `trust_posture` and `review_level`, and event polling responses include aggregate `trusted_event_count`/`review_event_count` for operator triage (`backend/secretapi/app.go`, `backend/secretapi/models.go`, `backend/secretapi/app_test.go`).
|
||||||
|
40. Chain-adjacent degraded-mode controls now include TLS and DNS dependency edges: membership and marketplace confirm fail closed with `dependency.tls_unavailable` / `dependency.dns_unavailable`, health surface exposes `tls`/`dns` dependency states, and conformance vectors include AB6-007/AB6-008 (`backend/secretapi/app.go`, `backend/secretapi/dependency_edges.go`, `backend/secretapi/app_test.go`, `backend/secretapi/dependency_edges_test.go`).
|
||||||
|
41. Marketplace checkout now supports deterministic financial threshold governance: quote responses expose `approval_required`/`approval_reason`, confirm fails closed with `approval_required` when threshold-gated approvals are missing, and confirm/audit outputs persist `approval_token_ref` + `approval_actor` (`backend/secretapi/marketplace.go`, `backend/secretapi/store.go`, `backend/secretapi/app_test.go`).
|
||||||
|
42. Admin assurance gates are now explicitly separated from EDUT ID state: governance admin controls (install token issuance + lease heartbeat/offline renew) and owner support tickets require `onramp_attested` assurance in addition to active membership/owner-role checks, and governance install status reports deterministic `identity_assurance_insufficient` blockers (`backend/secretapi/app.go`, `backend/secretapi/app_test.go`, `docs/api/governance-installer.openapi.yaml`, `docs/api/member-channel.openapi.yaml`).
|
||||||
|
43. Secret API error envelopes now include deterministic remediation guidance via `next_step` alongside `code` and `correlation_id`, with coverage for approval, assurance, session, dependency, and context failure paths (`backend/secretapi/app.go`, `backend/secretapi/app_test.go`, `docs/api/governance-installer.openapi.yaml`, `docs/api/member-channel.openapi.yaml`).
|
||||||
|
44. Dependency-edge stability windows now explicitly cover TLS and DNS recovery behavior in settlement paths: membership/marketplace confirmations remain fail-closed during recovery and resume automatically after window completion (`backend/secretapi/app_test.go`, `docs/conformance/availability-boundary-vectors.md` AB6-009/AB6-010).
|
||||||
|
45. Setup health diagnostics endpoint now exposes deterministic onboarding readiness checks (`/secret/setup/health`) for wallet/session/membership/assurance/principal state, with actionable next steps for failed checks and regression coverage in `backend/secretapi/app_test.go` (`docs/api/secret-system.openapi.yaml`).
|
||||||
|
46. Marketplace checkout confirm now enforces setup readiness before high-impact entitlement activation: non-bundled confirms fail closed with `setup_incomplete` when wallet setup health is not checkout-ready, with deterministic remediation to `/secret/setup/health` (`backend/secretapi/marketplace.go`, `backend/secretapi/app.go`, `backend/secretapi/app_test.go`).
|
||||||
|
|
||||||
Remaining in this repo:
|
Remaining in this repo:
|
||||||
|
|
||||||
1. Wire live store checkout flow to production marketplace APIs when available.
|
1. Wire live store checkout flow to production marketplace APIs when available.
|
||||||
2. Replace deployment templates with real contract addresses after chain deployment: `IN_PROGRESS` (Base Sepolia addresses captured in `docs/deployment/contract-addresses.base-sepolia.json`; mainnet pending).
|
2. Replace deployment templates with real contract addresses after chain deployment: `IN_PROGRESS` (Base Sepolia addresses captured in `docs/deployment/contract-addresses.base-sepolia.json`; mainnet pending).
|
||||||
3. Keep cross-repo address parity with `/Users/vsg/Documents/VSG Codex/contracts/deploy/runtime-addresses.base-sepolia.json`: `IN_PROGRESS`.
|
3. Keep cross-repo address parity with `<workspace-root>/contracts/deploy/runtime-addresses.base-sepolia.json`: `IN_PROGRESS`.
|
||||||
4. Add launcher/governance install UI that consumes governance installer APIs.
|
4. Add launcher/governance install UI that consumes governance installer APIs.
|
||||||
|
|
||||||
Cross-repo dependencies (kernel/backend/contracts):
|
Cross-repo dependencies (kernel/backend/contracts):
|
||||||
|
|||||||
@ -9,7 +9,7 @@ usage() {
|
|||||||
TOKEN="${1:-}"
|
TOKEN="${1:-}"
|
||||||
ORG="${2:-edut}"
|
ORG="${2:-edut}"
|
||||||
HOST="${3:-git.edut.dev}"
|
HOST="${3:-git.edut.dev}"
|
||||||
ROOT="/Users/vsg/Documents/VSG Codex"
|
ROOT="<workspace-root>"
|
||||||
AUTH_MODE=""
|
AUTH_MODE=""
|
||||||
AUTH_USERNAME=""
|
AUTH_USERNAME=""
|
||||||
AUTH_PASSWORD=""
|
AUTH_PASSWORD=""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user