Skip to content
Merged
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
47 changes: 44 additions & 3 deletions obp-api/src/main/scala/code/api/util/Glossary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3735,6 +3735,47 @@ object Glossary extends MdcLoggable {
|In short, Static (standard) endpoints are defined in Git as Scala source code, where as Dynamic (custom) endpoints are defined in the OBP database.
|
|Modifications to Static endpoint core properties such as URLs and response bodies require source code changes and an instance restart. However, JSON Schema Validation and Dynamic Connector changes can be applied in real-time.
""".stripMargin)

glossaryItems += GlossaryItem(
title = "Resource Doc",
description =
s"""
|A Resource Doc is the machine readable definition / description of an OBP Endpoint.
|
|The aim is that as much endpoint definition as possible is *defined first* within the Resource Doc making the Resource Doc the canonical source of truth about the endpoints structure and behaviour.
|
|In total Resource Docs form the server registry of the API: every Endpoint, static or dynamic, is registered in the running server with its Resource Doc, and that registry is the source of truth about the API surface.
|
|Note that the Resource Docs (like the Glossary) can contain instance variables about the OBP-API instance that is running, so HOSTNAMES and various configuration settings are automatically correct.
|
|An OBP API instance only serves resource docs about endpoints that are actually enabled so any client (e.g. the OBP MCP Server or API Explorer) can use them as a capability discovery channel.
|
|
|Each Resource Doc includes:
|
| 1) The Operation ID / Scala Partial Function name (the source code / function that runs the endpoint) which uniquely identifies the Endpoint (e.g. getCoreAccountById)
| 2) The API version the Endpoint is implemented in
| 3) The request verb (GET, POST, PUT, DELETE etc.) and URL path
| 4) A summary and a longer description (markdown)
| 5) An example request body and a successful response body. These are generated from the actual Scala case classes the Endpoint uses, so field names and types reflect the real implementation rather than separately maintained documentation.
| 6) The possible error responses
| 7) Tags used to group Endpoints in the API Explorer and filter Resource Docs
| 8) The Roles (Entitlements) required to call the Endpoint. Roles declared in the Resource Doc are automatically checked by the framework at runtime.
| 9) Connector methods the Endpoint depends on (linking to the related Message Docs)
|
|Because the Resource Doc registry lives inside the running server, it differs from a published OpenAPI file in two important ways:
|
|* The arrow of generation points from code to documentation: the Swagger / OpenAPI documents that OBP publishes are generated *from* the Resource Docs, not maintained alongside the code. This avoids documentation drift.
|
|* It covers Endpoints created at runtime: Dynamic Endpoints and the auto-generated CRUD Endpoints for Dynamic Entities get Resource Docs when they are created, so per-bank custom APIs are documented by the same mechanism as the static Endpoints - something a static, pre-published API description cannot do.
|
|Resource Docs are available over the Resource Doc endpoints in OBP format (which includes OBP specific metadata such as Roles, Tags and Connector methods) and in Swagger / OpenAPI format. They can be filtered by tags, functions and API collections.
|
|As mentioned above, Resource Docs power the API Explorer interface and are the natural foundation for programmatic consumers of the API surface: SDK generators, API management tooling and AI assistants (such as [Opey](/glossary#Opey) and [OBP-MCP](/glossary#OBP-MCP)) that need to discover, select and validate calls to Endpoints.
|
|See also [Endpoint](/glossary#Endpoint), [Static Endpoint](/glossary#Static-Endpoint), [Dynamic Endpoint Manage](/glossary#Dynamic-Endpoint-Manage), [Message Doc](/glossary#Message-Doc)
|
""".stripMargin)

glossaryItems += GlossaryItem(
Expand Down Expand Up @@ -5869,7 +5910,7 @@ object Glossary extends MdcLoggable {
|
|- **Documentation / discovery** — `list_endpoints_by_tag`, `get_endpoint_schema`, glossary tools. Served from local JSON, no network.
|- **Business calls** — `call_obp_api` proxies whatever the endpoint declares: `GET /banks/{BANK_ID}/accounts`, `POST .../transaction-requests/SEPA`, `PUT /accounts/{ACC}/label`, `DELETE /my/consents/{CONSENT_ID}`, etc. Real money / data moves.
|- **Index refresh** — at startup and on a timer, OBP-MCP re-fetches OBP's resource-docs and swagger to rebuild the local indexes, so discovery stays fast and offline.
|- **Index refresh** — at startup and on a timer, OBP-MCP re-fetches OBP's [Resource Docs](/glossary#Resource-Doc) and swagger to rebuild the local indexes, so discovery stays fast and offline.
|
|## Authentication and authorization
|
Expand All @@ -5894,7 +5935,7 @@ object Glossary extends MdcLoggable {
|
|OBP-MCP is the canonical way to make Open Bank Project endpoints **agent-callable**. Instead of teaching every LLM about every endpoint up front, the LLM is given five generic tools and lets the indexes and schemas guide it to the right call at runtime. The same server can serve internal agents (Opey) and external clients (Claude Desktop, IDE plugins, third-party agents) by switching auth providers.
|
|See also: [Opey](/glossary#Opey), [Consent](/glossary#Consent), [Authentication: OAuth 2.0](/glossary#Authentication:-OAuth-2.0).
|See also: [Opey](/glossary#Opey), [Resource Doc](/glossary#Resource-Doc), [Consent](/glossary#Consent), [Authentication: OAuth 2.0](/glossary#Authentication:-OAuth-2.0).
|
""")

Expand Down Expand Up @@ -5958,7 +5999,7 @@ object Glossary extends MdcLoggable {
|
|**OBP-MCP is the *tool surface* over OBP-API. Opey II is the *agent* that drives it.** Before OBP-MCP, Opey had to be both. Now OBP-MCP provides discovery and authenticated calls as a generic, multi-client surface (Claude Desktop, IDE plugins, third-party agents can all use it), and Opey II becomes a thinner, more focused orchestrator: planning, approvals, conversation state, streaming, and the chat UX that OBP-Portal embeds.
|
|See also: [OBP-MCP](/glossary#OBP-MCP), [Consent](/glossary#Consent), [Authentication: OAuth 2.0](/glossary#Authentication:-OAuth-2.0).
|See also: [OBP-MCP](/glossary#OBP-MCP), [Resource Doc](/glossary#Resource-Doc), [Consent](/glossary#Consent), [Authentication: OAuth 2.0](/glossary#Authentication:-OAuth-2.0).
|
""")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,22 +260,25 @@ object OpenCorridorSettlement extends MdcLoggable {

// Enqueue the Interface C messages in this same DB transaction (the outbox).
// Credit notifications went to each beneficiary at promise-report-back time
// (OpenCorridorProcessor); settlement sends each beneficiary an advice so
// its already-paid-out credits get marked settled.
// (OpenCorridorProcessor); settlement sends BOTH party banks an advice with
// the FULL covered list, so each node stamps its already-paid-out credits
// AND its own outbound promises settled — including the party that did not
// trigger the settle, which otherwise never learns of the coverage (the
// instruction only moves money, and at net zero it is not sent at all).
settlementAdviceCount <- Future {
covered.groupBy(_.mTo_BankId.get).map { case (beneficiaryBankId, rows) =>
val advice = OutBoundOpenCorridorSettlementAdvice(
settlement_id = settlementTrId,
currency = currency,
net_amount = netAbs.toString(),
debtor_bank_id = debtorBankId,
creditor_bank_id = creditorBankId,
covered_transaction_request_ids = rows.map(_.mTransactionRequestId.get),
idempotency_key = settlementTrId
)
val advice = OutBoundOpenCorridorSettlementAdvice(
settlement_id = settlementTrId,
currency = currency,
net_amount = netAbs.toString(),
debtor_bank_id = debtorBankId,
creditor_bank_id = creditorBankId,
covered_transaction_request_ids = covered.map(_.mTransactionRequestId.get),
idempotency_key = settlementTrId
)
Set(bankIdA, bankIdB).map { partyBankId =>
MessageOutbox.enqueue(
MessageOutbox.TYPE_OPEN_CORRIDOR, settlementTrId, MessageOutbox.SUBJECT_TYPE_SETTLEMENT_ID,
"obp_settlement_advice", beneficiaryBankId, Serialization.write(advice))
"obp_settlement_advice", partyBankId, Serialization.write(advice))
}.size
}
settlementInstructionCount <- Future {
Expand Down
16 changes: 9 additions & 7 deletions obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2643,13 +2643,15 @@ class Http4s700RoutesTest extends ServerSetupWithTestData {
outboxRows.size shouldBe 3
val adviceRows = outboxRows.filter(_.operationName == "obp_settlement_advice")
adviceRows.map(_.targetId).sorted shouldBe List(testBankId1.value, testBankId2.value).sorted
val bank2Advice = adviceRows.find(_.targetId == testBankId2.value)
.map(row => parse(row.payloadJson))
.getOrElse(fail("bank2's settlement advice should be enqueued"))
(bank2Advice \ "settlement_id") shouldBe JString(settlementId)
(bank2Advice \ "covered_transaction_request_ids") match {
case JArray(ids) => ids.collect { case JString(id) => id }.toSet shouldBe Set(promise1, promise2)
case _ => fail("covered_transaction_request_ids should be an array")
// Both party banks get the advice with the FULL covered list (both
// directions): each node stamps its credits AND its own promises from it.
adviceRows.foreach { row =>
val advice = parse(row.payloadJson)
(advice \ "settlement_id") shouldBe JString(settlementId)
(advice \ "covered_transaction_request_ids") match {
case JArray(ids) => ids.collect { case JString(id) => id }.toSet shouldBe Set(promise1, promise2, promise3)
case _ => fail("covered_transaction_request_ids should be an array")
}
}
val instructionRow = outboxRows.filter(_.operationName == "obp_settlement_instruction") match {
case row :: Nil => row
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,15 @@ case class OutBoundOpenCorridorSettlementInstruction(
)

/**
* `obp_settlement_advice` — published to each BENEFICIARY bank's vhost after a
* netted settle: "the promises you already paid out against are now covered".
* Purely reconciliatory — no money moves on this message (the debtor's
* `obp_settlement_instruction` does that). One advice per beneficiary bank,
* listing exactly the covered promise ids where that bank was the creditor.
* `obp_settlement_advice` — published to BOTH party banks' vhosts after a
* netted settle: "these promises are now covered". Purely reconciliatory — no
* money moves on this message (the debtor's `obp_settlement_instruction` does
* that, and at net zero no instruction exists at all — this advice is then the
* only settle-time message either bank receives).
* `covered_transaction_request_ids` is the FULL covered list of the pair, both
* directions: each node stamps whatever matches its own records (credits it
* paid out AND its own outbound promises); ids of the counterparty's records
* match nothing there and are ignored.
* Credit notifications themselves travel at promise-report-back time, not here.
*/
case class OutBoundOpenCorridorSettlementAdvice(
Expand Down
Loading