CodamAIDocs
Topicdone

Why both modes must behave the same in the subject area

Same code, same rules, same refusals. What “functionally identical” means in practice, where the difference does show, and where it does not.

Variants
one question, two pathssame: rules, answers, refusalsdifferent: latency, outage, start-up checks

What this is about

CIAS either runs in the same process as CDMS or as a separate service. That decision is a decision about operations: about processes, the network and outages. It must not be a decision about who is allowed to do what.

The reason is simple: otherwise there would be two systems under one name. A customer who is served in one and refused in the other would not be an operational difference but a security incident — and you would notice only when somebody moved.

How this is achieved technically

Between CDMS and CIAS there is no direct call but a port. A port is an interface: a question as a method signature, with no statement about who answers it or how. The answer comes from an adapter, and there are exactly two of them — one per topology.

flowchart TB
    G["Caller in cias-authentication<br/>TenantGate / AttributeLookup<br/><i>rules, cache, refusal</i>"]
    G --> P{{"Port<br/>TenantLookupPort"}}
    P -->|embedded| LA["LocalTenantLookupAdapter<br/>(cias-tenancy)"]
    P -->|standalone| RA["RemoteTenantLookupAdapter<br/>(cias-tenancy-client)"]
    RA -->|HTTP| CTL["TenantLookupController<br/>in the CIAS service"]
    LA --> UC["TenantManagementUseCase.standing"]
    CTL --> UC
    UC --> DB[("CIAS tenants")]

Read the picture from the bottom: both paths end at the same method. The local adapter calls it directly. The remote adapter sends HTTP to an endpoint in the CIAS service, and that endpoint calls exactly the same method. So the rule about whether a tenant may be served exists only once.

The same holds at the top: the caller is the same class in both topologies. The tenant gate’s cache, the 30-second window, refusing when in doubt — all of that belongs to the caller, not to the adapter. The adapters themselves must neither cache nor retry nor soften an answer.

The three questions across the boundary

Only three questions travel between CDMS and CIAS. Each has a port and two adapters:

QuestionWho asksEmbeddedStandalone
May tenant X be served right now?tenant gate, on every requestmethod call in cias-tenancyGET /cias/lookup/tenants/{key}
Which attribute values does this person hold in this tenant?filter chain, on every request with a tenantmethod call in cias-userGET /cias/lookup/users/{id}/attributes
Which roles and attributes does this module register?CIAS, at start and on demandcall of a bean in the same processGET /cias/fetch on the module

For the first two, CDMS asks CIAS. For the third it is the other way round: there CIAS asks CDMS. More on that under Modules register roles and attributes.

How the two are kept from drifting apart

Two adapters, written at different times, drift apart by themselves. Against that, every port has a shared contract suite: a set of tests that does not check one adapter but the port. Every adapter signs up to the same suite and has to pass it.

What these suites pin down is always the same kind of distinction — and it is the real reason they exist:

The distinction no adapter may blur
  1. 1
    CIAS
    served — the tenant exists and may work
    The request carries on.
  2. 2
    CIAS
    exists, but not served — suspended, closed, or outside its window
    Refused. The log says that somebody was deliberately suspended.
  3. 3
    CIAS
    nobody has heard of it — no tenant carries that key
    Refused as well, but for a different reason. For an operator this is a completely different next step.
  4. 4
    CIAS
    could not be asked — timeout, refused connection, unreadable answer
    Result: This is not a "no". An adapter that turned it into one would report every fault as a deliberate suspension. It throws instead, and the caller decides — out of its cache or with a refusal.

The same care applies to the third question: “registers nothing” and “could not be asked” must not be confused. The role reconciliation retires what a module no longer registers — an adapter that passed an outage through as an empty registration would strip a service of its roles just because it happened to be restarting.

Where the difference does show

“Functionally identical” does not mean “identical in every respect”. It means: what is decided is the same. What is not the same is what an outage means and how long something takes.

Same and not the same
Same
the subject area
  • which tenant is served and which is not
  • which roles and attribute values apply in a request
  • which requests are refused, and with which error key
  • what a role grant may do and what the ceiling forbids
  • which roles the reconciliation creates, renames or marks deprecated
Not the same
operations
  • latency: a method call against an HTTP call over the network
  • outage: only standalone can CIAS fail on its own
  • credentials: only standalone does CDMS need a service account of its own
  • start-up checks: the standalone service additionally checks for leftover development settings
  • databases: one shared against two separate ones

Three of these deserve a closer look.

The outage. Embedded there is no “half”: either the process runs or it does not. Standalone, CIAS can be down while CDMS keeps running. Then the tenant gate’s cache decides, and it decides by a deliberately asymmetric rule: an outage may not evict anyone who was already working, and may not admit anyone who was not. Whoever is in the cache carries on with the last known answer — a refusal included. Whoever is not in it is refused. Details under When CIAS is not reachable.

The delay of a suspension. The tenant gate remembers its answer for a short while, 30 seconds by default. So when you suspend a tenant, it takes effect after that window at the latest — in both topologies. Embedded this is easy to underestimate, because you assume that in the same process it must be immediate. But the window belongs to the caller, and the caller exists in both topologies.

The start-up checks. Both operating modes check at start whether development settings have been left in place, and then do not start unless codamai.safety.mode is warn. The one difference is the schema: standalone CIAS additionally requires ddl-auto: validate; embedded, the schema belongs to the application — see Reject when in doubt.

Where it does not show

For the client calling the API, the topology is invisible. There is no header, no field in the response and no error key you could read it from. A refused request looks the same in both topologies, and that is deliberate: refusals must not reveal anything about the internal structure. See Rejections that reveal nothing.

Roles and permissions do not shift invisibly either. What a role allows on the data is in the model and is enforced by CDMS — and CIAS appears there in neither topology.

Watch out

Next

Sources in the code and the knowledge base
  • CIAS/cias-kernel – TenantLookupPort, TenantStanding (empty ≠ not served), TenantBoundAttributePort
  • CIAS/cias-tenancy – LocalTenantLookupAdapter (delegates to TenantManagementUseCase.standing), TenantLookupController (calls the same method)
  • CIAS/cias-tenancy-client – RemoteTenantLookupAdapter, RemoteTenantBoundAttributeAdapter (every failure is thrown, 401/403 is not a "no")
  • CIAS/cias-user – LocalTenantBoundAttributeAdapter, AttributeLookupController (sits on top of the same adapter)
  • CIAS/cias-authentication – TenantGate (caller and cache, identical in both topologies), AttributeLookup, TokenParser, JwtSessionFilter
  • CIAS/cias-authorization – ModuleDeclarationPort, LocalModuleDeclarationAdapter, RemoteModuleDeclarationAdapter (GET /cias/fetch)
  • CIAS/cias-test-support – TenantLookupContract, TenantBoundAttributeContract; CIAS/cias-authorization – ModuleDeclarationContract
  • CIAS/cias-spring-boot-starter – CiasSafetyCheck (codamai.safety.mode)
  • CIAS/cias-runtime – CiasLocalProfileNotice
  • CIAS/cias-kernel/docs/adr – ADR-022; CIAS/cias-authentication/docs/adr – ADR-021, ADR-042; CIAS/cias-authorization/docs/adr – ADR-025
Search