Skip to content

Core · Service · Active

platform-connectors-service

Shared datasource boundary used by product backends and MCP tools to reach Salesforce through source-based, service-to-service connector contracts.

  • TypeScript
  • NestJS 11
  • PostgreSQL
  • jsforce
  • HTTP internal APIs

Spec sheet

Boundary

Core / Datasource

Runtime

NestJS 11 HTTP service

Default port

3200

Security

Internal-only surface guarded by x-platform-internal-token

Primary connector

Salesforce via jsforce

Persistence

PostgreSQL via CONNECTORS_DATABASE_URL

Responsibilities

  • Keep vendor SDK logic out of product BFFs.
  • Expose connector health, source configuration and capability discovery.
  • Support Salesforce object describe, structured query, raw read-only SOQL, SOSL and cursor pagination.
  • Expose single-record CRUD, batch record operations and controlled Apex REST invocation for product BFFs.

Interfaces and contract surface

  • GET /health
  • GET /internal/connectors/health
  • GET /internal/connectors/sources
  • POST /internal/connectors/sources/:sourceId/test
  • POST /internal/connectors/sources/:sourceId/configure
  • GET /internal/connectors/sources/:sourceId/status
  • GET /internal/connectors/sources/:sourceId/capabilities
  • GET /internal/connectors/sources/:sourceId/describe/objects
  • GET /internal/connectors/sources/:sourceId/describe/objects/:objectName
  • GET /internal/connectors/sources/:sourceId/describe/objects/:objectName/fields
  • POST /internal/connectors/sources/:sourceId/query/run
  • POST /internal/connectors/sources/:sourceId/query/page
  • POST /internal/connectors/sources/:sourceId/query/more
  • POST /internal/connectors/sources/:sourceId/query/raw
  • POST /internal/connectors/sources/:sourceId/query/raw/page
  • POST /internal/connectors/sources/:sourceId/query/raw/more
  • POST /internal/connectors/sources/:sourceId/query/sosl
  • POST /internal/connectors/sources/:sourceId/records/:objectName
  • POST /internal/connectors/sources/:sourceId/records/:objectName/create-many
  • POST /internal/connectors/sources/:sourceId/records/:objectName/update-many
  • POST /internal/connectors/sources/:sourceId/records/:objectName/upsert-many
  • POST /internal/connectors/sources/:sourceId/records/:objectName/delete-many
  • PUT /internal/connectors/sources/:sourceId/records/:objectName/:recordId
  • DELETE /internal/connectors/sources/:sourceId/records/:objectName/:recordId
  • POST /internal/connectors/sources/:sourceId/apex/invoke

Consumers

Dependencies and external touchpoints

