Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/045-persistence-jpa-completion-evidence-reachability.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
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>
2026-09-04 22:51:59 +09:00

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