These OAuth2.1 authorization-server endpoints (metadata, jwks, register, authorize, token, revoke) and the MCP gateway endpoints (protected-resource metadata, JSON-RPC mcp) authenticate via OAuth (PKCE / client credentials / bearer-token introspection), not the internal @PreAuthorize RBAC. Marking them @PublicEndpoint — consistent with TokenController pre-login endpoints — makes their public security posture explicit and satisfies the controller permission audit (337 endpoints: 326 guarded + 11 public).
The shared JDBC driver base interpolated the point value into the write SQL via
writeQuery.replace("${value}", ...), allowing a crafted write command value to
alter the statement. Bind the value with PreparedStatement#setString against a
single ? placeholder instead. Update the mysql/oracle/postgresql/sqlserver
writeQuery attribute docs to the ? convention and add the module's first test.
Document 31 modules that had no README: dc3-center-agentic, dc3-e2e,
common modules (agentic, sql, test, resource-registrar) and the facade
subsystem (api/grpc/local-auth/local-data/local-manager), plus 21
drivers. Content is grounded in each module's pom/yaml/source; WIP
skeleton drivers (can, dlms, ethernet-ip, iec104, lwm2m, zigbee) are
flagged as such.
thread: document the three JDK executor beans and the real dc3.thread
keys (the @Async claim was wrong). mqtt: correct the dc3.driver.mqtt
prefix/keys and the Spring Integration + Paho component roles.
@EnableAsync was on the business component MetadataEventPublisher; move
it to the module's @AutoConfiguration entry (ManagerInitRunner) so the
async switch lives with config, not a bean. Scope is unchanged.
Replace the unsafe (T) cast of PemReader.readPemObject() — which threw
ClassCastException at runtime — with explicit conversion: X.509 certs via
CertificateFactory and private keys (PKCS#1/PKCS#8, encrypted or plain) via
BouncyCastle's PEMParser. Add the bcpkix dependency and unit tests covering
the encrypted-key path used by MQTT TLS.
Replace bare `//` placeholders with concise intent comments where a
non-obvious reason exists (metadata-event publishing, referential-integrity
delete guards, gRPC registration steps), and remove the rest as leftover
field-group dividers or redundant restatements. Add class-level Javadoc to
12 auth Mapper/Manager/ManagerImpl interfaces to match the repository
convention. Comment-only change across manager, auth, and data modules.
Part of the x-dc3-ai annotation-standard rollout. Complete method/param javadoc (TokenService, RepositoryService, RuleEngine, ...), flag work-in-progress utilities and drivers (X509Util, MQTT/CAN/DLMS/... ) with explicit WARNING notes, and remove redundant inline comments / blank lines from query/VO/ext/constant classes plus mapper indentation. Comment/doc only, no behavior change.
createConnection and registerClient now take an enum-typed BO instead of a VO, so the service layer no longer parses wire strings. McpConnectionAddBO / OAuthClientRegistrationBO (enum principalType/grantType/clientType) are built from the request VO via McpConnectionBuilder.buildBOByAddVO / OAuthClientBuilder.buildBOByRequestVO; the controllers do the VO->BO conversion. The *Record projections stay String/Byte (untouched).
createConnection keeps creator stamping and every client_credentials check (service-account principal, client-SA binding, tenant match). The original 'unsupported client_type' guard is preserved: OAuthClientBuilder.isUnknownClientType lets both register controllers reject a non-blank but unrecognized client_type instead of silently defaulting to PUBLIC.
Replace bare String fields with domain enums on the read-response VOs and in service-internal comparisons, removing scattered McpConstant string literals. Five value-typed enums (OAuthGrantType/OAuthClientType/McpRiskLevel/McpAuditStatus/McpConfirmationStatus) land in common-constant with @EnumValue + @JsonValue + ofValue, registered in IndexedEnumContractTest.
VO fields: McpConnectionVO{principalType,grantType}, McpToolVO.riskLevel, McpAuditVO{principalType,riskLevel,status}, OAuthClientVO.clientType. Builders convert via @AfterMapping (Enum.ofValue); the *Record projections keep String/Byte untouched (OAuthMcpMapper custom-SQL resultType boundary). Service compares/assigns via enum.getValue(); Confirmation.DECISION_* kept (gateway-shared). common-constant gains jackson-annotations because grantType's wire value is lowercase and must serialize via @JsonValue, not the uppercase enum name.
client_credentials connections store the service account as principal_id, but the management ops (list/replace/revoke/listToolIds) authorized on principal_id == caller. The caller is the USER who built the connection while principal_id is the SERVICE_ACCOUNT, so they never matched — the connection's own creator could not list it, set its tool whitelist, or revoke it ("MCP connection does not exist").
Stamp creator_id (the acting user) in createConnection and key the management path on creator_id; the MCP runtime path (service-account bearer token) stays on principal_id. authorization_code connections are unaffected (creator == principal == USER).
Batch-resolve principals by id (selectBatchIds) returning PrincipalVO with principalName/displayName, so frontend lists can render principalId references as human-readable names instead of raw ids.
Replace the (#{x} IS NULL OR ...) optional-filter pattern with MyBatis <if> dynamic SQL in IdentityAuditLogMapper and OAuthMcpMapper. PostgreSQL cannot infer the placeholder type in '? IS NULL', which returned 500 on the identity-audit and mcp-audit list endpoints.
WebFlux does not bind application/x-www-form-urlencoded to
@RequestBody Mono<MultiValueMap> — every token and revocation request
returned 415 Unsupported Media Type, so no MCP client could obtain a bearer
token. Read the form with exchange.getFormData() instead (the canonical WebFlux
form API). Verified end-to-end: client_credentials token issuance now succeeds.
Regenerate the static openapi-{auth,manager,data,agentic}.json snapshots from
the running dev stack so the auth MCP catalog refresh reads the full
x-dc3-ai quality (riskLevel/destructive/idempotent/openWorld/hidden) for every
endpoint: manager 148, auth 91, data 75, agentic 20 (334/334 annotated).
OAuthClientRegistration request/response are REST controller bodies
(@RequestBody and Mono<R<...>> on POST /client/register) that never cross
a gRPC or messaging boundary, so the DTO suffix was misplaced. Move them
into auth.entity.vo (project VO convention is per-module) and drop the
suffix. Kept as plain Serializable rather than extending BaseVO to avoid
injecting audit fields into the OAuth-spec wire payload.
Replace the McpConnectionToolsReplace/McpToolCatalogList request DTOs (in
dc3-common-model) with McpConnectionToolsReplaceVO/McpToolCatalogQueryVO and a
full set of Mcp/OAuth VOs in dc3-common-auth, wired through MapStruct builders
for VO<->BO mapping, per the project DO/BO/VO convention. Update the
PointValueMapper WindowAggregateRequest reference to WindowAggregateQuery.
Remove the misleading hand-built model segment that adds extensions then asserts
the serialized JSON. That pattern proves nothing about swagger-core's annotation
pipeline (you're asserting a map you just inserted by hand).
Keep the focused test: read @Operation annotation via reflection, call
AnnotationsUtils.getExtensions(...) which IS swagger-core's real annotation→map
conversion, and assert both the x-dc3-ai key and its riskLevel=HIGH content.
End-to-end emission into /v3/api-docs is verified separately (Task 8).
Use AnnotationsUtils.getExtensions() (swagger-core-jakarta 2.2.47) to convert
@Extension(name="x-dc3-ai") annotation to a map, then serialize to JSON via
Json.pretty(). Proves the full annotation->map->JSON pipeline that McpOpenApiAggregator
will rely on. jaxrs2 Reader is not on the module classpath; this fallback gives the
same assurance without adding new dependencies.
Whitespace-only cleanup (javadoc @param alignment, license header escaping,
import ordering) across the dashboard/window-aggregate data services, repository
query and mcp gateway controller. No behaviour change.
Whitespace-only cleanup (import ordering, a missing space after summary=, license
header) in OAuthController, LocalCredentialVO and ServiceAccountServiceImpl.
No behaviour change.
Whitespace-only cleanup (import ordering, parameter indentation, license header)
in the agentic chat controllers, builders and services. No behaviour change.
Replace the mixed-purpose ResponseEnum with a ResponseCode interface implemented
by SuccessCode (OK/ADD/DELETE/UPDATE) and ErrorCode (NOT_FOUND/UNAUTHORIZED/
TOKEN_INVALID/IP_INVALID/FORBIDDEN/PASSWORD_*/VALIDATION/OUT_OF_RANGE/FAILURE),
each carrying its HTTP status so the envelope body code and transport status no
longer diverge.
- Introduce abstract BusinessException carrying an ErrorCode; all 23 business
exceptions extend it and bind a fixed code. ExceptionConfig reads the carried
code to set both body code and HTTP status (404->R404, 401->R401, 422->R422).
- Add GrpcRFactory (ok/fail/notFound) and remove the hand-written GrpcR builder
boilerplate across 19 gRPC servers.
- Drop the dead @EnumValue index and ofIndex/ofCode/ofName lookups; OUT_OF_RANGE
is now carried by OutRangeException.
- Update R factory methods to accept ResponseCode; migrate 90+ controllers,
11 gRPC facades, OpenAPI specs and README to the split enums.
Also fix several pre-existing red tests surfaced by the full suite:
- OAuthMcpRuntimeServiceImplTest: setUp set @Value fields refactored into
OAuthProperties; inject the properties object instead.
- DriverReadServiceImplTest: reset leaked ConnectionBackoff static state via a
new clear() in @BeforeEach.
- modbus-rtu/tcp: make the static modbusFactory non-final so tests can swap it.
- ble: override no-arg health() to report OFFLINE before initialization.
- PlcS7/facade-local tests: expect the thrown exception / use the single-arg
overload matching production behaviour.
- Remove a duplicate dc3-common-constant dependency in dc3-common-data/pom.xml.
Add @Operation to the MCP gateway endpoints (protected-resource metadata, JSON-RPC handler) and behavior-accurate method JavaDoc. Docs only, no logic changes.