The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
119 lines
9.3 KiB
Plaintext
119 lines
9.3 KiB
Plaintext
Command: printf '%s\n' '--- EvidenceAware manager refs ---'; git grep -n -w 'EvidenceAwareJpaTransactionManager' -- 'src/**/src/main/java/**/*.java'; printf '%s\n' '--- actual construction calls ---'; git grep -n -E 'EvidenceAwareJpaTransactionManager\.standard|new EvidenceAwareJpaTransactionManager' -- 'src/**/src/main/java/**/*.java' || true; printf '%s\n' '--- recorder/record refs ---'; git grep -n -E '\bCompletionUnknown(Recorder|Record)\b' -- 'src/**/src/main/java/**/*.java' || true; printf '%s\n' '--- metric implementation/calls ---'; git grep -n -E 'jpa\.transaction\.completion\.unknown|recordCompletionUnknown|JpaTransactionObservation' -- 'src/**/src/main/java/**/*.java' 'docs/jpa/**'; printf '%s\n' '--- observability composition refs ---'; git grep -n -w 'JpaObservabilityAutoConfiguration' -- 'src/**/src/main/java/**/*.java' || true; printf '%s\n' '--- application indeterminate path ---'; sed -n '105,185p' src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/SpringPolicyTransactionPort.java
|
|
Working directory: /shared/codebase/clean-architecture-backend-template
|
|
Executed at: 2026-08-29T08:01:16Z
|
|
Source revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
Observation boundary: Traces current production reachability for the custom evidence-aware transaction manager, completion-unknown recorder/record, transaction observation, completion-unknown meter, and application indeterminate result. It does not simulate a real lost-commit-ack database/network failure.
|
|
--- stdout/stderr ---
|
|
--- EvidenceAware manager refs ---
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/EvidenceAwareJpaTransactionManager.java:24:public class EvidenceAwareJpaTransactionManager extends JpaTransactionManager {
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/EvidenceAwareJpaTransactionManager.java:30: public EvidenceAwareJpaTransactionManager(
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/EvidenceAwareJpaTransactionManager.java:37: public static EvidenceAwareJpaTransactionManager standard(
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/EvidenceAwareJpaTransactionManager.java:39: return new EvidenceAwareJpaTransactionManager(
|
|
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaTransactionAutoConfiguration.java:55: * EvidenceAwareJpaTransactionManager.standard(entityManagerFactory, clock)}. The composition root
|
|
--- actual construction calls ---
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/EvidenceAwareJpaTransactionManager.java:39: return new EvidenceAwareJpaTransactionManager(
|
|
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaTransactionAutoConfiguration.java:55: * EvidenceAwareJpaTransactionManager.standard(entityManagerFactory, clock)}. The composition root
|
|
--- recorder/record refs ---
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/CompletionUnknownRecord.java:19:public record CompletionUnknownRecord(
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/CompletionUnknownRecord.java:28: public CompletionUnknownRecord {
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/CompletionUnknownRecord.java:41: public static CompletionUnknownRecord from(
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/CompletionUnknownRecord.java:44: return new CompletionUnknownRecord(
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/CompletionUnknownRecorder.java:13:public interface CompletionUnknownRecorder {
|
|
--- metric implementation/calls ---
|
|
docs/jpa/observability.md:23:| `jpa.transaction.completion.unknown` | its own counter, deliberately |
|
|
docs/jpa/runbooks.md:7:**Signal:** `jpa.transaction.completion.unknown` incremented; a `CompletionUnknownRecord` in the
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/observation/JpaTransactionObservation.java:19:public final class JpaTransactionObservation {
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/observation/JpaTransactionObservation.java:31: public static final String COMPLETION_UNKNOWN_METER = "jpa.transaction.completion.unknown";
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/observation/JpaTransactionObservation.java:36: public JpaTransactionObservation(MeterRegistry registry, String persistenceUnit) {
|
|
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/observation/JpaTransactionObservation.java:79: public void recordCompletionUnknown(PersistenceOperationName operation) {
|
|
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaObservabilityAutoConfiguration.java:7:import dev.caskeleton.adapter.outbound.persistence.observation.JpaTransactionObservation;
|
|
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaObservabilityAutoConfiguration.java:49: public Optional<JpaTransactionObservation> transactionObservation() {
|
|
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaObservabilityAutoConfiguration.java:50: return registry.map(meters -> new JpaTransactionObservation(meters, persistenceUnit));
|
|
--- observability composition refs ---
|
|
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaObservabilityAutoConfiguration.java:26:public final class JpaObservabilityAutoConfiguration {
|
|
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaObservabilityAutoConfiguration.java:35: public JpaObservabilityAutoConfiguration(
|
|
--- application indeterminate path ---
|
|
T value;
|
|
try {
|
|
value = action.get();
|
|
} catch (RuntimeException actionFailure) {
|
|
return new AttemptResult<>(
|
|
rollback(status, request.operationId(), tracker, actionFailure), physicalOwner);
|
|
}
|
|
|
|
tracker.observe(TransactionPhase.COMMIT_REQUESTED);
|
|
try {
|
|
transactionManager.commit(status);
|
|
} catch (RuntimeException commitFailure) {
|
|
if (sentinel.commitAcknowledged()) {
|
|
return new AttemptResult<>(
|
|
new TransactionResult.CommittedWithPostCommitFailure<>(
|
|
value, request.operationId(), commitFailure),
|
|
physicalOwner);
|
|
}
|
|
if (sentinel.rolledBack()
|
|
|| commitFailure instanceof UnexpectedRollbackException
|
|
|| TransactionRetryClassifier.isReplayCandidate(commitFailure)) {
|
|
return new AttemptResult<>(
|
|
new TransactionResult.DeterminateRollback<>(translate(commitFailure)), physicalOwner);
|
|
}
|
|
return new AttemptResult<>(
|
|
new TransactionResult.Indeterminate<>(
|
|
request.operationId(), tracker.lastObserved(), Optional.empty()),
|
|
physicalOwner);
|
|
}
|
|
|
|
if (!physicalOwner) {
|
|
return new AttemptResult<>(new TransactionResult.Participating<>(value), false);
|
|
}
|
|
tracker.observe(TransactionPhase.COMMIT_ACKED);
|
|
return new AttemptResult<>(
|
|
new TransactionResult.Committed<>(value, request.operationId()), true);
|
|
}
|
|
|
|
private boolean shouldRetry(
|
|
TransactionPolicyId policyId, AttemptResult<?> attemptResult, int attempt) {
|
|
if (policyId != TransactionPolicyId.COMMAND_SERIALIZABLE_REPLAY_SAFE
|
|
|| !attemptResult.physicalOwner()
|
|
|| attempt >= retryBackoff.maximumAttempts()
|
|
|| Thread.currentThread().isInterrupted()) {
|
|
return false;
|
|
}
|
|
TransactionResult<?> result = attemptResult.result();
|
|
if (result instanceof TransactionResult.DeterminateRollback<?> rollback) {
|
|
return TransactionRetryClassifier.isReplayCandidate(rollback.failure());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private <T> TransactionResult<T> rollback(
|
|
TransactionStatus status,
|
|
Optional<OperationId> operationId,
|
|
PhaseTracker tracker,
|
|
RuntimeException actionFailure) {
|
|
try {
|
|
transactionManager.rollback(status);
|
|
return new TransactionResult.DeterminateRollback<>(translate(actionFailure));
|
|
} catch (RuntimeException rollbackFailure) {
|
|
actionFailure.addSuppressed(rollbackFailure);
|
|
return new TransactionResult.Indeterminate<>(
|
|
operationId, tracker.lastObserved(), Optional.empty());
|
|
}
|
|
}
|
|
|
|
private RuntimeException translate(RuntimeException failure) {
|
|
return exceptionTranslator.translate(failure).map(RuntimeException.class::cast).orElse(failure);
|
|
}
|
|
|
|
private DefaultTransactionDefinition definition(
|
|
TransactionPolicyId policyId, TransactionStartBudget startBudget, PolicySpec policy) {
|
|
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
|
definition.setName("application-" + policyId.name().toLowerCase(Locale.ROOT));
|
|
definition.setPropagationBehavior(policy.propagation());
|
|
definition.setIsolationLevel(policy.isolation());
|
|
definition.setReadOnly(policy.readOnly());
|
|
definition.setTimeout(startBudget.springTimeoutSeconds());
|
|
return definition;
|
|
|
|
Exit code: 0
|