grant_type in GrpcMcpIntrospectDTO was a free-form string. Introduce
GrpcOAuthGrantType (GRPC_OAUTH_GRANT_TYPE_UNSPECIFIED / AUTHORIZATION_CODE
/ CLIENT_CREDENTIALS / REFRESH_TOKEN). Enum names are upper-case (Java
enum constants must be); the boundary converts to/from the lowercase
OAuth spec form (authorization_code) via toUpperCase / toLowerCase.
BO, DB and OAuth form parameter stay lowercase string.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
audit status in GrpcMcpAuditCommand was a free-form string. Introduce
GrpcMcpAuditStatus (MCP_AUDIT_STATUS_UNSPECIFIED / SUCCESS / DENIED /
POLICY_DENIED / ERROR / UNKNOWN) — names match McpAuditStatusEnum values.
Server maps proto->DTO via name(); facade maps BO string->proto via
toGrpcAuditStatus. BO and DB stay string.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
principal_type was a free-form string in GrpcMcpIntrospectDTO and
GrpcMcpAuditCommand. Introduce GrpcMcpPrincipalType
(MCP_PRINCIPAL_TYPE_UNSPECIFIED / USER / SERVICE_ACCOUNT / SYSTEM) —
names match PrincipalTypeEnum values, so the mapping is a direct
valueOf / name(). Server + facade convert at the proto boundary; BO,
gateway and HTTP header layer stay string.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
risk_level was a free-form string across GrpcMcpToolMetadataDTO,
GrpcMcpToolResolveDTO, GrpcMcpToolAuthorizeDTO and GrpcMcpAuditCommand.
Introduce GrpcMcpRiskLevel (MCP_RISK_LEVEL_UNSPECIFIED / LOW / MEDIUM /
HIGH) — names match McpRiskLevelEnum values, so the mapping is a direct
valueOf / name(). Server + facade convert at the proto boundary; the BO
and DB layer stay string.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(api): correct inverted enable_flag comment and document time unit
- tenant.proto enable_flag comment said "1 enabled, 0 disabled" but the
actual EnableFlagEnum is ENABLE=0, DISABLE=1 — callers reading the
comment would invert the flag. Correct it.
- base.proto create_time/operate_time are epoch milliseconds
(LocalDateTimeUtil/Instant.toEpochMilli); document the unit so callers
don't guess seconds vs millis.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(api): carry timestamp in point-value history; explicit bool presence
#5 Point-value history: the whole chain (proto -> gRPC server ->
PointValueService -> repository -> facades -> controller -> agentic tool)
returned List<String> and dropped create_time even though the DB row
already carried it. Switch to PointValueBO / FacadePointValueBO end to
end so callers receive each value with its timestamp.
#6 Field presence: GrpcR.ok and McpIntrospectDTO.active are now optional
bool, so "unset" is distinguishable from false on the wire.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): unify JWT signing key, drop salt/password from token contract
The JWT signing key was per-principal — securityKey + ":" + salt, where
salt was the credential salt also carried through the gateway and the
token RPC. That makes the JWT key weak and widens salt exposure to every
gateway hop. Sign with the single configured securityKey (HS256) instead
and remove salt from the JWT path entirely:
- KeyUtil.generateToken/parserToken drop the salt parameter; the HMAC
key is now just securityKey (must be >= 32 bytes for HS256).
- TokenService.generateToken/checkValid drop the salt parameter.
- token.proto GrpcLoginQuery drops salt and password — CheckValid only
ever needed tenant, name and token.
- gateway FilterService, TokenGrpcFacade, TokenController adapted; the
request header no longer feeds salt into validation.
The login-handshake salt (POST /token/salt, used to salt the password
client-side before verifyPassword) is unchanged — only the JWT signing
salt is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(api): type MCP authorize decision as proto enum
The authorize-tool-call decision was a free-form string, so callers
could only compare it against literals with no compile-time check.
Introduce GrpcMcpDecision (MCP_DECISION_UNSPECIFIED / AUTHORIZED /
CONFIRM_REQUIRED / REJECTED) and type GrpcMcpToolAuthorizeDTO.decision
with it. Enum names match the existing auth-side literals, so the
mapping is a direct valueOf / name().
- McpRuntimeServer builds the enum via toGrpcDecision (unknown -> UNSPECIFIED).
- McpRuntimeGrpcFacade maps enum -> BO string via name().
- gateway unchanged: it receives the BO (string decision) through the facade.
The other string MCP fields (risk_level, status, grant_type,
principal_type) stay strings for now — their value sets are wider and
partly lowercase (OAuth grant_type), so they are a follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(facade): align TokenLocalFacade with 3-arg checkValid (CI compile fix)
TokenLocalFacade (local impl) was missed when TokenFacade.checkValid
dropped salt; CI compiles facade-local-auth independently and caught
it. Align it + its test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Move proto files from api/center/ to api/common/ for auth, data, manager modules
- Update java_package and java_outer_classname in moved proto files
- Add GrpcDriverQuery message to shared query.proto in dc3-common-api
- Move GrpcDriverQuery proto from common.driver to common package
- Update Java imports referencing relocated proto messages
- Remove redundant pom.xml metadata (developers, licenses, scm, version)
from api submodules — inherited from parent
Co-Authored-By: Claude <noreply@anthropic.com>
D-1's fail-closed TenantLineHandler rejects unscoped queries, but gRPC
server threads bypass BaseController.async so no tenant was ever bound —
every query against a tenant-scoped table silently failed.
- proto: add tenant_id to manager/driver single & batch query messages
- facade: drop single-param abstract + default matchesTenant filter,
promote the tenantId-bearing overload (all callers already pass it)
- GrpcFacade/LocalFacade: carry tenant into request / onto ThreadLocal
- 9 manager+driver servers (27 RPCs): setTenantId + finally clear
- data servers: setTenantId on tenant-bearing RPCs; runIgnore for
getByRecordId (record_id globally unique, no tenant in payload)
- TokenServer.checkValid: runIgnore (dc3_tenant_membership, pre-login)
- driver clients (Device/Driver/Point): set tenant on getById queries
- alarm: drop tenant reverse-resolve (D-1 leftover the fail-closed
interceptor forbids); tenant now required from upstream, else drop
- add TenantLineInterceptorIntegrationTest (H2) covering scoped filter,
fail-closed, and runIgnore through the real MyBatis-Plus pipeline
Align the four api READMEs with the actual protos: Select*->Get*/List*
per the CRUD verb convention, fix api/common->api/center import paths
for auth/data, drop the fabricated manager SelectByServiceName RPC, and
document the previously omitted auth/data/manager services.
Tool authorization (HIGH-risk confirmation):
- add AuthorizeToolCall RPC; the gateway calls it before tool execution
- sign/consume confirmation tickets with idempotency-key dedup
- propagate x-dc3-ai metadata (riskLevel/destructive/openWorld/idempotent) from the scan through the registry to the tool catalog
OAuth hardening:
- rotate refresh tokens and revoke the authorization on replay via previous_refresh_token_hash
Password lifecycle:
- require password change on login when flagged or expired (R20303/R20304)
- expose a public POST /token/change_password endpoint
OpenAPI aggregation:
- switch McpOpenApiAggregator to classpath static snapshots, dropping the runtime fetch and McpAggregatorProperties
Update auth, gateway, architecture, quickstart, changelog, and superpowers design documents for the Principal identity model and OAuth-backed MCP server.
Replace legacy user-login and role-user binding runtime with Principal, LocalCredential, TenantMembership, ServiceAccount, and RolePrincipalBind.
Switch gateway/backend identity propagation to X-Auth-Principal and align agentic/controller security context types with PrincipalHeader.
Align CommandHistory/PointCommandHistory with the EventHistory pattern:
DB stores the enum index (SMALLINT via @EnumValue), gRPC carries int32,
JSON keeps enum name. Removes the lone string-code outlier in the schema.
- DO status/type/source: String code -> enum types
- VO type/source: String -> enum (status already enum); JSON stays name
- Builder: drop ofCode @AfterMapping (DO and VO now share enum type)
- RabbitMQ DTOs (CommandCall/PointCommandResult): status/source -> enum
- driver sendResult signatures: String status -> PointCommandStatusEnum
- gRPC command_history.proto status/source: string -> int32; server uses
getIndex()/ofIndex() to match EventHistoryServer
- tests updated to assert enum values
- Replace obsolete nexus-staging-maven-plugin with central-publishing-
maven-plugin in dc3-api and dc3-common
- Remove 20 redundant version declarations from dc3-coverage dependencies
- Fix snmp4j version from non-existent 3.8.3 to available 3.7.4
Introduce CommandAttribute/EventAttribute with config variants:
- REST controllers, MyBatis mappers, services, and DAL managers
- gRPC client/server builders and proto message definitions
- DTOs, BOs, DOs, VOs for type-safe data transfer layer
fix: add explicit ::text casts in json_build_object parameters
PostgreSQL cannot infer parameter types inside json_build_object()
calls. Add ::text casts to #{stateExtType} and #{stateDescription}
to resolve "could not determine data type of parameter $10" errors.
refactor: restructure driver gRPC client and metadata layer
- Rename GrpcStubConfig to DriverClientStubConfig with dedicated DTOs
- Extract attribute builders from inline gRPC conversion logic
- Consolidate driver metadata, register service, and command handling
- Update device state heartbeat and command result event handling
docs: add multilingual architecture diagram and sync READMEs
- Translate architecture SVG into zh, ja, vi variants
- Update README architecture image references per language
- Add missing service-level shortcut sections to ja/vi READMEs
Extends the existing point/device metadata refresh flow to drivers:
- MetadataEvent gains an optional targetServices set so manager-side
publishers can scope a metadata event to specific driver services
instead of broadcasting to every listener.
- New DRIVER value on MetadataTypeEnum, plus a new GetById RPC on
driver_driver.proto so drivers can re-fetch their registered metadata
without going through the full registration handshake again.
- DriverClient.refreshMetadata(driverId) calls the new RPC and reapplies
the returned device ids and attribute maps via a shared applyMetadata
helper.
- MetadataReceiver now handles MetadataTypeEnum.DRIVER:
DELETE -> clear DriverMetadata, DeviceMetadata, and PointMetadata
caches and flip status to OFFLINE
ADD/UPDATE -> driverClient.refreshMetadata(id)
- Manager-side MetadataEventListener forwards events to RabbitMQ keyed by
the target services in the event, falling back to broadcast when none
are specified.
- DeviceServiceImpl, DriverAttributeServiceImpl, PointAttributeServiceImpl,
and PointServiceImpl populate targetServices using the relevant driver
service names so changes only wake the drivers that own the affected
entity.
- DriverDriverServer.getById exposes the new RPC; new DriverDriverServerTest
covers it.
- MetadataReceiverTest gains driverUpdate / driverDelete cases;
MetadataEventListenerTest covers the per-service routing.
Refresh module READMEs, AGENTS.md, CONTRIBUTING.md, and dc3/doc/
guides to reflect the unified Dockerfile, consolidated compose stacks,
and updated build instructions.
Add three new RPC methods to PointValueApi: HistoryValue for querying
historical point values, ReadCommand and WriteCommand for device control.
Define corresponding request/response message types.
Thread a new api_group field end-to-end through the scanner, gRPC/local
facade, proto, auth entity layer, and registry sync service. Populated
from handler.getBeanType().getSimpleName() (e.g. ApiController), this is
the foundation for the forthcoming resource-tree hierarchy grouping
sibling endpoints under their controller.
Adds a startup-time registrar that scans the local WebFlux handler mappings
of each center service and reconciles the discovered endpoints against
dc3_api and dc3_resource (insert-if-missing, update-if-drifted, soft-delete-
if-orphaned), so the permission model is no longer hand-maintained.
Wiring:
- dc3-common-resource-registrar (new module): scanner, diff runner, Spring
Boot AutoConfiguration. Activates on ApplicationReadyEvent.
- dc3-common-facade-api: ResourceRegistryFacade + BOs define a transport-
neutral sync contract.
- dc3-common-facade-local / dc3-common-facade-grpc: two implementations
selected via dc3.facade.mode. center-auth/single use local; manager/data
call auth via gRPC.
- dc3-common-auth: biz-side ResourceRegistrySyncService performs the three-
way diff inside a transaction guarded by pg_advisory_xact_lock(hashtext
(serviceName)); exposes ResourceRegistryApi gRPC server.
- dc3-api-auth: new resource_registry.proto contract.
- ApiDO/BO/VO/Query gain a serviceName field so each service's endpoints
can be located with a single indexed lookup.
Deployment-visible changes:
- api_code format is the human-readable serviceName:METHOD:/path
- dc3_resource.resource_code format is api:serviceName:METHOD:/path
(prefixed namespace reserved for future resource types)
- center-auth application.yml sets dc3.facade.mode=local
- 4 center pom.xml files pull in the registrar (auth also gets facade-local)
Proto3 type resolution walks up the package hierarchy. After changing
package api.common.auth/data/manager to api.center.*, unqualified
references like GrpcR/GrpcBase (defined in api.common) can no longer
be resolved, causing PROTOC compile errors.
Revert package declarations to api.common.auth/data/manager while
keeping java_package as io.github.pnoker.api.center.* — java_package
is independent from proto package and controls Java output only.
1. manager_point.proto: add missing SelectById RPC and GrpcRPointDTO
response message to match driver_point.proto symmetry
2. Rename java_outer_classname to include module prefix to avoid
confusion between driver and manager modules:
- driver: DeviceProto→DriverDeviceProto, DriverProto→DriverRegistrationProto,
EntityProto→DriverEntityProto, PointProto→DriverPointProto,
QueryProto→DriverQueryProto, QueryPageProto→DriverQueryPageProto
- manager: DeviceProto→ManagerDeviceProto, DriverProto→ManagerDriverProto,
PointProto→ManagerPointProto, QueryProto→ManagerQueryProto,
QueryPageProto→ManagerQueryPageProto
3. Reorganize proto files into subdirectories matching proto package:
- auth: src/main/protobuf/api/center/auth/
- data: src/main/protobuf/api/center/data/
- driver: src/main/protobuf/api/common/driver/
- manager: src/main/protobuf/api/center/manager/
Update all intra-module imports to use full absolute paths
- dc3-api/pom.xml: move <profiles> to after <build> per Maven POM
standard element ordering; move <dependencyManagement> before
<dependencies>
- driver_device.proto: rename driverConfigs/pointConfigs to
driver_configs/point_configs (proto3 snake_case convention)
- auth/data/manager protos: align package declaration from
api.common.xxx to api.center.xxx to match java_package option
- driver_entity.proto, driver_query.proto, driver_query_page.proto,
manager_query_page.proto: remove trailing blank lines