Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
permissions:
contents: read
concurrency:
group: pages
group: pages-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
Expand Down
13 changes: 10 additions & 3 deletions examples/postman/MAVULA-API-v1.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"info": {
"_postman_id": "46219cb0-ef74-4fb4-8b79-6f4e38aa4c8b",
"name": "MAVULA API v1",
"description": "Public identity, ledger and workbench integration workflows.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{ "key": "identity_base_url", "value": "https://identity.mavula.dev" },
{ "key": "ledger_base_url", "value": "https://ledger.mavula.dev" },
{ "key": "workbench_base_url", "value": "https://workbench.mavula.dev" },
{ "key": "identity_base_url", "value": "https://identity.mavula.io" },
{ "key": "ledger_base_url", "value": "https://ledger.mavula.io" },
{ "key": "workbench_base_url", "value": "https://workbench.mavula.io" },
{ "key": "client_id", "value": "" },
{ "key": "client_assertion", "value": "" },
{ "key": "access_token", "value": "" },
{ "key": "account_id", "value": "" },
{ "key": "request_id", "value": "" }
Expand All @@ -17,6 +20,7 @@
"name": "Identity",
"item": [
{
"id": "b5087a27-65f4-42ae-943c-559b0af9ce81",
"name": "Effective identity",
"request": {
"method": "GET",
Expand All @@ -30,6 +34,7 @@
"name": "Accounts",
"item": [
{
"id": "4ce12ccf-e45b-47dc-bf4d-8c67dfbdc421",
"name": "Create account",
"request": {
"method": "POST",
Expand All @@ -44,6 +49,7 @@
}
},
{
"id": "096c1163-bf05-4e72-8b31-627dfeb29c6f",
"name": "Submit freeze request",
"request": {
"method": "POST",
Expand All @@ -63,6 +69,7 @@
"name": "Workbench",
"item": [
{
"id": "bb96c02f-4210-4f78-b8ed-f540c466f835",
"name": "Read platform status",
"request": {
"method": "GET",
Expand Down
52 changes: 40 additions & 12 deletions openapi/identity-access.public.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ info:
name: GNU Affero General Public License v3.0 only
identifier: AGPL-3.0-only
servers:
- url: https://identity.mavula.dev
- url: https://identity.mavula.io
description: Identity Access public endpoint
tags:
- name: Discovery
Expand All @@ -33,14 +33,14 @@ paths:
application/json:
schema: { $ref: '#/components/schemas/OpenIdConfiguration' }
example:
issuer: https://identity.mavula.dev
authorization_endpoint: https://identity.mavula.dev/auth
token_endpoint: https://identity.mavula.dev/token
jwks_uri: https://identity.mavula.dev/jwks
revocation_endpoint: https://identity.mavula.dev/token/revocation
issuer: https://identity.mavula.io
authorization_endpoint: https://identity.mavula.io/auth
token_endpoint: https://identity.mavula.io/token
jwks_uri: https://identity.mavula.io/jwks
revocation_endpoint: https://identity.mavula.io/token/revocation
response_types_supported: [code]
grant_types_supported: [authorization_code, refresh_token, client_credentials]
token_endpoint_auth_methods_supported: [client_secret_basic, client_secret_post]
token_endpoint_auth_methods_supported: [none, private_key_jwt]
id_token_signing_alg_values_supported: [PS256]
/jwks:
get:
Expand Down Expand Up @@ -72,7 +72,7 @@ paths:
'302':
description: Redirect to the operator interaction or registered callback URI
headers:
Location: { schema: { type: string, format: uri } }
Location: { schema: { type: string } }
'400': { $ref: '#/components/responses/OAuthError' }
/token:
post:
Expand All @@ -93,7 +93,14 @@ paths:
authorization_code:
value: { grant_type: authorization_code, code: auth_code_value, redirect_uri: 'https://app.example/callback', code_verifier: pkce_verifier }
client_credentials:
value: { grant_type: client_credentials, client_id: service_client, client_secret: redacted, scope: internal.worker }
value:
grant_type: client_credentials
client_id: service_client
client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion: signed_private_key_jwt
resource: urn:mavula:ledger-core
tenant_id: tenant_01
scope: internal.worker
responses:
'200':
description: OAuth token response
Expand Down Expand Up @@ -154,6 +161,15 @@ components:
openid: Authenticate an operator.
profile: Read effective identity claims.
finance.read: Read tenant-scoped financial resources.
finance.write: Submit tenant-scoped financial operations.
finance.approve: Approve controlled financial operations.
configuration.write: Manage tenant product configuration.
compliance.manage: Manage regulatory interoperability operations.
audit.read: Read authorized audit evidence.
identity.admin: Manage institutional identity configuration.
workbench.read: Read Workbench operational state.
workbench.jobs.write: Submit supported Workbench jobs.
observability.read: Read authorized operational status.
clientCredentials:
tokenUrl: /token
scopes:
Expand Down Expand Up @@ -199,6 +215,10 @@ components:
redirect_uri: { type: string, format: uri }
code_verifier: { type: string }
client_id: { type: string }
client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
client_assertion: { type: string }
resource: { type: string }
tenant_id: { type: string }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remova o seletor de tenant dos grants de operador

Ao adicionar tenant_id aos schemas de authorization_code (e também de refresh_token, linhas 231-234), o contrato público passa a aceitar um tenant informado pelo cliente em grants que já devem estar vinculados ao authorization code/refresh token. Em validadores/SDKs gerados a partir deste OpenAPI, isso viola o modelo documentado de tenant derivado da identidade e cria um caminho suportado para tentativas de troca de tenant; mantenha o seletor somente no grant de serviço ou rejeite-o nesses grants.

Useful? React with 👍 / 👎.

RefreshTokenRequest:
type: object
additionalProperties: false
Expand All @@ -208,15 +228,22 @@ components:
refresh_token: { type: string }
client_id: { type: string }
scope: { type: string }
client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
client_assertion: { type: string }
resource: { type: string }
tenant_id: { type: string }
ClientCredentialsTokenRequest:
type: object
additionalProperties: false
required: [grant_type]
required: [grant_type, client_id, client_assertion_type, client_assertion, resource]
properties:
grant_type: { const: client_credentials }
client_id: { type: string }
client_secret: { type: string, format: password }
scope: { type: string }
client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
client_assertion: { type: string }
resource: { type: string }
tenant_id: { type: string }
RevokeTokenRequest:
type: object
additionalProperties: false
Expand All @@ -225,7 +252,8 @@ components:
token: { type: string }
token_type_hint: { type: string, enum: [access_token, refresh_token] }
client_id: { type: string }
client_secret: { type: string, format: password }
client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
client_assertion: { type: string }
TokenResponse:
type: object
required: [access_token, token_type, expires_in]
Expand Down
63 changes: 35 additions & 28 deletions openapi/ledger-core.public.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ info:
name: GNU Affero General Public License v3.0 only
identifier: AGPL-3.0-only
servers:
- url: https://ledger.mavula.dev
- url: https://ledger.mavula.io
description: Ledger Core public endpoint
security: [{ bearerAuth: [] }]
tags:
Expand Down Expand Up @@ -717,16 +717,16 @@ components:
required: [account_code]
properties:
account_code: { type: string, minLength: 1, maxLength: 64 }
debit_amount: { type: string }
credit_amount: { type: string }
debit_amount: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
credit_amount: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
AccountPosting:
type: object
additionalProperties: false
required: [account_id, direction, amount, currency]
properties:
account_id: { type: string }
direction: { type: string, enum: [DEBIT, CREDIT] }
amount: { type: string }
amount: { type: string, pattern: '^(?:[1-9][0-9]*(?:\.[0-9]{1,2})?|0\.(?:0[1-9]|[1-9][0-9]?))$' }
currency: { type: string, pattern: '^[A-Z]{3}$' }
reference: { type: string, maxLength: 160 }
FinancialCorrection:
Expand All @@ -737,12 +737,15 @@ components:
type: object
required: [amount, currency]
properties:
amount: { type: string }
amount: { type: string, pattern: '^(?:[1-9][0-9]*(?:\.[0-9]{1,2})?|0\.(?:0[1-9]|[1-9][0-9]?))$' }
currency: { type: string, pattern: '^[A-Z]{3}$' }
allocation:
type: object
required: [principal, interest, fees]
properties: { principal: { type: string }, interest: { type: string }, fees: { type: string } }
properties:
principal: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
interest: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
fees: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
journal:
type: object
required: [ledger_lines]
Expand All @@ -760,28 +763,32 @@ components:
reason: { type: string, minLength: 1, maxLength: 500 }
correction: { $ref: '#/components/schemas/FinancialCorrection' }
FinancialAdjustmentRequest:
allOf:
- $ref: '#/components/schemas/CreateFinancialAdjustment'
- type: object
required: [id, status, requested_by, correlation_id, created_at]
properties:
id: { type: string }
status: { $ref: '#/components/schemas/DecisionStatus' }
target_transaction_id: { type: string }
target_journal_entry_id: { type: string }
target_loan_id: { type: string }
requested_by: { type: string }
decided_by: { type: string }
decision_reason: { type: string }
failure_reason: { type: string }
correlation_id: { type: string }
reversal_transaction_id: { type: string }
reversal_journal_entry_id: { type: string }
replacement_transaction_id: { type: string }
replacement_journal_entry_id: { type: string }
created_at: { type: string, format: date-time }
decided_at: { type: string, format: date-time }
applied_at: { type: string, format: date-time }
type: object
additionalProperties: false
required: [id, target_type, target_id, adjustment_type, status, reason, requested_by, correlation_id, created_at]
properties:
id: { type: string }
target_type: { type: string, enum: [TRANSACTION, JOURNAL_ENTRY] }
target_id: { type: string, minLength: 1, maxLength: 128 }
adjustment_type: { type: string, enum: [REVERSAL, CORRECTION] }
status: { $ref: '#/components/schemas/DecisionStatus' }
reason: { type: string, minLength: 1, maxLength: 500 }
correction: { $ref: '#/components/schemas/FinancialCorrection' }
target_transaction_id: { type: string }
target_journal_entry_id: { type: string }
target_loan_id: { type: string }
requested_by: { type: string }
decided_by: { type: string }
decision_reason: { type: string }
failure_reason: { type: string }
correlation_id: { type: string }
reversal_transaction_id: { type: string }
reversal_journal_entry_id: { type: string }
replacement_transaction_id: { type: string }
replacement_journal_entry_id: { type: string }
created_at: { type: string, format: date-time }
decided_at: { type: string, format: date-time }
applied_at: { type: string, format: date-time }
FinancialAdjustmentPage:
type: object
required: [items]
Expand Down
59 changes: 47 additions & 12 deletions openapi/workbench.public.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ info:
name: GNU Affero General Public License v3.0 only
identifier: AGPL-3.0-only
servers:
- url: https://workbench.mavula.dev
- url: https://workbench.mavula.io
description: Workbench public endpoint
security: [{ bearerAuth: [] }]
tags:
Expand All @@ -27,6 +27,9 @@ paths:
description: Submit tenant-scoped payment work. The tenant is always derived from the access token.
tags: [Jobs]
x-mavula-permissions: [workbench.jobs.write]
parameters:
- { $ref: '#/components/parameters/IdempotencyKey' }
- { $ref: '#/components/parameters/CorrelationId' }
requestBody:
required: true
content:
Expand All @@ -35,9 +38,7 @@ paths:
examples:
payment_capture:
value:
queue: payments
type: PAYMENT_CAPTURE
max_attempts: 3
payload:
idempotency_key: payment_capture_20260715_001
correlation_id: checkout_20260715_001
Expand All @@ -52,6 +53,8 @@ paths:
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'409': { $ref: '#/components/responses/Conflict' }
'503': { $ref: '#/components/responses/ServiceUnavailable' }
/api/jobs/{jobId}:
get:
operationId: getJob
Expand Down Expand Up @@ -289,18 +292,40 @@ components:
additionalProperties: false
properties: { limit: { type: integer, minimum: 1, maximum: 500 } }
CreateJob:
oneOf:
- $ref: '#/components/schemas/CreatePaymentCaptureJob'
- $ref: '#/components/schemas/CreatePaymentDisbursementJob'
- $ref: '#/components/schemas/CreatePaymentSettlementJob'
- $ref: '#/components/schemas/CreatePaymentReconciliationJob'
discriminator: { propertyName: type }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mapeie explicitamente o discriminator de jobs

Sem mapping, a resolução do discriminator do OpenAPI usa o valor de type como nome implícito de schema em components (especificação); como os payloads usam PAYMENT_CAPTURE, PAYMENT_DISBURSEMENT, etc., mas os schemas se chamam CreatePaymentCaptureJob e similares, geradores/validadores que aplicam o discriminator não conseguem selecionar o subtype correto. Adicione um mapping de cada valor de type para o respectivo schema ou remova o discriminator e deixe o oneOf ser resolvido pelos const.

Useful? React with 👍 / 👎.

CreatePaymentCaptureJob:
type: object
additionalProperties: false
required: [type, payload]
properties:
type: { type: string, enum: [PAYMENT_CAPTURE, PAYMENT_DISBURSEMENT, PAYMENT_SETTLEMENT, PAYMENT_RECONCILIATION] }
queue: { type: string, const: payments, default: payments }
payload:
oneOf:
- $ref: '#/components/schemas/PaymentStartPayload'
- $ref: '#/components/schemas/PaymentSettlementPayload'
- $ref: '#/components/schemas/PaymentReconciliationPayload'
max_attempts: { type: integer, minimum: 1, default: 3 }
type: { type: string, const: PAYMENT_CAPTURE }
payload: { $ref: '#/components/schemas/PaymentStartPayload' }
Comment on lines 303 to +307

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Atualize o exemplo de job para o novo contrato estrito

Com este novo schema, cada variante de CreateJob tem additionalProperties: false e expõe apenas type e payload; porém src/content/docs/v1/guides/payment-jobs.mdx:16 ainda instrui o envio de queue e max_attempts. Esse curl copiado pelos integradores será rejeitado como 400 contra o contrato publicado, então remova esses campos do guia ou recoloque-os no schema se ainda forem aceitos.

Useful? React with 👍 / 👎.

CreatePaymentDisbursementJob:
type: object
additionalProperties: false
required: [type, payload]
properties:
type: { type: string, const: PAYMENT_DISBURSEMENT }
payload: { $ref: '#/components/schemas/PaymentStartPayload' }
CreatePaymentSettlementJob:
type: object
additionalProperties: false
required: [type, payload]
properties:
type: { type: string, const: PAYMENT_SETTLEMENT }
payload: { $ref: '#/components/schemas/PaymentSettlementPayload' }
CreatePaymentReconciliationJob:
type: object
additionalProperties: false
required: [type, payload]
properties:
type: { type: string, const: PAYMENT_RECONCILIATION }
payload: { $ref: '#/components/schemas/PaymentReconciliationPayload' }
WorkerJob:
type: object
required: [id, queue, type, tenant_id, payload, status, attempts, max_attempts, created_at, updated_at]
Expand Down Expand Up @@ -397,12 +422,19 @@ components:
payload: { type: object, additionalProperties: true }
PlatformStatus:
type: object
additionalProperties: false
required: [status, service, version, environment, started_at, uptime_seconds, dependencies, worker, schedules, queues]
properties:
service: { type: string }
status: { type: string, enum: [ok, degraded, down] }
timestamp: { type: string, format: date-time }
version: { type: string }
environment: { type: string }
started_at: { type: string, format: date-time }
uptime_seconds: { type: integer, minimum: 0 }
worker: { type: object, additionalProperties: true }
dependencies: { type: object, additionalProperties: true }
schedules: { type: array, items: { $ref: '#/components/schemas/ScheduledJob' } }
queues: { type: array, items: { $ref: '#/components/schemas/QueueStats' } }
HttpError:
type: object
required: [statusCode, message]
Expand Down Expand Up @@ -432,3 +464,6 @@ components:
Conflict:
description: Idempotency key, state, or delivery reference conflicts with existing state
content: { application/json: { schema: { $ref: '#/components/schemas/HttpError' } } }
ServiceUnavailable:
description: Durable receipt storage or queue execution is temporarily unavailable
content: { application/json: { schema: { $ref: '#/components/schemas/HttpError' } } }
Loading