Core Engine — Service Reference
Module: backends/core-engine · Port: 8080 (REST only — no gRPC server) · Package:
com.attestpro.coreengine
Purpose
The platform’s orchestrator and the only service allowed to know about multi-service workflows. Every frontend — Issuer Portal, Verifier Portal, Credential Wallet, Custodian Portal, Admin Console’s issuer/verifier-facing flows — talks to Core Engine’s REST API (via API Gateway), never directly to a downstream gRPC service. Core Engine is the anti-corruption layer: it translates REST requests into gRPC calls against Credential Issuance, Verification Engine, Issuer Registry, Program Catalog, Learner Records, and Custodian Registry, and owns cross-cutting concerns none of those services should each reimplement — audit logging, holder/verifier/custodian/issuer authentication, notifications, and consent.
The platform’s largest module by a wide margin: 55 REST controllers, 6 gRPC clients (all
mTLS — see Internal gRPC mTLS), and 57 tables in its own
core Postgres schema.
Why it exists (not a thin proxy)
A thinner design would let API Gateway route each REST call straight to the owning microservice. That fails for any request that isn’t naturally single-service — issuing a credential requires validating the issuer (Issuer Registry), checking accreditation scope and resolving catalog/learner links (Program Catalog, Learner Records), delegating the actual signing (Credential Issuance), and writing an audit trail entry, all as one logical operation. Core Engine is where that coordination logic lives, so the other services stay single-responsibility with no knowledge of each other.
Responsibility breakdown
| Domain | Controllers | Base path |
|---|---|---|
| Public / unauthenticated | PublicVerificationController, VerifyCredentialController, IssueCredentialController, NetworkStatsController, SystemHealthController, DemoModeController, DocsSupportController | /api/v1/verify, /credentials, /network-stats, /system-health, /demo-mode, /support |
| Holder (Credential Wallet) | HolderAuthController, HolderPasskeyController, HolderClaimController, HolderCredentialController, HolderRequestController, HolderShareController, HolderAccessLogController, HolderNotificationController, HolderConsentController, HolderEvidenceController, HolderProofLinkController, HolderWalletKeyController, HolderOnboardingInviteController, HolderRegistryController | /api/v1/holder* |
| Issuer portal | IssuerAuthController, IssuerPortalController, IssuerPortalUserController, IssuerTeamController, IssuerSsoController, IssuerApiKeyController, IssuerKycController, IssuerManagementController | /api/v1/issuer*, /portal/me* |
| Verifier | VerifierAuthController, VerifierManagementController | /api/v1/verifier* |
| Custodian (largest group) | CustodianAuthController, CustodianIntakeController, CustodianCredentialController, CustodianDirectAttestationController, CustodianHolderController, CustodianManagementController, CustodianComplianceController, CustodianConsentController, CustodianEvidenceController, CustodianErasureController, CustodianReperformanceController, CustodianSearchController, CustodianSsoController, CustodianTeamController, CustodianWorkforceController | /api/v1/custodian*, /custodians |
| Admin-facing (served by Core Engine) | IssuerOnboardingAdminController, CustodianOnboardingAdminController, FederatedIssuerAdminController, CatalogAdminController, AnomalyAdminController, ClaimIntegrityAdminController | /api/v1/admin/* |
| AI-assisted (advisory, never gating) | BulkIssuanceAssistController, CredentialTemplateAssistController, CredentialInsightController | — |
| Reporting/export | AvetmissExportController, AuditSearchController | /api/v1/reports, /audit-logs |
The admin-facing group above is distinct from backends/admin-console’s own REST API, which
serves platform-wide oversight screens not tied to a specific workflow queue.
Data owned (core schema)
| Group | Representative tables |
|---|---|
| Audit | audit_logs — the hash-chained tamper-evident trail |
| Holder | persons, person_identifiers, holder_accounts, holder_name_variants, holder_name_change_requests, holder_sessions, holder_magic_links, holder_passkeys, holder_recovery_codes, holder_wallet_keys, holder_requests, holder_notifications, holder_proof_links, share_links |
| Claim integrity | claim_challenges, claim_holds, holder_onboarding_invites |
| Issuer portal | issuer_portal_users, issuer_portal_sessions, issuer_preferences, issuer_magic_links, issuer_sessions, issuer_onboarding_leads |
| Verifier | verifier_accounts, verifier_magic_links, verifier_sessions |
| Custodian portal/auth | custodian_accounts, custodian_portal_users, custodian_portal_sessions, custodian_magic_links, custodian_sessions, custodian_onboarding_leads |
| Custodian intake | custodian_intake_batches, custodian_intake_items, custodian_intake_item_catalog_matches, custodian_item_events |
| Custodian compliance | custodian_credential_policies, custodian_waivers, custodian_expiry_reminders, custodian_reperformance_schedules, custodian_erasure_requests |
| Custodian consent/evidence | custodian_consent_requests, custodian_holder_consents, custodian_evidence_requests |
| Custodian workforce | custodian_people, custodian_groups, custodian_group_requirements, custodian_group_members |
| Custodian search | custodian_saved_searches, custodian_search_defaults, custodian_saved_search_usage, custodian_search_history |
| LLM | llm_analysis_attempts |
No other service has a grant on core; conversely, Core Engine has no grant on wallet
(Credential Issuance’s schema) — anything it needs about a credential is fetched via
CredentialIssuanceGrpcClient, never a native cross-schema query.
gRPC clients (all mTLS)
| Client | Downstream | Port |
|---|---|---|
CredentialIssuanceGrpcClient | Credential Issuance | 50051 |
VerificationEngineGrpcClient | Verification Engine | 50052 |
IssuerRegistryGrpcClient / IssuerSsoGrpcClient | Issuer Registry (shares one channel) | 50053 |
CustodianRegistryGrpcClient / CustodianSsoGrpcClient | Custodian Registry (shares one channel) | 50056 |
CatalogRegistryGrpcClient | Program Catalog | 50054 |
LearnerRecordsGrpcClient | Learner Records | 50055 |
All channels build via MtlsChannelFactory with no plaintext fallback — a missing/invalid cert
fails startup rather than silently downgrading.
Orchestration internals
IssuanceOrchestrator
Two entry points, deliberately separate methods (not a signerType branch): issueCredential
(Tier-1, issuer-signed) and attestCredentialAsCustodian (Tier-2, custodian-attested) — issuer
validation, accreditation-scope enforcement, and JWT-based caller authorization are Issuer-specific
concepts that don’t apply to a Custodian.
- Best-effort notification runs after audit, in its own try/catch — deliberate: by the time it
runs the credential is already durably issued and audited as
SUCCESS, so a notification failure must never surface as an issuance failure (that would both mis-log a spurious secondFAILEDrow and invite a retry with no idempotency key, risking a double-issue). A placeholder-DID holder gets an emailed claim link; otherwise an in-app notification — mutually exclusive. - No saga-style rollback — if a later step fails after an earlier one succeeded, there’s no
compensating transaction; error handling is “catch, audit as
FAILED, rethrow.”
attestCredentialAsCustodian is simpler: delegate to
credentialIssuanceClient.issueCustodianAttestation (persists a Tier-2
CustodianAttestationCredential carrying evidence object key/SHA-256 and an optional
catalog-entry-version link), audit as SUCCESS/FAILED attributed to the custodian (not an
issuer — AuditLogEntity.issuerId stays null), then the same best-effort notification pattern.
AuditService and the tamper-evident hash chain
AuditService is the single write path for every audit event — 15 distinct logging methods
(logIssuance, logVerification, logRevocation, logKeyRotation, logUnauthorizedAttempt,
logAdminAction, logSupersessionLink, logHolderAuthEvent, logClaimIntegrityEvent,
logVerifierAuthEvent, logCustodianAuthEvent, logDocumentPreviewAccess,
logDocumentCaptureAttempt, logCustodianComplianceEvent, logIssuerAuthEvent,
logCustodianConsentEvent, logHolderProofLinkEvent), all writing to core.audit_logs.
A subset — issuance, verification, custodian consent grants/revokes, holder proof-link lifecycle
events, and claim-integrity events — extend a per-holder, tamper-evident SHA-256 hash chain, not
just an append-only row. Given the full ordered sequence of a holder’s chained rows, recomputing
each row’s hash from its own fields plus the previous row’s hash must reproduce exactly the stored
content_hash — any row deleted, reordered, or edited after the fact breaks recomputation for
every row after it.
AuditChainHasher computes:
SHA-256(previousHash | credentialId | issuerId | verificationStatus |
verifiedAt.toEpochMilli() | ipAddress | verifierOrganization |
verifierAccountId | purpose | disclosedClaims)pipe-joined, nulls mapped to empty string. Deliberately excludes request_metadata (a JSONB
column) — Postgres normalizes stored JSON text on write, so re-reading it wouldn’t reproduce the
exact bytes hashed at write time, and every row would look tampered on first read.
AuditService.attachChainLink — how a row joins the chain:
Events on the same chain: logIssuance (Tier-1 and Tier-2), logVerification,
logCustodianConsentEvent, logHolderProofLinkEvent, logClaimIntegrityEvent — one chain per
holder, not a separate ledger per feature.
AuditChainVerificationService is the read-side counterpart — walks a holder’s chain and
recomputes hashes to detect tampering, mirroring AuditChainHasher.hash exactly, row-by-row in
order.