Notes

  • Frontend applications must never call this service directly.
  • The current public proxy does not expose a dedicated host for this service in local stack.
  • The legacy /internal/connectors/salesforce/* controller has been removed; consumers should use source-based endpoints under /internal/connectors/sources/:sourceId.

Source references

  • platform-connectors-service/README.md
  • docs/core-services-integration.md
  • platform-connectors-service/package.json

Come integrarsi davvero ​

platform-connectors-service e un boundary esclusivamente service-to-service. I frontend non devono chiamarlo mai direttamente: un backend/BFF di prodotto traduce il contratto tecnico in un contratto pubblico piu adatto al dominio applicativo.

Variabili utili ​

bash
export CONNECTORS_URL=http://127.0.0.1:3200
export INTERNAL_TOKEN=platform-local-stack-internal-token
export SOURCE_ID=salesforce-default

salesforce-default e il sourceId locale standard usato dai BFF quando non devono gestire piu sorgenti.

1. Discovery rapida di sorgenti e stato ​

bash
curl -sS "$CONNECTORS_URL/internal/connectors/sources" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"
bash
curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/status" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"

2. Test e configurazione di una sorgente Salesforce ​

Prima puoi validare le credenziali senza persisterle:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/test" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "providerKey": "salesforce",
    "config": {
      "mode": "username-password",
      "apiVersion": "60.0",
      "loginUrl": "https://login.salesforce.com",
      "username": "integration@example.com",
      "password": "super-secret-password",
      "securityToken": "security-token"
    }
  }'

Quando il probe e corretto, usa lo stesso payload su /configure per salvarlo:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/configure" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "providerKey": "salesforce",
    "config": {
      "mode": "username-password",
      "apiVersion": "60.0",
      "loginUrl": "https://login.salesforce.com",
      "username": "integration@example.com",
      "password": "super-secret-password",
      "securityToken": "security-token"
    }
  }'

3. Describe e query strutturata ​

Campi di un oggetto:

bash
curl -sS \
  "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects/Contact/fields" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"

Per i BFF che hanno gia template SOQL/SOSL propri, il contratto source-based espone anche:

  • POST /internal/connectors/sources/$SOURCE_ID/query/raw
  • POST /internal/connectors/sources/$SOURCE_ID/query/raw/page
  • POST /internal/connectors/sources/$SOURCE_ID/query/raw/more
  • POST /internal/connectors/sources/$SOURCE_ID/query/sosl

Query strutturata paginata:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/page" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "pageSize": 100,
    "query": {
      "kind": "structured",
      "query": {
        "objectName": "Contact",
        "select": ["Id", "Name", "Email"],
        "sort": [
          {
            "field": "Name",
            "direction": "ASC"
          }
        ],
        "limit": 100
      }
    }
  }'

Se la risposta contiene nextCursor, la pagina successiva si legge cosi:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/more" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "cursor": "<NEXT_CURSOR>",
    "pageSize": 100
  }'

4. Operazioni record e Apex controllato ​

Creazione singola:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "record": {
      "LastName": "Rossi",
      "Email": "mario.rossi@example.com"
    }
  }'

Operazioni batch:

  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/create-many
  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/update-many
  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/upsert-many
  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/delete-many

Apex REST controllato:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/apex/invoke" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "method": "POST",
    "path": "/services/apexrest/example",
    "payload": {"id":"001xx000003DGbYAAW"}
  }'

La vecchia surface /internal/connectors/salesforce/* e stata rimossa: i consumer devono passare dagli endpoint source-based.

5. Transazione atomica multi-oggetto ​

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/transactions" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "allOrNone": true,
    "operations": [
      {
        "id": "contact",
        "action": "create",
        "objectName": "Contact",
        "values": {"LastName":"Rossi"},
        "returning": ["Id"]
      },
      {
        "id": "curriculum",
        "action": "update",
        "objectName": "Curriculum__c",
        "recordId": "a10xx0000000001AAA",
        "values": {"Contact__c":{"$ref":"contact.recordId"}}
      }
    ]
  }'

Una request diventa una sola Salesforce Composite Graph atomica. Operation id, selettori e reference vengono validati globalmente; le dipendenze possono comparire in qualunque ordine e un topological sort stabile le riordina, mentre reference mancanti e cicli sono rifiutati. Il limite provider include le read aggiunte per normalizzare gli upsert e non viene mai aggirato con split automatici. Una precondizione UNCHANGED_SINCE fallita e normalizzata come PRECONDITION_FAILED (409).

Ogni configurazione Salesforce richiede apiVersion esplicita nel formato NN.0; una configurazione persistita legacy senza versione risulta indisponibile finche non viene salvata nuovamente. Non viene usata la versione predefinita di jsforce. Il parser interno accetta al massimo 20 MiB, limite distinto dai vincoli Composite Graph validati dall'adapter prima della chiamata provider.

Un consumer reale lato backend e sfdc-external/backend/src/platform/platform-clients.ts.

Endpoint reference ​

Header comuni per tutte le route interne:

  • x-platform-internal-token: $INTERNAL_TOKEN
  • Content-Type: application/json sulle POST e PUT

GET /health aggrega state store, registry provider e sole sorgenti configurate. Se non esiste nessuna sorgente configurata risponde degraded; una sorgente placeholder non configurata, per esempio salesforce-default, non degrada un runtime che usa un'altra sorgente configurata.

Discovery e configurazione ​

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
GET /healthVerifica liveness del servizio e delle sorgenti configurate.Nessun body.curl -sS "$CONNECTORS_URL/health"{"status":"ok","checks":{"sources":{"status":"up","configuredSources":1}}}degraded se nessuna sorgente e configurata o solo alcune sorgenti configurate non sono disponibili; down per state store, registry o tutte le sorgenti configurate non disponibili. Consumer: status dashboard.
GET /internal/connectors/healthHealth interna dei provider datasource.Header interno.curl -sS "$CONNECTORS_URL/internal/connectors/health" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"status":"ok","sources":[{"sourceId":"salesforce-default","configured":true}]}401/403 token. Consumer: operations e MCP.
GET /internal/connectors/sourcesLista sorgenti note e capability.Header interno.curl -sS "$CONNECTORS_URL/internal/connectors/sources" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"sourceId":"salesforce-default","providerKey":"salesforce","configured":true,"capabilities":{"data":true,"describe":true,"paging":true,"aggregates":true,"rawQuery":true,"sosl":true,"bulkMutation":true,"apexRest":true}}]}401/403 token. Consumer: BFF, MCP e operations.
POST /internal/connectors/sources/:sourceId/testProva una configurazione provider senza salvarla.providerKey?, config.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/test" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"providerKey":"salesforce","config":{"mode":"username-password","loginUrl":"https://login.salesforce.com","username":"integration@example.com","password":"<PASSWORD>","securityToken":"<TOKEN>"}}'{"sourceId":"salesforce-default","providerKey":"salesforce","success":true,"capabilities":{"data":true,"describe":true,"paging":true,"aggregates":true,"rawQuery":true}}400 config non valida, 502 login provider fallito. Consumer: operations.
POST /internal/connectors/sources/:sourceId/configureSalva una configurazione provider dopo probe riuscito.providerKey?, config.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/configure" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"providerKey":"salesforce","config":{"mode":"username-password","loginUrl":"https://login.salesforce.com","username":"integration@example.com","password":"<PASSWORD>","securityToken":"<TOKEN>"}}'{"sourceId":"salesforce-default","providerKey":"salesforce","success":true,"capabilities":{"data":true,"describe":true,"paging":true}}400 config non valida, 502 provider, 5xx state store. Consumer: operations.
GET /internal/connectors/sources/:sourceId/statusStato persistito di una sorgente.Path sourceId.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/status" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"sourceId":"salesforce-default","providerKey":"salesforce","configured":true,"lastConfiguredAt":"2026-05-03T10:00:00.000Z","lastSuccessAt":"2026-05-03T10:10:00.000Z"}401/403 token. Consumer: BFF e operations.
GET /internal/connectors/sources/:sourceId/capabilitiesCapability provider per una sorgente.Path sourceId.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/capabilities" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"sourceId":"salesforce-default","providerKey":"salesforce","capabilities":{"data":true,"describe":true,"paging":true,"aggregates":true,"rawQuery":true,"sosl":true,"bulkMutation":true,"apexRest":true}}404 provider non supportato. Consumer: MCP e BFF.

Describe e query ​

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
GET /internal/connectors/sources/:sourceId/describe/objectsLista oggetti disponibili.Path sourceId.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"name":"Contact","label":"Contact","custom":false,"searchable":true}]}409 sorgente non configurata, 502 provider. Consumer: BFF metadata e MCP.
GET /internal/connectors/sources/:sourceId/describe/objects/:objectNameDettaglio oggetto.Path objectName.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects/Contact" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"name":"Contact","label":"Contact","custom":false,"searchable":true}400 nome oggetto non valido, 404 non trovato. Consumer: BFF metadata.
GET /internal/connectors/sources/:sourceId/describe/objects/:objectName/fieldsCampi oggetto, inclusi filtri e picklist.Path objectName.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects/Contact/fields" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"name":"Email","label":"Email","type":"email","required":false,"filterable":true}]}400/404/502. Consumer: UI builder e query builder BFF.
POST /internal/connectors/sources/:sourceId/query/runEsegue query strutturata o aggregata senza cursor paging.query o piano al top-level.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/run" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"query":{"kind":"structured","query":{"objectName":"Contact","select":["Id","Name"],"limit":10}}}'{"done":true,"totalSize":1,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}],"renderedQueryText":"SELECT Id, Name FROM Contact LIMIT 10"}400 piano non valido, 409 non configurata, 502 provider. Consumer: BFF.
POST /internal/connectors/sources/:sourceId/query/pageEsegue query strutturata/aggregata e restituisce una pagina.query, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/page" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"pageSize":100,"query":{"kind":"structured","query":{"objectName":"Contact","select":["Id","Name","Email"],"limit":100}}}'{"done":false,"totalSize":250,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi","Email":"mario@example.test"}],"nextCursor":"cursor_123"}400 piano o page size, 502 provider. Consumer: BFF liste.
POST /internal/connectors/sources/:sourceId/query/moreContinua una query paginata.cursor, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/more" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"cursor":"cursor_123","pageSize":100}'{"done":true,"totalSize":250,"records":[]}400 cursor mancante/scaduto, 502 provider. Consumer: BFF liste.
POST /internal/connectors/sources/:sourceId/query/rawEsegue SOQL read-only con opzioni runtime.soql, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/raw" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"soql":"SELECT Id, Name FROM Contact LIMIT 10","options":{"maxFetch":10}}'{"done":true,"totalSize":1,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}]}400 SOQL mutante/non read-only, 502 provider. Consumer: BFF con template SOQL propri e MCP high-risk.
POST /internal/connectors/sources/:sourceId/query/raw/pageSOQL read-only paginato.soql, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/raw/page" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"soql":"SELECT Id, Name FROM Contact","pageSize":50}'{"done":false,"totalSize":200,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}],"nextCursor":"cursor_456"}400 SOQL non valido, 502 provider. Consumer: BFF e MCP.
POST /internal/connectors/sources/:sourceId/query/raw/moreContinua SOQL raw paginato.cursor, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/raw/more" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"cursor":"cursor_456","pageSize":50}'{"done":true,"totalSize":200,"records":[]}400 cursor mancante/scaduto. Consumer: BFF e MCP.
POST /internal/connectors/sources/:sourceId/query/soslEsegue SOSL Salesforce read-only.sosl.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/sosl" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"sosl":"FIND {mario} IN ALL FIELDS RETURNING Contact(Id, Name, Email LIMIT 10)"}'{"done":true,"totalSize":1,"records":[{"attributes":{"type":"Contact"},"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}]}400 SOSL non valido, 502 provider. Consumer: search BFF e MCP high-risk.

Record e Apex ​

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
POST /internal/connectors/sources/:sourceId/records/:objectNameCrea un record singolo.record o values.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"record":{"LastName":"Rossi","Email":"mario.rossi@example.test"}}'{"id":"003xx000004TmiYAAS","success":true,"errors":[]}400 valori non validi, 502 provider. Consumer: BFF prodotto.
POST /internal/connectors/sources/:sourceId/records/:objectName/create-manyCrea record in batch.records, options.allOrNone?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/create-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"records":[{"LastName":"Rossi"},{"LastName":"Bianchi"}],"options":{"allOrNone":false}}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"errors":[]}]}400 array mancante, 502 provider. Consumer: import BFF.
POST /internal/connectors/sources/:sourceId/records/:objectName/update-manyAggiorna record in batch.records con Id, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/update-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"records":[{"Id":"003xx000004TmiYAAS","Email":"new@example.test"}]}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"errors":[]}]}400 Id mancante, 502 provider. Consumer: sync BFF.
POST /internal/connectors/sources/:sourceId/records/:objectName/upsert-manyUpsert batch su external id.records, externalIdField, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/upsert-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"externalIdField":"External_Id__c","records":[{"External_Id__c":"ext-1","LastName":"Rossi"}]}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"created":false,"errors":[]}]}400 external id mancante. Consumer: sync/import BFF.
POST /internal/connectors/sources/:sourceId/records/:objectName/delete-manyCancella record in batch.recordIds, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/delete-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"recordIds":["003xx000004TmiYAAS"],"options":{"allOrNone":false}}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"errors":[]}]}400 recordIds vuoto, 502 provider. Consumer: admin BFF controllati.
PUT /internal/connectors/sources/:sourceId/records/:objectName/:recordIdAggiorna un record singolo.Path recordId, body record o values.curl -sS -X PUT "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/003xx000004TmiYAAS" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"record":{"Email":"updated@example.test"}}'{"id":"003xx000004TmiYAAS","success":true,"errors":[]}400 recordId/values non validi, 404/502 provider. Consumer: BFF prodotto.
DELETE /internal/connectors/sources/:sourceId/records/:objectName/:recordIdCancella un record singolo.Path recordId.curl -sS -X DELETE "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/003xx000004TmiYAAS" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"deleted":true}400 recordId mancante, 404/502 provider. Consumer: BFF admin controllati.
POST /internal/connectors/sources/:sourceId/apex/invokeInvoca Apex REST controllato.method POST o PATCH, path, payload?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/apex/invoke" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"method":"POST","path":"/services/apexrest/example","payload":{"id":"001xx000003DGbYAAW"}}'{"status":200,"payload":{"ok":true}}400 metodo/path non ammesso, 502 provider. Consumer: BFF con integrazioni Apex approvate.

I consumer devono preferire query/page e query/more quando possono esprimere il filtro con il contratto strutturato; le query raw e SOSL restano superfici ad alto rischio e vanno esposte dai BFF solo dietro policy applicative esplicite.

Workspace reference: /Users/jeanpaul/projects/cs-repository