Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/151-objectstorage-control-probes.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

253 lines
14 KiB
Plaintext

Sub-scope 02 (objectstorage) control/** - canonical codec + durable record types
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T02:42:36+00:00
=== OWNED FILES ===
2aad9351e0ff5ed233e9214d3a1f4cb00e08856d control/CanonicalJsonObjectControlRecordCodec.java 898
181741da87e8e2cb446a12357fb92e253f1797f2 control/CanonicalJsonReader.java 144
cb68ae97f9f05107a889aa6c6c8ee9c8b902163d control/CanonicalJsonWriter.java 90
fb3b7ff53be9c5cb4e15f87d0fb4062f3a5fb88f control/ControlRecordSupport.java 37
66afca559b3bbfe7339c164275be64261063429a control/ObjectControlConflictException.java 11
afb47e404c79ce53c833758d0e09cff6584c3a4a control/ObjectControlCorruptionException.java 15
5e557c1346e0b608a64bde312f9f7ea753b1ae5e control/ObjectControlMutation.java 13
f11454db71aee82a0121da9815085539c6ee1e21 control/ObjectControlRecord.java 42
0d46ec38359f4b8a67c9894bc7dd84666e78bf86 control/ObjectControlRecordCodec.java 9
8952f710cdcb1e9ac0dfba38a8c38a59b8a330a9 control/ObjectControlRecordEnvelope.java 45
00219ff8fa7400ec15dd5342ac625e3450ffa651 control/ObjectControlStore.java 22
3c5092718ba9b840525734cd8c8c0e1d2265d721 control/ObjectControlVersion.java 18
e74088f13b773f1e500a7906a5ee0788341f3afc control/ObjectDirectMultipartGrantRecord.java 96
d7ee355f993afad82dccf31980ceacaf4aafecd8 control/ObjectDirectMultipartSessionRecord.java 198
2e8b26782800b119eec56a851c4c49fd392d770e control/ObjectDirectSessionRecord.java 32
33e0de98997a246d2ca4d73027c8d55bf60ceed2 control/ObjectManifestRecord.java 53
b33587f0c5ee6df490172334130397310cd32fb7 control/ObjectMultipartPartRecord.java 41
057db5996cc3d1bd04f2df1039c1ad7e7575f6b3 control/ObjectMultipartSessionRecord.java 46
9d777251edc5e12077f959bb48f9426319060925 control/ObjectOperationRecord.java 210
cec085f7ba744dbb0f549e50eed0db2c2f874f20 control/ObjectPublicationHandoffRecord.java 188
a72bfdafe079cacc721a5d7c90fb3fc98a82e303 control/ObjectReferencePointerRecord.java 32
6c38f41ba9826a83290ae1e068d66b2f5e4363fa control/ObjectReferenceRecord.java 39
fb3f5735058c2e2b623a2d5923f42242edee4bae control/ObjectStagedObjectRecord.java 180
c1c1c83a2e5ac08da148e92a047bd8c7ddc8aec3 control/UnsupportedObjectControlSchemaException.java 11
f8eee5cf1a763bb3b40401481d798691a2207b12 test/control/ObjectControlRecordCodecTest.java 339
main LOC: 2470 total
=== 8.1 reachability: does any control type escape the leaf? ===
$ git grep -rln 'objectstorage.control' -- . | grep -v "^adapter/outbound/objectstorage/"
exit=1
$ grep -c 'public' adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlRecord.java adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlStore.java
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlRecord.java:1
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlStore.java:2
exit=0
=== 8.2 canonical strictness: what the reader and writer refuse ===
$ grep -n 'printable ASCII only\|character < 0x20 || character > 0x7e' adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonWriter.java adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonReader.java adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ControlRecordSupport.java
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonWriter.java:82: } else if (character < 0x20 || character > 0x7e) {
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonWriter.java:83: throw new IllegalArgumentException("canonical JSON supports printable ASCII only");
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonReader.java:119: } else if (character < 0x20 || character > 0x7e) {
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ControlRecordSupport.java:25: || value.chars().anyMatch(character -> character < 0x20 || character > 0x7e)) {
exit=0
$ grep -n 'private void field' -A 10 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonReader.java
91: private void field(String expectedName) {
92- if (first) {
93- first = false;
94- } else {
95- expect(',');
96- }
97- if (!quoted().equals(expectedName)) {
98- throw corrupt();
99- }
100- expect(':');
101- }
exit=0
$ grep -n 'public void end' -A 6 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonReader.java
84: public void end() {
85- expect('}');
86- if (cursor != input.length()) {
87- throw corrupt();
88- }
89- }
90-
exit=0
$ grep -n 'digitStart == cursor' -B 6 -A 4 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonReader.java
53- cursor++;
54- }
55- int digitStart = cursor;
56- while (cursor < input.length() && input.charAt(cursor) >= '0' && input.charAt(cursor) <= '9') {
57- cursor++;
58- }
59: if (digitStart == cursor
60- || (input.charAt(digitStart) == '0' && cursor - digitStart > 1)
61- || (negative && input.charAt(digitStart) == '0')) {
62- throw corrupt();
63- }
exit=0
=== 8.2b envelope: digest + canonical Base64 round trip ===
$ grep -n 'Base64.getEncoder().encodeToString(payload).equals(base64Payload)' -B 8 -A 6 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonObjectControlRecordCodec.java
81- }
82- byte[] payload;
83- try {
84- payload = Base64.getDecoder().decode(base64Payload);
85- } catch (IllegalArgumentException exception) {
86- throw new ObjectControlCorruptionException(
87- "control payload is not canonical Base64", exception);
88- }
89: if (!Base64.getEncoder().encodeToString(payload).equals(base64Payload)
90- || !MessageDigest.isEqual(
91- digest.getBytes(StandardCharsets.US_ASCII),
92- sha256Hex(payload).getBytes(StandardCharsets.US_ASCII))) {
93- throw corrupt();
94- }
95- ObjectControlRecord record = decodePayload(payload);
exit=0
$ grep -n 'MAXIMUM_ENVELOPE_BYTES\|MAXIMUM_TERMINAL_RECEIPT_BYTES\|MAXIMUM_PART_BYTES\|enforceFamilySize' adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonObjectControlRecordCodec.java
47: private static final int MAXIMUM_ENVELOPE_BYTES = 64 * 1024;
48: private static final int MAXIMUM_TERMINAL_RECEIPT_BYTES = 16 * 1024;
49: private static final int MAXIMUM_PART_BYTES = 4 * 1024;
55: enforceFamilySize(record, payload.length);
72: if (encoded == null || encoded.length == 0 || encoded.length > MAXIMUM_ENVELOPE_BYTES) {
96: enforceFamilySize(record, payload.length);
107: if (encoded.length > MAXIMUM_ENVELOPE_BYTES) {
885: private static void enforceFamilySize(ObjectControlRecord record, int payloadLength) {
888: ? MAXIMUM_PART_BYTES
889: : record.terminal() ? MAXIMUM_TERMINAL_RECEIPT_BYTES : MAXIMUM_ENVELOPE_BYTES;
exit=0
=== 8.2c the reader decodes bytes leniently; what closes that? ===
$ grep -n 'new String(bytes, StandardCharsets.UTF_8)' -B 4 -A 3 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonReader.java
12- public CanonicalJsonReader(byte[] bytes) {
13- if (bytes == null) {
14- throw corrupt();
15- }
16: input = new String(bytes, StandardCharsets.UTF_8);
17- expect('{');
18- }
19-
exit=0
$ grep -n 'character < 0x20 || character > 0x7e' -B 3 -A 2 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonReader.java
116- if (character != '"' && character != '\\') {
117- throw corrupt();
118- }
119: } else if (character < 0x20 || character > 0x7e) {
120- throw corrupt();
121- }
exit=0
=== 8.3 sealed family + exhaustive switch ===
$ grep -n 'sealed interface ObjectControlRecord' -A 14 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlRecord.java
4:public sealed interface ObjectControlRecord
5- permits ObjectDirectSessionRecord,
6- ObjectDirectMultipartGrantRecord,
7- ObjectDirectMultipartSessionRecord,
8- ObjectManifestRecord,
9- ObjectMultipartPartRecord,
10- ObjectMultipartSessionRecord,
11- ObjectOperationRecord,
12- ObjectPublicationHandoffRecord,
13- ObjectReferencePointerRecord,
14- ObjectReferenceRecord,
15- ObjectStagedObjectRecord {
16-
17- int schemaVersion();
18-
exit=0
$ grep -n 'return switch (record)' -A 14 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/CanonicalJsonObjectControlRecordCodec.java
128: return switch (record) {
129- case ObjectDirectSessionRecord direct -> directSession(writer, direct);
130- case ObjectDirectMultipartSessionRecord session -> directMultipartSession(writer, session);
131- case ObjectDirectMultipartGrantRecord grant -> directMultipartGrant(writer, grant);
132- case ObjectOperationRecord operation -> operation(writer, operation);
133- case ObjectManifestRecord manifest -> manifest(writer, manifest);
134- case ObjectReferencePointerRecord pointer -> pointer(writer, pointer);
135- case ObjectReferenceRecord reference -> reference(writer, reference);
136- case ObjectStagedObjectRecord staged -> staged(writer, staged);
137- case ObjectPublicationHandoffRecord handoff -> handoff(writer, handoff);
138- case ObjectMultipartSessionRecord session -> session(writer, session);
139- case ObjectMultipartPartRecord part -> part(writer, part);
140- };
141- }
142-
exit=0
$ grep -n 'enum Family' -A 14 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlRecord.java | grep -c ','
10
exit=0
=== 8.3b LIST is deliberately absent ===
$ grep -n 'LIST is deliberately absent' -B 2 -A 8 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlStore.java
4-import java.util.Optional;
5-
6:/** Provider-neutral exact-lookup/create/CAS control storage. LIST is deliberately absent. */
7-public interface ObjectControlStore {
8-
9- Optional<StoredControlRecord> read(String key);
10-
11- StoredControlRecord create(String key, ObjectControlRecord record);
12-
13- StoredControlRecord compareAndSet(String key, ObjectControlMutation mutation);
14-
exit=0
$ git grep -rn 'interface ObjectControlStore' -A 8 -- 'adapter/outbound/objectstorage/src/main' | head -12
fatal: unable to resolve revision: -A
exit=0
=== 8.4 record invariants: do the compact constructors re-derive rather than trust? ===
$ grep -n 'actualPolicyDigest' -B 6 -A 4 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectOperationRecord.java
40- requestFingerprint = ControlRecordSupport.hexDigest("requestFingerprint", requestFingerprint);
41- operationKind = ControlRecordSupport.bounded("operationKind", operationKind, 64);
42- Objects.requireNonNull(route, "route must be non-null");
43- Objects.requireNonNull(policySnapshot, "policySnapshot must be non-null");
44- policySnapshotDigest =
45- ControlRecordSupport.hexDigest("policySnapshotDigest", policySnapshotDigest);
46: String actualPolicyDigest =
47- new CanonicalJsonObjectControlRecordCodec()
48- .sha256Hex(ObjectPolicySnapshotCodec.encode(policySnapshot));
49: if (!actualPolicyDigest.equals(policySnapshotDigest)) {
50- throw new IllegalArgumentException("policySnapshotDigest does not match the snapshot");
51- }
52- if (expectedContentIdentity != null
53- && expectedContentIdentity.exactLength() > policySnapshot.maximumObjectBytes()) {
exit=0
$ grep -n 'pending effect and certainty do not agree' -B 4 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectOperationRecord.java
58- throw new IllegalArgumentException("stateVersion must be positive");
59- }
60- Objects.requireNonNull(effectCertainty, "effectCertainty must be non-null");
61- if ((pendingEffect == null) != (effectCertainty == ObjectEffectCertainty.NOT_SENT)) {
62: throw new IllegalArgumentException("pending effect and certainty do not agree");
exit=0
$ grep -n 'active handoff lease is expired at write time\|cannot be released and abort-authorized' -B 4 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectPublicationHandoffRecord.java
37- }
38- claimantDigest = ControlRecordSupport.hexDigest("claimantDigest", claimantDigest);
39- Objects.requireNonNull(leaseExpiresAt, "leaseExpiresAt must be non-null");
40- if (!released && !abortAuthorized && !leaseExpiresAt.isAfter(updatedAt)) {
41: throw new IllegalArgumentException("active handoff lease is expired at write time");
42- }
43- if (released && abortAuthorized) {
44: throw new IllegalArgumentException("handoff cannot be released and abort-authorized");
exit=0
$ grep -nE '@Test| void ' adapter/outbound/objectstorage/src/test/java/dev/caskeleton/adapter/outbound/objectstorage/control/ObjectControlRecordCodecTest.java | head -30
57: @Test
58: void everyClosedRecordFamilyRoundTripsByteForByte() {
79: @Test
80: void operationGoldenEnvelopeIsFrozen() {
87: @Test
88: void everyRecordFamilyHasAFrozenGoldenEnvelopeDigest() {
104: @Test
105: void corruptionUnknownOuterFieldsTruncationAndOversizeFailClosed() {
124: @Test
125: void duplicateUnknownMissingAndReorderedPayloadFieldsFailClosed() {
144: @Test
145: void newerSchemaIsQuarantinedInsteadOfAppearingAbsent() {
exit=0