Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/255-messaging-capability-doc-vs-code-drift.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

147 lines
6.8 KiB
Plaintext

# evidence 255 — messaging-capability-doc-vs-code-drift
# revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5
# cwd: /shared/codebase/clean-architecture-backend-template/src/messaging
# command: bash /tmp/capdrift.sh
# ---- raw output ----
==== code: MessagingCapabilities record component order ====
boolean brokerAcknowledgement,
boolean replicationOrPersistenceEvidence,
boolean perMessageSettlement,
boolean batchSettlement,
boolean orderedStream,
boolean keyedOrdering,
boolean replay,
boolean delayedDelivery,
boolean brokerTransaction,
boolean deduplicatedPublish,
boolean nativeDeadLetter,
boolean topologyManagement) {
==== code: per-adapter CAPABILITIES arrays ====
-- messaging-kafka (KafkaMessagingTransport)
private static final MessagingCapabilities CAPABILITIES =
new MessagingCapabilities(
true, true, true, true, true, true, true, false, true, false, false, true);
-- messaging-rabbit (RabbitMessagingTransport)
new MessagingCapabilities(
true, true, true, false, false, false, false, true, false, false, true, true);
-- messaging-kafka-share-experimental (KafkaShareWorkQueueCapability)
public static MessagingCapabilities capabilities() {
return new MessagingCapabilities(
true, true, true, false, false, false, false, false, false, false, false, false);
-- messaging-pulsar-experimental (PulsarMessagingTransport, SHARED and KEY_SHARED)
private static final MessagingCapabilities SHARED_CAPABILITIES =
new MessagingCapabilities(
true, true, true, true, false, false, true, true, false, false, true, true);
private static final MessagingCapabilities KEY_SHARED_CAPABILITIES =
new MessagingCapabilities(
true, true, true, true, false, true, true, true, false, false, true, true);
-- messaging-nats-experimental (NatsJetStreamTransport)
private static final MessagingCapabilities CAPABILITIES =
new MessagingCapabilities(
true, true, true, true, true, false, true, false, false, true, false, true);
==== doc: docs/messaging/support-matrix.md capability table ====
## Capability 매트릭스
`MessagingCapabilities`가 런타임에 선언하는 값이다. `false`인 기능을 요구하는 destination profile은
**startup에서 실패**하며, 조용히 약화되지 않는다.
| Capability | Kafka | Kafka Share | RabbitMQ | Pulsar | NATS JS |
|---|---|---|---|---|---|
| brokerAcknowledgement | O | O | O | O | O |
| replicationOrPersistenceEvidence | O | O | O | O | O |
| perMessageSettlement | O | O | O | O | O |
| batchSettlement | O | X | X | O | O |
| orderedStream | O | **X** | X | X | O |
| keyedOrdering | O | **X** | X | Key_Shared만 | X |
| replay | O | X | X | O | O |
| delayedDelivery | X | X | retry queue로 대행 | O | X |
| brokerTransaction | O | X | X | 미승격 | X |
| deduplicatedPublish | O | X | X | X | O |
| nativeDeadLetter | X | X | O | O | **X** |
| topologyManagement | O | X | O | O | O |
Kafka Share Group이 ordering 전부 `X`인 것은 설계 결정이다. share group은 개별 record를
==== code javadoc: why Kafka deduplicatedPublish is false ====
*/
public final class KafkaMessagingTransport implements MessagingTransport {
/**
* What this adapter can prove, with deduplicated publish set to false.
*
* <p>It was {@code true}, justified by producer idempotence. Producer idempotence deduplicates
* <em>sequence retries within one producer session</em>: the producer id is reassigned on
* restart, so the same logical message published again after a crash is a new sequence and the
* broker stores it twice. The capability an application reads is "the broker suppresses duplicate
* publishes of a stable id", which needs a persistent message-id store that this platform does
* not have.
*
* <p>Declaring it true means {@code PublishDeduplication} is accepted and silently does nothing —
* the caller believes the broker is deduplicating and skips the idempotency it would otherwise
* build. False makes that request a startup failure instead, which is the whole reason the flag
* exists.
*
* <p>The order of these flags is the record's: brokerAcknowledgement, replication evidence,
* per-message settlement, batch settlement, ordered stream, keyed ordering, replay, delayed
* delivery, broker transaction, deduplicated publish, native dead letter, topology management.
*/
private static final MessagingCapabilities CAPABILITIES =
==== doc: support-matrix.md runtime-membership claim ====
23:> 또한 registry의 messaging leaf는 모두 `runtime_memberships`가 비어 있다. 이는 **build-only /
==== authority: src/messaging/CLAUDE.md runtime-membership section ====
## Runtime membership이 비어 있다는 것의 의미
**이 절은 한동안 사실이 아닌 채로 남아 있었다.** "registry의 모든 messaging leaf는
`runtime_memberships`가 비어 있고 따라서 build-only"라고 쓰여 있었는데, 다섯 어댑터 remediation이
`messaging-spring-boot-starter`를 `app-bootstrap` 의존성으로 넣으면서 그 closure 전체가 런타임
classpath에 올라갔다. 정확한 목록은 registry가 소유하므로 여기서 세지 않는다 — 세는 순간 다시
drift한다.
지금 의미는 이렇다:
- 대부분의 leaf는 `runtime_memberships`에 `app-bootstrap`을 갖는다. **배포된 아티팩트가 싣고 있고,
master switch `app.messaging.enabled`로 켜진다.** 기본값은 `false`다.
- 여전히 비어 있는 leaf(experimental transport, 일부 codec, bridge, testkit)만이 build-only /
incubating이다.
- **런타임에 있다는 것과 자격이 증명됐다는 것은 다르다.** Kafka publish 경로만 실브로커 round trip으로
자격 검증됐고(`MessagingLiveRoundTripQualificationTest`), Rabbit·JDBC inbox/outbox·claim check·admin
plane은 shipped, inactive, unqualified다. 이 구분은
`MessagingMembershipQualificationTest`가 문서가 아니라 테스트로 붙들고 있다.
- 애플리케이션에 새로 배선하려면 registry의 `runtime_memberships`를 먼저 바꾸고
==== registry truth ====
shipped (runtime_memberships non-empty): 18
messaging-admin-api
messaging-admin-runtime
messaging-claim-check
messaging-cloudevents
messaging-core-api
messaging-inbox-jdbc-postgresql
messaging-kafka
messaging-observability
messaging-outbox-jdbc-postgresql
messaging-policy
messaging-rabbit
messaging-reliability-api
messaging-runtime-core
messaging-schema-api
messaging-schema-json
messaging-security
messaging-spring-boot-starter
messaging-transport-spi
build-only (runtime_memberships == []): 7
messaging-kafka-share-experimental
messaging-nats-experimental
messaging-pulsar-experimental
messaging-schema-avro
messaging-schema-protobuf
messaging-spring-cloud-stream-bridge
messaging-testkit