Skip to Content

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

DomainControllersBase path
Public / unauthenticatedPublicVerificationController, 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 portalIssuerAuthController, IssuerPortalController, IssuerPortalUserController, IssuerTeamController, IssuerSsoController, IssuerApiKeyController, IssuerKycController, IssuerManagementController/api/v1/issuer*, /portal/me*
VerifierVerifierAuthController, 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/exportAvetmissExportController, 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)

GroupRepresentative tables
Auditaudit_logs — the hash-chained tamper-evident trail
Holderpersons, 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 integrityclaim_challenges, claim_holds, holder_onboarding_invites
Issuer portalissuer_portal_users, issuer_portal_sessions, issuer_preferences, issuer_magic_links, issuer_sessions, issuer_onboarding_leads
Verifierverifier_accounts, verifier_magic_links, verifier_sessions
Custodian portal/authcustodian_accounts, custodian_portal_users, custodian_portal_sessions, custodian_magic_links, custodian_sessions, custodian_onboarding_leads
Custodian intakecustodian_intake_batches, custodian_intake_items, custodian_intake_item_catalog_matches, custodian_item_events
Custodian compliancecustodian_credential_policies, custodian_waivers, custodian_expiry_reminders, custodian_reperformance_schedules, custodian_erasure_requests
Custodian consent/evidencecustodian_consent_requests, custodian_holder_consents, custodian_evidence_requests
Custodian workforcecustodian_people, custodian_groups, custodian_group_requirements, custodian_group_members
Custodian searchcustodian_saved_searches, custodian_search_defaults, custodian_saved_search_usage, custodian_search_history
LLMllm_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)

ClientDownstreamPort
CredentialIssuanceGrpcClientCredential Issuance50051
VerificationEngineGrpcClientVerification Engine50052
IssuerRegistryGrpcClient / IssuerSsoGrpcClientIssuer Registry (shares one channel)50053
CustodianRegistryGrpcClient / CustodianSsoGrpcClientCustodian Registry (shares one channel)50056
CatalogRegistryGrpcClientProgram Catalog50054
LearnerRecordsGrpcClientLearner Records50055

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 second FAILED row 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.