feat: add production capability foundations
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
# Fileserver Production Capability Deep Design
|
||||
|
||||
- 작성일: 2026-07-26
|
||||
- 상태: 상세 설계 완료, Phase 0–1 및 Phase 2 일부 local R1 구현, R2 이상 미구현
|
||||
- 상태: 상세 설계 완료, Phase 0–1 및 Phase 2 `local-persistent` R2 구현, 후속 provider/운영
|
||||
capability 미구현
|
||||
- 독립 아키텍처 재리뷰: blocker/high 0건
|
||||
- 기준: Java 21, Spring Boot 4.0.0, Gradle 멀티모듈 Clean Architecture
|
||||
- 대상 leaf: `adapter-outbound-fileserver`
|
||||
- 구현 추적: 이 문서의 목표 전체가 아니라 framework-free port, local staged CSV, single-node
|
||||
operation journal/recovery까지만 적용되었다.
|
||||
- 구현 추적: 이 문서의 장기 목표 전체가 아니라 provider-neutral application/control 계약,
|
||||
exact selector, pre-provisioned local filesystem을 위한 `local-persistent` R2 provider까지만
|
||||
적용되었다.
|
||||
- 상위 문서:
|
||||
[Production Capability Platform Design](2026-07-26-production-capability-platform-design.md)
|
||||
|
||||
@@ -26,24 +28,45 @@
|
||||
- operation-scoped JVM/OS file lock과 hard-link-only publication protocol;
|
||||
- overwrite-capable legacy port의 별도 opt-in/root 및 canonical overlap 차단;
|
||||
- 안전한 commit primitive가 없을 때 copy-to-final로 downgrade하지 않는 fail-closed 동작.
|
||||
- `app.fileserver` exact destination/provider selector와 producer 호출 전 unknown destination
|
||||
거부;
|
||||
- provider ID별 singleton runtime과 서로 다른 provider ID의 동일 normalized root 소유 거부;
|
||||
- provider-neutral canonical operation v2/private manifest/reference index와 opaque
|
||||
`fsr1.<route-token>.<file-id>.<check-digits>` direct lookup;
|
||||
- strict UTF-8/canonical schema-v1 terminal record의 read-only compatibility와 schema-v2-only
|
||||
write;
|
||||
- absolute/pre-provisioned root, ancestor/root symlink, real path, owner/mode, FileStore
|
||||
name/type, mount sentinel, `SecureDirectoryStream`, exclusive-create/hard-link/file·directory
|
||||
force startup attestation;
|
||||
- `WRITING -> SEALED -> DATA_PUBLISHED -> MANIFEST_PUBLISHED -> REFERENCE_PUBLISHED ->
|
||||
PUBLISHED` durable publication ordering;
|
||||
- data/manifest/reference/receipt 전체 교차검증과 deterministic resume/quarantine;
|
||||
- terminal mismatch에서 journal과 모든 artifact를 불변 보존하는 fail-closed recovery;
|
||||
- `FILE_AND_DIRECTORY_SYNC` receipt와 forked-process force-boundary/OS operation-lock
|
||||
qualification seam;
|
||||
- `app-bootstrap` opt-in composition과 disabled-default/no-filesystem-side-effect gating.
|
||||
|
||||
아직 구현되지 않은 범위:
|
||||
|
||||
- Phase 2의 cross-node fencing, reference/private-manifest index, exhaustive crash/symlink-race
|
||||
qualification;
|
||||
- 운영 cleanup/quota/retention과 effective capability probe인 Phase 3;
|
||||
- `shared-mounted`/NFS multi-client semantics와 cross-node producer fencing;
|
||||
- 운영 background reconciliation/reaper, retention, quota/backpressure인 Phase 3;
|
||||
- Fileserver 전용 readiness/health, metrics, tracing, structured audit;
|
||||
- SFTP provider인 Phase 4;
|
||||
- NFS/HA/bootstrap evidence인 Phase 5;
|
||||
- NFS/HA/operator topology evidence인 Phase 5;
|
||||
- optional delete/read/scan operation인 Phase 6.
|
||||
|
||||
따라서 현재 journal은 single-node local recovery seam이며 Fileserver R2 완료 증거가 아니다.
|
||||
기존 `FileExportPort`도 호환성을 위해
|
||||
따라서 현재 R2 claim은 `local-persistent`에만 한정한다. `FILE_AND_DIRECTORY_SYNC`는 attested
|
||||
filesystem 안에서 file과 관련 directory force가 성공했다는 뜻이며 physical device,
|
||||
storage-controller cache, volume replica, backup/site의 power-loss protection을 뜻하지 않는다.
|
||||
그 축은 deployment/storage evidence가 별도로 소유한다. 기존 `FileExportPort`도 호환성을 위해
|
||||
남아 있으며, 전체 행 materialization과 absolute path receipt를 사용하는 legacy 경로다.
|
||||
기존 R1 terminal artifact는 strict read-only로 원래 `PROCESS_LOCAL_SYNC` receipt만 복원하고
|
||||
manifest/reference 생성, schema-v2 rewrite, R2 guarantee 자동 승격을 하지 않는다.
|
||||
|
||||
## 1. 설계 판정
|
||||
|
||||
현재 Fileserver 구현은 운영 파일서버가 아니라 다음 한 경로만 제공하는 R1 이하의 로컬
|
||||
CSV 예제다.
|
||||
설계 시작 당시 Fileserver 구현은 운영 파일서버가 아니라 다음 한 경로만 제공하는 R1 이하의
|
||||
로컬 CSV 예제였다. 현재의 increment 상태와 보장 경계는 §0을 따른다.
|
||||
|
||||
```text
|
||||
List<List<String>>
|
||||
@@ -109,7 +132,11 @@ List<List<String>>
|
||||
|
||||
이번 문서는 위 항목을 구현 계획을 작성할 수 있는 수준까지 확정한다.
|
||||
|
||||
## 3. 현재 코드의 증거 기반 진단
|
||||
## 3. 초기 코드의 증거 기반 진단
|
||||
|
||||
아래 표는 설계가 시작된 2026-07-26의 baseline을 보존한 역사적 진단이다. 현재 구현 상태는
|
||||
§0이 권위이며, 아래 결함 중 streaming/opaque receipt/exclusive publication/control plane/local
|
||||
attestation/composition은 후속 increment에서 해소되었다.
|
||||
|
||||
| 영역 | 현재 구현 | 운영상 의미 |
|
||||
| --- | --- | --- |
|
||||
@@ -134,13 +161,14 @@ List<List<String>>
|
||||
- `src/application-core/src/main/java/dev/caskeleton/application/fileexport/FileExportPort.java`
|
||||
- `src/application-core/src/main/java/dev/caskeleton/application/fileexport/ExportedFile.java`
|
||||
- `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FilesystemCsvExportAdapter.java`
|
||||
- `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportProperties.java`
|
||||
- `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportSettings.java`
|
||||
- `src/adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilesystemCsvExportAdapterTest.java`
|
||||
- `src/config/architecture/modules.json`
|
||||
- `src/app-bootstrap/build.gradle`
|
||||
|
||||
현재 7개 fileserver unit test와 leaf `check`는 성공한다. 이는 현재 문서화된 로컬 happy-path
|
||||
계약이 동작한다는 증거일 뿐 production readiness 증거는 아니다.
|
||||
당시 7개 fileserver unit test와 leaf `check` 성공은 로컬 happy-path만 증명했다. 현재의
|
||||
`local-persistent` claim은 별도 root attestation, control/payload/recovery, forked crash와
|
||||
cross-process OS lock qualification suite의 통과를 요구한다.
|
||||
|
||||
## 4. 범위와 명시적 비범위
|
||||
|
||||
@@ -1927,22 +1955,26 @@ ca-skeleton:
|
||||
`docs/registries/env-keys.yaml`, `application.yml`, typed settings, conditional beans를 end-to-end
|
||||
검증한다.
|
||||
|
||||
Template baseline에 필요한 key 예:
|
||||
현재 구현된 `local-persistent` composition에 등록하는 key:
|
||||
|
||||
```text
|
||||
APP_FILESERVER_PRIMARY_ROOT
|
||||
APP_FILESERVER_PRIMARY_MOUNT_ID
|
||||
APP_FILESERVER_SFTP_HOST
|
||||
APP_FILESERVER_SFTP_USERNAME
|
||||
APP_FILESERVER_SFTP_PRIVATE_KEY_SECRET_REF
|
||||
APP_FILESERVER_SFTP_KNOWN_HOSTS_SECRET_REF
|
||||
APP_FILESERVER_SFTP_CONTROL_ROOT
|
||||
APP_FILESERVER_SFTP_SPOOL_ROOT
|
||||
APP_FILESERVER_SECRET_CONFIG_ROOT
|
||||
APP_FILESERVER_ENABLED
|
||||
APP_FILESERVER_LOCAL_ROOT
|
||||
APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_NAME
|
||||
APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_TYPE
|
||||
APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256
|
||||
APP_FILESERVER_LOCAL_EXPECTED_OWNER
|
||||
```
|
||||
|
||||
모두 restart-only다. `APP_FILESERVER_ENABLED=false`가 shipped default이며, 나머지 다섯
|
||||
attestation 값은 `app.fileserver.enabled=true`일 때 모두 필요하다. Root는 absolute/existing
|
||||
directory, FileStore name/type과 owner는 non-blank exact match, sentinel digest는 64-character
|
||||
lowercase SHA-256여야 한다.
|
||||
|
||||
Dynamic destination topology는 YAML/config tree가 소유하고 secret value는 secret source가
|
||||
제공한다.
|
||||
제공한다. 앞의 broader topology 예시에 있는 SFTP/NFS key는 아직 env registry나 shipped
|
||||
`application.yml`에 등록하지 않는다. 실제 provider, dependency, real-service qualification이
|
||||
추가되는 후속 increment에서만 등록한다.
|
||||
|
||||
## 24. Health와 observability
|
||||
|
||||
@@ -2228,9 +2260,10 @@ Nightly:
|
||||
|
||||
### 27.1 Dependency ownership
|
||||
|
||||
현재 가장 가까운 `src/adapter/outbound/fileserver/CLAUDE.md`는 pure JDK, external dependency
|
||||
없음, NFS/SFTP stand-in만을 허용한다. 따라서 이 문서만으로 SFTP SDK를 Gradle에 추가할 수
|
||||
없다. 구현 Phase 0에서 아키텍처 승인과 함께 다음 rule drift를 먼저 갱신한다.
|
||||
현재 가장 가까운 `src/adapter/outbound/fileserver/CLAUDE.md`는 pure JDK filesystem과 Spring
|
||||
configuration baseline만 허용하고 `local-persistent`만 구현 대상으로 인정한다. NFS/SFTP
|
||||
stand-in이나 SDK는 허용하지 않는다. 따라서 이 문서만으로 SFTP SDK를 Gradle에 추가할 수 없다.
|
||||
후속 SFTP 구현에서는 아키텍처 승인과 함께 다음 rule drift를 먼저 갱신한다.
|
||||
|
||||
- local `CLAUDE.md`의 책임을 local-only demo에서 provider-based publication으로 변경;
|
||||
- external `NONE` 규칙을 exact allowlist로 변경;
|
||||
@@ -2240,7 +2273,7 @@ Nightly:
|
||||
|
||||
이 rule migration 전 SFTP dependency 추가나 runtime activation은 HARD-STOP이다.
|
||||
|
||||
`adapter-outbound-fileserver`:
|
||||
후속 provider rule migration의 후보 allowlist이며 현재 dependency가 아니다:
|
||||
|
||||
- JDK NIO local/mounted provider;
|
||||
- Spring autoconfigure;
|
||||
@@ -2260,7 +2293,8 @@ starter를 추가하지 않는다.
|
||||
|
||||
### 27.2 Bootstrap composition
|
||||
|
||||
안전한 explicit binding/gating과 config test가 먼저 구현된 후:
|
||||
`local-persistent`에 대한 안전한 explicit binding/gating과 config test가 구현되었고 다음
|
||||
composition을 적용했다.
|
||||
|
||||
1. `modules.json`의 `app-bootstrap.allowed_dependencies`에
|
||||
`adapter-outbound-fileserver` 추가;
|
||||
@@ -2271,7 +2305,8 @@ starter를 추가하지 않는다.
|
||||
6. disabled-adapter architecture scan에 fileserver 추가;
|
||||
7. env/settings/readiness contract 추가.
|
||||
|
||||
Classpath에 들어왔다는 이유로 local provider가 활성화되면 안 된다.
|
||||
`application.yml`의 `app.fileserver.enabled=false`가 shipped default다. Classpath에 들어왔다는
|
||||
이유만으로 local provider가 활성화되거나 filesystem side effect가 발생하지 않는다.
|
||||
|
||||
### 27.3 SDK split trigger
|
||||
|
||||
@@ -2287,9 +2322,11 @@ Classpath에 들어왔다는 이유로 local provider가 활성화되면 안 된
|
||||
|
||||
### Phase 0 — Truthful topology와 contract freeze
|
||||
|
||||
- 현재 Fileserver를 R1 local CSV demo로 명시;
|
||||
상태: 완료. 현재 문서는 provider별 구현 상태와 보장 경계를 분리한다.
|
||||
|
||||
- 초기 Fileserver를 R1 local CSV demo로 명시하고 후속 R2 범위를 분리;
|
||||
- Fileserver `CLAUDE.md`와 README의 responsibility/dependency/registry SSOT drift 수정;
|
||||
- current bootstrap 미합성 상태 명시;
|
||||
- 초기 bootstrap 미합성 상태와 후속 disabled-default opt-in composition을 함께 기록;
|
||||
- v2 contract와 error registry 승인;
|
||||
- journal/reference/control-plane schema 승인;
|
||||
- accepted-attempt와 global coordination guarantee 분리;
|
||||
@@ -2306,6 +2343,8 @@ Acceptance:
|
||||
|
||||
### Phase 1 — Streaming application contract와 CSV
|
||||
|
||||
상태: 완료. Framework-free `FilePublicationPort`와 bounded streaming CSV 경로가 구현되었다.
|
||||
|
||||
- `FilePublicationPort`;
|
||||
- operation ID/fingerprint;
|
||||
- effective policy snapshot;
|
||||
@@ -2321,6 +2360,9 @@ Acceptance:
|
||||
|
||||
### Phase 2 — Secure local/mounted publication
|
||||
|
||||
상태: `local-persistent` 완료. `shared-mounted`/NFS multi-client profile과 cross-node fencing은
|
||||
미구현이다.
|
||||
|
||||
- staging;
|
||||
- digest/manifest;
|
||||
- sealed journal과 protocol별 artifact ordering;
|
||||
@@ -2336,6 +2378,8 @@ Acceptance:
|
||||
|
||||
### Phase 3 — Resource/maintenance/observability
|
||||
|
||||
상태: 미구현.
|
||||
|
||||
- concurrency/byte quota;
|
||||
- timeout/cancel/shutdown;
|
||||
- staging reaper/report;
|
||||
@@ -2348,6 +2392,8 @@ Acceptance:
|
||||
|
||||
### Phase 4 — SFTP provider
|
||||
|
||||
상태: 미구현. SFTP setting/env/dependency/bean도 등록하지 않는다.
|
||||
|
||||
- Spring Integration/Apache MINA;
|
||||
- host key/secrets;
|
||||
- bounded pool/timeouts;
|
||||
@@ -2362,6 +2408,9 @@ Acceptance:
|
||||
|
||||
### Phase 5 — NFS/HA evidence와 bootstrap
|
||||
|
||||
상태: `app-bootstrap`의 disabled-default opt-in composition과 local env mapping만 완료.
|
||||
NFS/HA/operator topology evidence는 미구현이다.
|
||||
|
||||
- multi-client NFS profile;
|
||||
- operator attestation;
|
||||
- app-bootstrap composition;
|
||||
@@ -2374,6 +2423,8 @@ Acceptance:
|
||||
|
||||
### Phase 6 — Optional read/delete와 module split review
|
||||
|
||||
상태: 미구현.
|
||||
|
||||
- opaque content transfer;
|
||||
- expected-version managed delete;
|
||||
- provider split 조건 재평가;
|
||||
@@ -2381,6 +2432,9 @@ Acceptance:
|
||||
|
||||
## 29. 완료 기준
|
||||
|
||||
아래는 이 장기 설계 전체의 완료 기준이며 현재 충족되지 않았다. 현재 완료 claim은 §0의
|
||||
`local-persistent` R2 범위로 제한한다.
|
||||
|
||||
Fileserver R2 완료를 주장하려면:
|
||||
|
||||
- application contract에 path/provider/SDK가 없음;
|
||||
|
||||
@@ -949,6 +949,13 @@ and durable interfaces are explicit.
|
||||
|
||||
### 13.3 Object storage
|
||||
|
||||
The authoritative implementation-level design for this capability is
|
||||
[Object Storage Production Capability Deep Design](2026-07-28-objectstorage-production-capability-design.md).
|
||||
Its ordered RED–GREEN execution batches and promotion gates are in the
|
||||
[Object Storage Production Capability Implementation Plan](../plans/2026-07-28-objectstorage-production-capability.md).
|
||||
This subsection is only the cross-capability baseline; the dedicated design governs when details
|
||||
differ.
|
||||
|
||||
Replace whole-object `byte[]` as the only path with:
|
||||
|
||||
- streaming upload/download and range reads;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Redis Production Capability Deep Design
|
||||
|
||||
- Date: 2026-07-26
|
||||
- Status: 상세 설계 완료, Phase 0 및 Phase 1 일부 standalone R1 구현, R2 미구현
|
||||
- Status: 상세 설계 완료, 5개 standalone `implemented-candidate`, selected/R2 없음
|
||||
- Scope: Redis 전용 production capability와 단계적 구현 설계
|
||||
- Baseline: Java 21, Spring Boot 4.0.0, Gradle multi-module Clean Architecture template
|
||||
- Parent:
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
## 0. 구현 상태
|
||||
|
||||
2026-07-28 기준 구현된 범위:
|
||||
2026-07-30 기준 구현된 범위:
|
||||
|
||||
- `application-core`의 provider-neutral `CacheRegionPort`와 hit/negative/miss/schema/unavailable
|
||||
결과 구분;
|
||||
@@ -21,9 +21,17 @@
|
||||
- generic application API가 아닌 package-private `RedisAtomicPrimitives` internal R0 foundation과
|
||||
compatibility failure;
|
||||
- managed Lettuce standalone connection lifecycle과 finite command timeout;
|
||||
- `EVALSHA` 우선, 정확한 `NOSCRIPT`에만 `EVAL` fallback하는 production executor;
|
||||
- `EVALSHA` 우선, 정확한 `NOSCRIPT`에만 catalog script를 `SCRIPT LOAD`하고 digest를 검증한 뒤
|
||||
`EVALSHA`를 한 번 재시도하는 production executor;
|
||||
- versioned digest-protected bounded binary cache envelope, positive/negative TTL, invalidate와
|
||||
corrupt/future/unavailable 구분을 제공하는 `CacheRegionPort<String,String>` reference adapter;
|
||||
- envelope v2의 absolute soft/hard expiry, injected clock freshness 판정, deterministic
|
||||
policy-revision/key jitter, hard minimum과 physical Redis TTL 일치;
|
||||
- framework-free `CacheAsideExecutor`와 typed source/result/cancellation contract;
|
||||
- maximum in-flight key/waiter/source concurrency/admission/load deadline을 제한하는 local
|
||||
single-flight와 source bulkhead, abandoned-flight opportunistic reaping;
|
||||
- authoritative absence만 negative-cache하고 classified transient failure에만 hard-expiry 전
|
||||
stale fallback을 허용하는 application policy;
|
||||
- HMAC key secret/namespace/value bound typed settings와 disabled zero-connection composition;
|
||||
- `managed`/`external` client mode를 통한 결정적 runtime 선택;
|
||||
- reconnect command replay 차단, finite Lettuce request queue와 client-side admission;
|
||||
@@ -32,27 +40,44 @@
|
||||
- managed runtime 활성화 시 Redis host 누락을 `localhost`로 숨기지 않는 startup fail-fast;
|
||||
- generic Lua executor/descriptor와 raw-key typed primitive를 package-private collaborator로
|
||||
닫고 Spring composition에는 semantic cache port만 노출;
|
||||
- 명시적 Redis 7.4 standalone service lane의 실제 TTL expiry, compare-delete Lua,
|
||||
oversized bulk-reply 차단 검증.
|
||||
- 명시적 Redis 7.2/7.4 standalone service lane의 실제 TTL expiry, compare-delete Lua,
|
||||
oversized bulk-reply 차단 검증;
|
||||
- `shared-contract`의 provider-neutral edge rate-limit request/policy/decision/outcome/port;
|
||||
- fixed window, sliding-window counter, token bucket의 versioned one-key Lua와 bounded
|
||||
structured MULTI reply parser;
|
||||
- private HMAC key, Redis server time, clock regression clamp, denial-no-consume, finite state
|
||||
TTL과 pre-send/post-dispatch failure certainty를 보존하는 semantic provider;
|
||||
- cache와 endpoint/connection/admission/settings를 공유하지 않는 coordination-role 전용
|
||||
`app.rate-limit` composition과 disabled zero-side-effect gating;
|
||||
- 세 알고리즘을 실제 standalone Redis에 실행하도록 선택 가능한 service qualification lane;
|
||||
- request-replay idempotency, cache refresh soft lease, versioned session repository semantic
|
||||
provider와 각 card-owned standalone/security/fault/compatibility evidence;
|
||||
- cache generation/revision invalidation, bounded local L1, authenticated invalidation hint,
|
||||
semantic health/metrics와 standalone TLS+named ACL evidence.
|
||||
|
||||
아직 구현되지 않은 범위:
|
||||
|
||||
- cache jitter, soft/hard TTL, cache-aside/single-flight/source bulkhead;
|
||||
- refresh-ahead와 probabilistic early refresh;
|
||||
- Redis Functions 배포와 program upgrade/rollback compatibility matrix;
|
||||
- health/metrics/TLS/ACL/secret/topology/eviction 검증;
|
||||
- distributed rate limit, idempotency, lease/fencing, session;
|
||||
- Sentinel runtime, Cluster production qualification, k3s/multi-node/failover/rotation,
|
||||
effective eviction/persistence attestation;
|
||||
- fenced coordination과 multi-process/pod session 및 L1/L2 distributed qualification;
|
||||
- Phase 1의 전체 acceptance와 R2/R3 승격 증거.
|
||||
|
||||
따라서 standalone runtime/string cache는 R1 evidence를 가지지만 Redis capability 전체 또는
|
||||
어떤 production topology도 R2가 아니다. raw-key Lua foundation과
|
||||
rate/idempotency/lease/session은 semantic composition이 없어 여전히 R0다.
|
||||
현재 registry의 cache, edge rate limit, request-replay idempotency, cache refresh soft lease,
|
||||
session card는 standalone promotion topology의 `implemented-candidate`다. fenced coordination만
|
||||
`not-implemented`다. `implemented-candidate`는 구현과 card-owned evidence lane을 뜻할 뿐 release
|
||||
selection이나 R2 qualification이 아니다. checked-in `selected` card가 0개이므로 Redis capability
|
||||
전체 또는 어떤 production topology에도 R2 release claim을 하지 않는다.
|
||||
|
||||
## 1. 설계 판정
|
||||
|
||||
설계 착수 당시 `adapter:outbound:cache-redis`는 실제 Redis client, connection, topology, TTL,
|
||||
codec, atomic program, failure semantics가 없는 R0 extension seam이었다. 2026-07-28 구현으로
|
||||
standalone managed Lettuce runtime과 semantic string cache는 R1까지 올라왔지만, topology,
|
||||
TLS/ACL, restart/fault/eviction evidence가 없으므로 여전히 production-ready adapter는 아니다.
|
||||
codec, atomic program, failure semantics가 없는 R0 extension seam이었다. 2026-07-30 현재 위 5개
|
||||
semantic provider는 standalone `implemented-candidate`이며 standalone TLS+named ACL과 bounded
|
||||
fault evidence도 있다. 그러나 selection, Sentinel/Cluster, multi-node/failover/rotation,
|
||||
effective eviction/persistence attestation과 R3 증거가 없으므로 production-ready/R2라는 단일
|
||||
label을 붙이지 않는다.
|
||||
|
||||
이번 설계는 다음 구조를 선택한다.
|
||||
|
||||
@@ -75,16 +100,16 @@ TLS/ACL, restart/fault/eviction evidence가 없으므로 여전히 production-re
|
||||
|
||||
| Capability | 현재 | 목표 |
|
||||
| --- | --- | --- |
|
||||
| Redis runtime | managed Lettuce standalone R1 + explicit external-client mode | Spring Data Redis + Lettuce 기반 typed runtime |
|
||||
| Cache | `Optional<String> get`, `void put` | typed region, TTL, negative/stale, invalidate, cache-aside |
|
||||
| Rate limit | inbound-web single-node fixed window | policy별 fixed/sliding/token/GCRA Redis provider |
|
||||
| Idempotency | JPA 전제, owner token 없음 | atomic claim, owner-safe complete, execution/replay TTL 분리 |
|
||||
| Lock | JDBC efficiency lock | Redis efficiency lease + 별도 fenced contract |
|
||||
| Session | JWT stateless 고정 | JWT 또는 isolated Redis Session의 명시적 profile |
|
||||
| Atomic helper | 없음 | versioned Function/Lua program registry |
|
||||
| Topology | 없음 | standalone, Sentinel, Cluster의 typed exclusive profile |
|
||||
| Failure | 모든 cache exception을 miss로 변환 | capability별 fail-open/closed/degraded/indeterminate |
|
||||
| CI | fake unit test | real Redis, topology, concurrency, failure, compatibility matrix |
|
||||
| Redis runtime | canonical role router와 managed/external Lettuce runtime, standalone candidate | Sentinel runtime과 Cluster production qualification |
|
||||
| Cache | standalone `implemented-candidate`; generation/soft lease/bounded L1과 TLS/ACL/fault lane | multi-process L1/L2와 HA/persistence/eviction attestation |
|
||||
| Rate limit | fixed/sliding-counter/token-bucket standalone `implemented-candidate` | HA topology, failover와 R3 evidence |
|
||||
| Idempotency | owner-safe Redis V2 standalone `implemented-candidate`; JDBC provider와 명시적 선택 | actual-used image/event evidence와 selected promotion |
|
||||
| Lock | Redis efficiency lease candidate; fenced coordination은 `not-implemented` | protected-resource stale fencing-token rejection |
|
||||
| Session | JWT와 isolated Redis Session profile; Redis는 standalone `implemented-candidate` | multi-process/pod와 failover/rotation qualification |
|
||||
| Atomic helper | versioned closed Lua catalog와 typed internal facade | Redis Functions upgrade/rollback matrix |
|
||||
| Topology | standalone candidate; Cluster code seam; Sentinel runtime 미구현 | Sentinel/Cluster/k3s multi-node qualification |
|
||||
| Failure | capability별 typed degraded/unavailable/indeterminate와 bounded fault lane | 실제 topology event chain과 persistence/restart evidence |
|
||||
| CI | strict registry matrix, real candidate lanes, sanitized artifact/reconciler | actual-used image attestation과 actual fault-event capture |
|
||||
|
||||
설계가 완료되었다는 뜻은 구현 계약과 단계가 결정되었다는 뜻이다. 현재 Redis runtime이
|
||||
production-ready가 되었다는 뜻은 아니다.
|
||||
@@ -145,7 +170,11 @@ production-ready가 되었다는 뜻은 아니다.
|
||||
표의 링크 대상보다 예시 YAML이나 migration alias가 우선하지 않는다. 상충하는 두 설정이
|
||||
존재하면 임의 precedence를 선택하지 않고 startup을 실패시킨다.
|
||||
|
||||
## 3. 증거 기반 현재 상태
|
||||
## 3. 설계 착수 당시 증거 기반 baseline
|
||||
|
||||
이 절 전체는 구현 전 repository를 조사한 2026-07-26 역사적 baseline이다. 아래의 “현재”는 그
|
||||
조사 시점을 가리키며 2026-07-30 구현 상태를 설명하지 않는다. 최신 구현/readiness truth는 §0,
|
||||
§1의 현재 열, checked-in `src/config/redis/readiness-cards.yaml`, Redis leaf README를 따른다.
|
||||
|
||||
### 3.1 실제 Redis client가 없다
|
||||
|
||||
@@ -5904,8 +5933,8 @@ indexed repository는 Cluster/node-specific event와 orphan index cleanup을 별
|
||||
최소 실제 topology:
|
||||
|
||||
- primary;
|
||||
- replica;
|
||||
- independent Sentinel quorum.
|
||||
- replica 2개;
|
||||
- 서로 다른 k3s node에 배치한 Sentinel 3개와 quorum 2.
|
||||
|
||||
test:
|
||||
|
||||
@@ -5921,6 +5950,260 @@ test:
|
||||
|
||||
단일 fake Sentinel endpoint로 HA를 증명하지 않는다.
|
||||
|
||||
#### 37.13.1 Sentinel discovery와 data runtime 분리
|
||||
|
||||
Sentinel discovery channel과 Redis data-node channel은 같은 Lettuce client/SSL context로
|
||||
합치지 않는다. 각각 독립된 named material과 lifecycle을 갖는다.
|
||||
|
||||
| Channel | 책임 | 허용 material |
|
||||
| --- | --- | --- |
|
||||
| Sentinel discovery | master name 조회와 quorum 관측 | Sentinel ACL username/password reference, Sentinel CA/trust, discovery timeout |
|
||||
| Redis data | capability command/program 실행 | data-node ACL username/password reference, data CA/trust, command/admission/drain timeout |
|
||||
|
||||
discovery는 다음 조건을 모두 만족할 때만 새 primary 후보를 반환한다.
|
||||
|
||||
- 구성된 Sentinel endpoint 최소 3개 중 2개 이상이 같은 master host/port를 보고한다;
|
||||
- 응답한 Sentinel 수와 동의 수가 각각 bounded deadline 안에서 기록된다;
|
||||
- master name이 exact configured name과 같다;
|
||||
- 반환 endpoint가 loopback, wildcard, unspecified address가 아니고 allowlisted deployment
|
||||
identity/member에 속한다;
|
||||
- TLS hostname/SAN 검증을 통과한다;
|
||||
- Sentinel credential 또는 trust를 data connection에, data material을 Sentinel connection에
|
||||
재사용하지 않는다.
|
||||
|
||||
한 Sentinel의 응답, 최초 응답 또는 DNS 문자열 일치만으로 primary를 바꾸지 않는다. discovery
|
||||
실패 detail에는 endpoint, username, secret reference/value, certificate subject를 남기지 않고
|
||||
sanitized reason과 동의 수만 남긴다.
|
||||
|
||||
#### 37.13.2 bounded rediscovery와 runtime swap
|
||||
|
||||
정상 polling은 bounded single-flight로 실행하며, write/read command의 topology failure가
|
||||
발생하면 같은 single-flight에 bounded immediate rediscovery를 요청한다. 새 primary가
|
||||
qualification을 통과하면:
|
||||
|
||||
1. 새 data runtime을 생성한다;
|
||||
2. version/program/semantic readiness를 검증한다;
|
||||
3. 기존 `RedisRoleCommandRouter`에 한 번만 install한다;
|
||||
4. 기존 runtime은 새 admission을 닫고 in-flight command를 bounded drain한다;
|
||||
5. drain timeout 뒤에는 강제 close하되 완료되지 않은 mutation을 성공/미실행으로 추정하지 않는다.
|
||||
|
||||
failover 직전 또는 도중의 mutation은 자동 replay하지 않는다. transport가 실행 여부를 증명하지
|
||||
못하면 capability가 `INDETERMINATE`를 반환하고, idempotency/session은 같은 operation token의
|
||||
inspect/reconcile 또는 재인증 경로를 사용한다. read-only command도 semantic contract가 허용하는
|
||||
경우에만 새 runtime에서 재시도한다.
|
||||
|
||||
`snapshot()`/readiness scrape는 정상 polling의 실행 엔진으로 사용하지 않는다. scrape나 command가
|
||||
없는 동안에도 primary 변경을 발견해야 하므로, active Sentinel role이 하나 이상일 때만 registry가
|
||||
다음 bounded poller를 소유한다.
|
||||
|
||||
- registry당 daemon worker 1개와 active Sentinel role당 fixed-delay task 1개만 만든다;
|
||||
- 기본 polling period는 30초이고 typed setting은 5초 이상 5분 이하만 허용한다;
|
||||
- scheduled poll과 command-failure trigger는 role별 같은 single-flight를 공유하며 한 role에
|
||||
discovery/install 작업은 최대 1개만 실행하거나 대기한다;
|
||||
- Standalone/Cluster만 선택되거나 Redis capability가 비활성이면 poller/thread/task를 0개 만든다;
|
||||
- close는 새 trigger를 거절하고 scheduled task를 취소한 뒤 worker를 bounded shutdown하며,
|
||||
close와 경합해 늦게 생성된 candidate는 install하지 않고 정확히 한 번 닫는다.
|
||||
|
||||
command failure signal은 route lease가 반환된 뒤 발행한다. connection/timeout/topology 계열의
|
||||
`UNAVAILABLE`만 immediate rediscovery를 요청하고, overload, ACL denial, validation/size rejection은
|
||||
요청하지 않는다. signal listener의 실패는 원래 command의 `NOT_APPLIED`/`INDETERMINATE` 판정을
|
||||
절대 덮어쓰지 않는다.
|
||||
|
||||
정상 poll은 Sentinel discovery credential/CA만 사용해 endpoint를 조회한다. 현재 route와 같은
|
||||
primary면 data credential/CA를 해석하거나 새 data connection을 열지 않는다. primary가 달라졌을
|
||||
때만 이미 quorum-approved/allowlisted 된 exact endpoint로 data candidate를 열어 TOCTOU 성격의
|
||||
이중 discovery를 피한다. route는 endpoint를 출력하지 않는 package-private identity와 monotonic
|
||||
generation token을 가진다. candidate qualification 중 다른 rotation이 먼저 완료되면 stale
|
||||
generation candidate를 닫고 install하지 않는다. 같은 identity도 candidate를 닫고 no-op 처리한다.
|
||||
|
||||
#### 37.13.3 replication 보장과 판정
|
||||
|
||||
Sentinel은 primary election을 제공하지만 asynchronous replication의 zero-data-loss를 보장하지
|
||||
않는다. qualification 환경은 correctness role에 `min-replicas-to-write`와 bounded
|
||||
`min-replicas-max-lag`를 설정하고, 중요한 mutation은 명시된 replica acknowledgement 정책을
|
||||
사용한다. 이 설정도 strong consistency나 cross-store exactly-once 증거가 아니다.
|
||||
|
||||
failover 판정은 다음을 구분한다.
|
||||
|
||||
- 응답과 요구된 replica acknowledgement가 확인된 mutation: 새 primary에서 보존되어야 한다;
|
||||
- response-only cut 또는 acknowledgement 결과를 확인할 수 없는 mutation:
|
||||
`INDETERMINATE`, blind retry 금지;
|
||||
- acknowledgement 전 명확한 connection/admission 실패: `NOT_APPLIED`가 wire evidence로
|
||||
증명되는 경우에만 미실행으로 판정한다.
|
||||
|
||||
#### 37.13.4 Sentinel-first R2 qualification lab
|
||||
|
||||
이번 Phase 5의 첫 실행 slice는 기존 host k3s를 변경하지 않는 disposable Multipass lab이다.
|
||||
|
||||
```text
|
||||
ca-redis-lab-server 2 CPU / 3 GiB / 12 GiB k3s server
|
||||
ca-redis-lab-agent-1 2 CPU / 2.5 GiB / 12 GiB k3s agent
|
||||
ca-redis-lab-agent-2 2 CPU / 2.5 GiB / 12 GiB k3s agent
|
||||
pod CIDR 10.52.0.0/16
|
||||
service CIDR 10.53.0.0/16
|
||||
kube context ca-redis-lab
|
||||
```
|
||||
|
||||
lab kubeconfig와 transient material/raw observation은 Gradle root의 ignored
|
||||
`src/build/redis-lab` 아래에만 쓰며 사용자의 default kubeconfig에 merge하거나 덮어쓰지 않는다.
|
||||
host 관측에는 default kubeconfig의 run-scoped copy와 시작 시점의 exact host context를
|
||||
사용하지만, fingerprint/CIDR 관측이 끝난 즉시 성공/실패와 무관하게 copy를 제거한다. 모든
|
||||
lab mutating command는 별도 lab kubeconfig와 `ca-redis-lab` context를 함께 요구한다.
|
||||
VM 이름은 위 exact allowlist만 허용한다. launch 전에 exact name을 run-owned state에
|
||||
`PENDING`으로 atomic 예약하고 성공 직후 `CREATED`로 승격한다. timeout, partial create,
|
||||
state 승격 실패는 이 run이 예약한 exact name만 delete/purge한다. global `multipass purge`,
|
||||
host `kubectl delete`, default-context write는 금지한다.
|
||||
|
||||
run-scoped rendered cloud-init은 secret이 아닌 exact `RUN_ID|VM_NAME` ownership marker를
|
||||
instance에 기록한다. cleanup/down은 bounded marker read가 state owner와 name 일치를
|
||||
증명할 때만 delete한다. launch timeout/error는 `RECONCILE` tombstone과 bounded late-create
|
||||
poll로 처리한다. instance가 끝까지 없거나 marker가 unreadable/mismatch면 외부 same-name
|
||||
instance를 추측해 삭제하지 않고 state를 유지한 채 fail-closed한다.
|
||||
|
||||
lifecycle 전체는 nonblocking exclusive lock과 run identity를 사용한다. direct `up`과
|
||||
`run` 모두 첫 launch 전에 emergency cleanup을 활성화하며 signal/concurrent invocation이
|
||||
다른 run의 state 또는 VM을 채택·삭제하지 못한다. `run -- <command>`에는 lifecycle lock file
|
||||
descriptor를 상속하지 않는다. K3s는 mutable installer를 pipe로 실행하지 않고 exact release
|
||||
URL/SHA-256을 repository에 pin한다. host download와 각 VM transfer 뒤 checksum/version을
|
||||
다시 확인한 후에만 start한다.
|
||||
기본 bounded external child도 lifecycle lock descriptor를 닫으며 lock acquisition만
|
||||
명시적인 keep-lock 경로를 사용한다.
|
||||
`run`의 inner `up` 성공과 user command 시작 사이에도 cleanup-required flag는 연속 유지되며,
|
||||
signal handler가 ownership을 0으로 보는 handoff gap을 허용하지 않는다.
|
||||
lab kubeconfig renderer는 one-cluster/context/user schema의 모든 identity-bearing key를
|
||||
generic count하며 duplicate/extra server, context cluster/user, item/name,
|
||||
current-context를 last-key-wins로 남기지 않고 fail-closed한다.
|
||||
|
||||
#### 37.13.4.1 lab lifecycle 완료 경계와 strict kubeconfig renderer
|
||||
|
||||
`Task 11.1A`는 하나의 리뷰 단위로 너무 많은 책임을 가졌으므로 다음 두 하위 작업으로 분리한다.
|
||||
|
||||
- `Task 11.1A-1`: VM 이름/소유권 marker, `PENDING|CREATED|RECONCILE` state, lock FD,
|
||||
signal/handoff cleanup, host fingerprint와 bounded external command를 소유한다.
|
||||
- `Task 11.1A-2`: pinned K3s admin kubeconfig의 strict validation과 lab 전용 rename/render만
|
||||
소유한다.
|
||||
|
||||
`11.1A-1` 코드는 `11.1A-2` 동안 동결한다. `11.1A-2`가 독립 테스트와 독립 리뷰를 통과하기
|
||||
전에는 부모 `11.1A`를 완료로 표시하지 않으며 VM 생성도 허용하지 않는다.
|
||||
|
||||
`11.1A-2`는 범용 YAML parser가 아니다. 입력은 pinned K3s가 생성하는 admin kubeconfig의
|
||||
canonical block-style 문서 하나로 제한한다. 별도 tracked
|
||||
`infra/redis-lab/lib/render-kubeconfig.awk`가 line/indentation/state allowlist를 적용하며,
|
||||
identity-bearing key를 찾는 denylist나 발견된 mutation별 정규식 패치를 사용하지 않는다.
|
||||
|
||||
허용 grammar는 다음을 모두 만족해야 한다.
|
||||
|
||||
- top-level `apiVersion`, `clusters`, `contexts`, `current-context`, `kind`, `preferences`,
|
||||
`users`는 canonical 순서와 exact spelling/indentation으로 한 번만 존재한다;
|
||||
- cluster/context/user list는 각각 한 항목만 가지며 identity는 모두 exact `default`다;
|
||||
- cluster는 exact loopback `server: https://127.0.0.1:6443`와 하나의
|
||||
`certificate-authority-data` scalar만 가진다;
|
||||
- context는 exact `cluster: default`, `user: default`와 optional single `namespace` scalar만
|
||||
가진다;
|
||||
- user는 하나의 `client-certificate-data`와 `client-key-data` scalar만 가진다;
|
||||
- `preferences: {}`만 유일한 flow collection 예외다. 그 밖의 `{}`, `[]`, quoted/tagged/
|
||||
explicit key, anchor, alias, merge key, tab, CRLF, YAML document marker, unknown key,
|
||||
duplicate/reordered identity, trailing content는 fail-closed한다;
|
||||
- source `server`, cluster/context/user name과 current-context만 변환한다. CA/client material,
|
||||
namespace와 그 밖의 허용 scalar는 byte-preserving pass-through다;
|
||||
- renderer source 자체와 destination의 canonical parent/symlink/permission 계약을 lifecycle
|
||||
static validation에 포함한다. validation 또는 render 실패 시 destination을 제거하고
|
||||
constant sanitized failure만 출력한다.
|
||||
|
||||
정상 fixture는 pinned K3s admin kubeconfig의 certificate-data shape를 사용한다. negative
|
||||
mutation은 duplicate/extra identity뿐 아니라 canonical item 아래의 sibling
|
||||
`cluster : {...}`, `context : {...}`, whitespace-before-colon, flow collection, quoted/tagged/
|
||||
anchor/alias/merge, unknown/reordered/missing key를 포함한다. 모든 실패는 lab `kubectl` 전에
|
||||
발생하고 현재 invocation이 marker로 증명한 VM만 cleanup하며 prior
|
||||
`CREATED|RECONCILE` state는 byte-for-byte 보존한다.
|
||||
|
||||
tracked `infra/redis-lab`에는 lifecycle script, cloud-init template, Redis/Sentinel config
|
||||
template, Kubernetes manifest와 secret 없는 contract test만 둔다. 실행 시 생성하는 k3s token,
|
||||
ACL password, data/Sentinel/untrusted CA와 private key, rendered Secret/config, raw observation은
|
||||
`umask 077`인 transient directory에만 둔다. `redis-cli --pass`, tracked PEM/Secret data,
|
||||
`hostPath`/`hostNetwork`/privileged/NodePort/LoadBalancer는 사용하지 않는다.
|
||||
|
||||
host isolation은 preflight/postflight의 canonical projection을 비교한다. default kubeconfig
|
||||
digest, current context/API, sorted node/providerID/podCIDR, controller replica, Service NodePort,
|
||||
host interface/route CIDR와 Multipass inventory가 대상이다. host service CIDR은 현재 할당된
|
||||
ClusterIP만 보고 추측하지 않고, 명시적으로 검증한 input 또는 신뢰할 수 있는 host 설정에서
|
||||
읽는다. 외부 명령과 exact 3-node Ready 대기는 bounded다. 불일치 시 qualification을
|
||||
실패시키되 script가 host 상태를 추측해 되돌리려고 mutate하지 않는다.
|
||||
|
||||
workload는 Redis primary 1 + replica 2, Sentinel 3/quorum 2를 서로 다른 node에 배치한다.
|
||||
data와 Sentinel은 stable ordinal/headless DNS가 필요한 별도 StatefulSet이며
|
||||
`kubernetes.io/hostname` required anti-affinity와 `maxSkew=1/DoNotSchedule` topology spread를
|
||||
사용하고 `podManagementPolicy: Parallel`을 명시한다. data는 PVC와 AOF
|
||||
`appendfsync everysec`를 사용한다. Sentinel config는 discovery/failover 시 rewrite되므로
|
||||
bootstrap 원본을 pod별 writable PVC config로 최초 1회 atomic init-copy하되 restart 때 이미
|
||||
존재하는 rewritten config를 덮어쓰지 않는다. 비어 있거나 손상된 기존 config도 자동으로
|
||||
덮지 않고 startup을 실패시켜 증거를 보존한다.
|
||||
|
||||
data/Sentinel plaintext port는 0이며 TLS port만 연다. `tls-replication yes`, hostname
|
||||
resolution/announcement와 stable DNS SAN을 사용한다. data plane과 Sentinel plane의 CA/leaf
|
||||
material은 분리하며 peer 연결에 필요한 root만 explicit trust bundle에 포함한다. ACL은
|
||||
application data, replica, Sentinel-to-data, Sentinel peer, application Sentinel discovery
|
||||
identity로 나눈다. Redis data ACL과 Sentinel ACL은 별도 template/projection이며 plane
|
||||
identity를 서로 노출하지 않는다. default user는 off이며 application/data/discovery
|
||||
identity에는 `+@all`, `allkeys`, `allchannels`를 주지 않는다. replica는
|
||||
`+psync +replconf +ping`, Sentinel-to-data identity는 Sentinel control에 필요한 최소
|
||||
command/channel set만 가진다.
|
||||
|
||||
exec probe를 사용하고 default-deny NetworkPolicy 뒤 data 6379, Sentinel 26379, kube-dns,
|
||||
exact qualification/application pod selector만 허용한다. data/Sentinel PDB는 각각
|
||||
`minAvailable: 2`이며 non-root, read-only root filesystem, privilege-escalation false,
|
||||
capability drop ALL, seccomp RuntimeDefault, requests/limits를 요구한다. `hostPath`,
|
||||
host namespaces, privileged, NodePort/LoadBalancer와 tracked Secret/PEM은 금지한다.
|
||||
|
||||
정적 lifecycle contract와 manifest/security contract는 VM 없이 blocking check에서 검증하고,
|
||||
한 필드씩 제거/변조하는 mutation-negative fixture로 실제 방어력을 확인한다. 이 정적 통과는
|
||||
TLS handshake, ACL authorization, CNI enforcement, scheduling/failover의 실행 증거가 아니다.
|
||||
shell contract는 별도 fixture repository만 사용하며 actual `src/build/redis-lab` state를
|
||||
byte-for-byte 보존한다. fake PATH는 explicit safe wrapper 외 모든 명령을 fail-closed한다.
|
||||
live lab에서는 TLS/ACL negative test, `SENTINEL CKQUORUM`, writable config rewrite/restart,
|
||||
exact 3 Ready placement, PDB/NetworkPolicy enforcement와 image ID/digest를 별도로 검증한다.
|
||||
Redis image는 `src/gradle/redis-test-images.properties`의 `redis.minimum.image` exact
|
||||
tag+digest를 사용한다.
|
||||
|
||||
ordinal bootstrap은 최초 `redis-data-0` primary와 두 replica만 정적으로 증명한다.
|
||||
failover 동안 죽어 있던 old primary가 재합류할 때 readiness가 stale direct write를 허용하지
|
||||
않고 새 primary의 replica로 수렴하는지는 live gate다. PDB 선언은 voluntary eviction
|
||||
제약일 뿐 node/AZ failure 증거가 아니다.
|
||||
|
||||
k3s control-plane HA, physical host/AZ failure, Redis Cluster는 이 lab의 증거가 아니다.
|
||||
hosted GitHub Actions에서는 Multipass를 설치하거나 실행하지 않는다. 실제 lab qualification은
|
||||
trusted dedicated runner 또는 local explicit execution에서만 허용한다. 외부 PR 코드를
|
||||
self-hosted lab에서 실행하지 않는다.
|
||||
|
||||
초기 test budget은 운영 SLA가 아니라 bounded regression limit이다.
|
||||
|
||||
- Sentinel election: 60초 이내;
|
||||
- client rediscovery와 runtime swap: election 뒤 추가 30초 이내;
|
||||
- required semantic readiness 복구: fault injection 뒤 총 90초 이내.
|
||||
|
||||
실제 측정값을 evidence timeline에 기록하며 limit만 기록한 문서는 증거가 아니다.
|
||||
|
||||
#### 37.13.5 Sentinel-first capability acceptance
|
||||
|
||||
이 slice는 correctness-sensitive cross-pod state를 우선 검증한다.
|
||||
|
||||
- edge rate limit: failover 전 quota state가 조용히 reset되지 않고 evaluation replay가 일관된다;
|
||||
- request-replay idempotency: claim/start/renew/complete와 terminal replay가 owner-safe하며
|
||||
불확실 mutation은 중복 실행하지 않는다;
|
||||
- Redis session: create/read/touch/rotate/revoke가 서로 다른 application pod에서 보이고,
|
||||
failover 뒤 confirmed state가 유지되며 stale session이 부활하지 않는다;
|
||||
- cache refresh soft lease와 optional cache는 공통 runtime 회귀를 확인하되 이 slice만으로
|
||||
Cluster scaling 또는 distributed L1 invalidation R2를 주장하지 않는다.
|
||||
|
||||
fault 순서는 baseline qualification 뒤 current primary pod를 kill하고 readiness unavailable,
|
||||
Sentinel quorum election, client rediscovery, runtime swap/drain, semantic readiness recovery를
|
||||
실제 timestamp로 수집한다. old primary는 replica로 재합류해야 하고, recovery 뒤 모든 actor가
|
||||
같은 runtime generation을 관측해야 한다.
|
||||
|
||||
evidence bundle은 실제 실행 image digest/image ID, config/program digest, fault/election/recovery
|
||||
timeline, capability별 outcome/certainty, sanitized Kubernetes/Sentinel observation, lab teardown
|
||||
결과를 포함한다. manifest의 `NOT_CAPTURED`를 문자열로 바꾸는 것만으로 증거를 만들 수 없다.
|
||||
|
||||
### 37.14 Cluster topology
|
||||
|
||||
최소 multi-primary Cluster와 replica에서:
|
||||
@@ -6115,20 +6398,9 @@ canonical card ID와 Gradle task mapping:
|
||||
registry key, capability descriptor ID, `card-<id>` tag, evidence artifact의 card ID는 이 표와 byte-for-byte
|
||||
같아야 한다. short alias를 허용하지 않는다.
|
||||
|
||||
```yaml
|
||||
cards:
|
||||
redis-cache:
|
||||
state: selected # selected | implemented-candidate | not-implemented
|
||||
selected-topology: sentinel # standalone | sentinel | cluster
|
||||
required-evidence:
|
||||
- standalone
|
||||
- security
|
||||
- fault
|
||||
- compatibility
|
||||
- selected-topology
|
||||
redis-session:
|
||||
state: not-implemented
|
||||
```
|
||||
현재 card 상태와 topology/evidence는 이 문서에 복제하지 않으며
|
||||
`src/config/redis/readiness-cards.yaml`만을 따른다. 현재 `selected` card는 없으며,
|
||||
`implemented-candidate`는 release selection 또는 R2 qualification을 뜻하지 않는다.
|
||||
|
||||
각 `redis<Card>Readiness` task는 이 registry의 해당 card tag와 required evidence tag의 교집합을
|
||||
실행하고, category마다 test count > 0, 성공 artifact, image/program/config digest를 요구한다.
|
||||
@@ -6224,10 +6496,14 @@ nightly `redis-all-candidates`는 `redisAllImplementedCandidates`를 실행한
|
||||
품질 신호/승격 blocker지만 현재 selected card의 이미 존재하는 release evidence를 다른 card
|
||||
미구현 때문에 자동 취소하지 않는다.
|
||||
|
||||
각 job은 JUnit XML/HTML, container logs, sanitized topology/fault timeline,
|
||||
`program-set.json`/digest, effective capability card, image digest attestation을 artifact로 올린다.
|
||||
secret, raw Redis key/value, session/idempotency token은 artifact에 포함하지 않는다. PR artifact
|
||||
retention은 짧게, release evidence는 조직의 audit retention 정책에 맞춘다.
|
||||
각 job은 `build/redis-evidence` 아래에서 allowlist schema로 다시 생성한 bounded manifest,
|
||||
capability card, sanitized test summary만 artifact로 올린다. Gradle의 raw JUnit XML/HTML,
|
||||
`system-out`/`system-err`, stack trace, container log/inspect, TLS/ACL fixture material은 업로드하지
|
||||
않는다. 실제 사용 image attestation과 실제 topology/fault event chain을 수집하지 못한 현재
|
||||
artifact는 각각 `NOT_CAPTURED`와 `releaseQualification=NOT_CLAIMED`를 기록하며, reconciler는
|
||||
이 상태의 future `selected` 승격을 실패시킨다. secret/reference value, raw endpoint/key/value,
|
||||
session/idempotency/lease token은 artifact에 포함하지 않는다. Candidate artifact retention은
|
||||
짧게, 실제 release evidence는 조직의 audit retention 정책에 맞춘다.
|
||||
|
||||
### 37.24 no silent skip
|
||||
|
||||
@@ -6526,6 +6802,36 @@ Acceptance:
|
||||
- no silent skip;
|
||||
- program/ACL/schema conformance.
|
||||
|
||||
#### Phase 5A — Sentinel-first R2 qualification slice
|
||||
|
||||
Phase 5 전체를 한 번에 구현하지 않는다. 먼저 §37.13의 disposable 3-node k3s Sentinel 환경에서
|
||||
다음 순서로 진행한다.
|
||||
|
||||
1. lab lifecycle/preflight/host-isolation contract를 테스트 우선으로 고정한다;
|
||||
2. Sentinel discovery와 Redis data runtime을 별도 auth/trust/lifecycle로 구현한다;
|
||||
3. quorum-consistent discovery, bounded rediscovery, qualified runtime swap와 bounded drain을
|
||||
구현한다;
|
||||
4. security positive/negative test 후 rate limit, idempotency, session의 multi-pod 정상 경로를
|
||||
실행한다;
|
||||
5. primary kill과 response-loss fault를 주입하고 capability invariant와 `INDETERMINATE`
|
||||
semantics를 검증한다;
|
||||
6. image/fault timeline을 실제 관측에서 생성하고 sanitizer/reconciler를 통과시킨다;
|
||||
7. focused/full Gradle verification과 독립 review를 마친 뒤 이 slice에서 멈춘다.
|
||||
|
||||
이번 slice에 포함하지 않는 항목:
|
||||
|
||||
- Redis Cluster와 Cluster cache scaling;
|
||||
- fenced coordination;
|
||||
- R3 capacity soak/long chaos/reshard;
|
||||
- k3s control-plane HA, physical host/AZ failure;
|
||||
- full credential/certificate rotation drill;
|
||||
- optional cache의 Sentinel release promotion.
|
||||
|
||||
이번 slice의 agent-side 종료 상태는 `R2-ready candidate`다. repository가 human-only commit
|
||||
policy를 사용하므로 clean committed source와 실제 remote GitHub Actions evidence는 사람이
|
||||
수행하는 최종 promotion gate다. 이 두 증거가 없으면 readiness card를 `selected`로 바꾸거나
|
||||
R2라고 표시하지 않는다.
|
||||
|
||||
### Phase 6 — R3와 split review
|
||||
|
||||
- actual Cluster reshard/failover;
|
||||
@@ -6573,6 +6879,28 @@ Acceptance:
|
||||
- runbook/capability card;
|
||||
- LLM Wiki capture.
|
||||
|
||||
### 40.2 Sentinel-first slice 종료 게이트
|
||||
|
||||
§37.13과 Phase 5A의 작업은 아래가 모두 충족된 경우에만 `R2-ready candidate`로 종료한다.
|
||||
|
||||
- exact VM inventory와 dedicated kubeconfig로 lab create/verify/destroy가 반복 가능하다;
|
||||
- host k3s context, node, workload와 default kubeconfig의 전/후 fingerprint가 같다;
|
||||
- Sentinel discovery와 Redis data auth/trust가 분리되고 negative security test가 통과한다;
|
||||
- primary kill 뒤 quorum election, qualified runtime swap, bounded drain과 semantic readiness
|
||||
recovery의 실제 timeline이 있다;
|
||||
- rate limit, idempotency, session을 서로 다른 pod에서 검증하고 failover 뒤 invariant가
|
||||
유지된다;
|
||||
- confirmed acknowledgement와 `INDETERMINATE`를 구분하며 blind mutation replay가 없다;
|
||||
- actual image/config/program digest와 sanitized evidence가 reconciler를 통과한다;
|
||||
- focused test, Redis readiness 관련 task, repository `test`/`check`, architecture/env/public-path
|
||||
gate와 독립 review가 통과한다;
|
||||
- exact allowlist VM teardown과 lab resource 정리 결과가 기록된다.
|
||||
|
||||
위 조건은 clean committed source와 실제 remote CI를 대신하지 않는다. 두 최종 promotion
|
||||
증거가 없으면 card 상태는 `implemented-candidate`, `releaseQualification=NOT_CLAIMED`를
|
||||
유지한다. 종료 뒤 Redis Cluster/R3/fenced coordination 또는 fileserver/HTTP client로 자동으로
|
||||
넘어가지 않고 다음 우선순위를 다시 결정한다.
|
||||
|
||||
R3는 추가로:
|
||||
|
||||
- failover/partition;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# HTTP Client Production Capability Deep Design
|
||||
|
||||
- 작성일: 2026-07-27
|
||||
- 상태: 상세 설계 완료, Phase 0/1 기반 및 legacy deadline R1 구현, R2 미구현
|
||||
- 상태: 상세 설계 완료, Phase 0/1 기반·legacy deadline R1·canonical zero-binding 구현, R2 미구현
|
||||
- 기준: Java 21, Spring Boot 4.0.0, Gradle 멀티모듈 Clean Architecture
|
||||
- 대상 leaf: `adapter-outbound-httpclient`
|
||||
- 구현 추적: typed operation/target foundation, legacy JDK 안전 결함과 active logical deadline
|
||||
@@ -28,11 +28,16 @@
|
||||
- client별 bounded live-worker admission, non-cooperative worker의 slot retention, shutdown 시
|
||||
active task cancellation과 신규 admission 차단;
|
||||
- worker MDC 복사/정리와 retry ThreadLocal lifecycle 정렬.
|
||||
- strict canonical expected-state/binding/provider map binder와 exact provider/destination/catalog
|
||||
resolver;
|
||||
- `DISABLED_VERIFIED` descriptor와 zero-binding HTTP runtime resource 0 composition;
|
||||
- `httpclient-static-buffered=NOT_IMPLEMENTED` fail-closed ACTIVE admission;
|
||||
- legacy settings/configuration의 global Spring scan 분리와 explicit migration binder.
|
||||
|
||||
아직 구현되지 않은 범위:
|
||||
|
||||
- application feature-specific production port와 실제 upstream anti-corruption adapter;
|
||||
- canonical binding/expected-state/full profile tuple/card registry와 zero-binding resource 0 계약;
|
||||
- full compatibility profile tuple/scenario registry와 release-eligible readiness evidence;
|
||||
- Apache HC5 pool/acquire/lifetime/idle provider;
|
||||
- Apache engine phase별 deadline 전달, wire hard cancellation과 connection quarantine;
|
||||
- DNS/address/SSRF/TLS/mTLS/proxy/auth/secret lifecycle;
|
||||
@@ -42,7 +47,8 @@
|
||||
|
||||
따라서 현재 `OutboundHttpClient`는 migration용 JDK R1 이하 facade이며 HTTP capability R2가 아니다.
|
||||
legacy 실행 경로는 active logical deadline을 사용하지만 operation catalog와 engine phase
|
||||
deadline을 아직 사용하지 않는다. 이 단면만으로 hard cancellation이나 R2를 주장하지 않는다.
|
||||
deadline을 아직 사용하지 않는다. Canonical ACTIVE도 현재 `NOT_IMPLEMENTED` card에서 실패한다.
|
||||
이 단면만으로 hard cancellation이나 R2를 주장하지 않는다.
|
||||
|
||||
## 1. 설계 판정
|
||||
|
||||
@@ -147,11 +153,12 @@ production capability는 아니다.
|
||||
dependency별 configuration에서 `baseline(...)`을 직접 호출하도록 안내한다. repository 전체에서
|
||||
이를 호출하는 production consumer는 없다.
|
||||
|
||||
다만 “binding 0개”가 HTTP 관련 bean 0개라는 뜻은 아니다. Component scan이 이 configuration을
|
||||
읽으면 `OutboundHttpSettings`, shutdown guard, `RestClient`/builder 차단 BeanPostProcessor,
|
||||
error mapper, logger와 retry policy 같은 global infrastructure bean은 생성된다. Named
|
||||
client/semantic-port binding은 없는데 required global timeout 설정과 전역 부작용은 존재하는
|
||||
비대칭 상태다.
|
||||
초기 조사 시점에는 component scan이 `OutboundHttpSettings`, shutdown guard,
|
||||
`RestClient`/builder 차단 BeanPostProcessor, error mapper, logger와 retry policy를 생성해
|
||||
“binding 0개”와 “HTTP resource 0개”가 일치하지 않았다. Phase 1 구현에서 이 결함은 폐쇄됐다.
|
||||
현재 settings와 두 legacy configuration은 global scan 대상이 아니며 canonical composition은
|
||||
immutable configuration, registry, resolver와 sanitized `DISABLED_VERIFIED` descriptor만 만든다.
|
||||
기본 `application.yml`과 `application-test.yml`도 legacy `app.outbound.http.*`를 선언하지 않는다.
|
||||
|
||||
다만 sample에는 이미 다음 seam이 있다.
|
||||
|
||||
@@ -4893,6 +4900,11 @@ inbound/use-case budget
|
||||
|
||||
## 33. Configuration design
|
||||
|
||||
2026-07-28 구현 단면은 canonical expected-state/binding/provider map의 strict binding, exact
|
||||
provider/destination/code-owned catalog resolution과 `httpclient-static-buffered` card derivation까지
|
||||
포함한다. 아래 full provider tuple의 pool/security/TLS/auth 필드는 아직 bind/runtime model로
|
||||
구현되지 않았다.
|
||||
|
||||
### 33.1 Canonical activation shape
|
||||
|
||||
상위 capability platform과 같은 canonical prefix를 사용한다.
|
||||
@@ -5229,7 +5241,15 @@ Base URI, proxy endpoint, SSL bundle/secret reference 변경은 운영 영향이
|
||||
|
||||
### 33.7 Legacy migration
|
||||
|
||||
현재 `app.outbound.http.*`는 migration-only alias다.
|
||||
`app.outbound.http.*`는 canonical application configuration에 포함되지 않는 migration-only
|
||||
입력이다.
|
||||
|
||||
현재 구현은 global `@ConfigurationPropertiesScan`을 제거하고
|
||||
`OutboundHttpSettings.bindLegacy(Binder)`/직접 생성자만 남겼다. Canonical composition은
|
||||
expected state가 DISABLED여도 legacy property가 하나라도 보이면 silent no-op 대신
|
||||
fail-closed한다. Legacy fork는 canonical composition 밖에서 migration binder와 configuration을
|
||||
명시적으로 import해야 한다. 아래 deprecation warning, one-destination conversion,
|
||||
release-window removal은 후속 migration 단계다.
|
||||
|
||||
1. legacy만 있으면 deprecation warning과 함께 immutable legacy settings로 변환;
|
||||
2. canonical과 legacy가 동시에 있으면 값이 같아도 startup failure;
|
||||
@@ -5257,6 +5277,10 @@ Application은 adapter type, `RestClient`, Apache type을 알지 못한다.
|
||||
|
||||
### 34.2 Zero-binding contract
|
||||
|
||||
이 절의 resource 0 계약은 `HttpClientCompositionConfigTest`와
|
||||
`OptionalAdapterBeanGatingTest`로 구현됐다. 기본 composition은 inert registry/resolver/descriptor
|
||||
외에 HTTP runtime bean을 만들지 않으며 `DISABLED_VERIFIED`만 게시한다.
|
||||
|
||||
Binding이 없으면 다음이 모두 0개여야 한다.
|
||||
|
||||
- engine client와 connection manager;
|
||||
|
||||
+141
-10
@@ -1,7 +1,7 @@
|
||||
# Fileserver R2 Control Plane and Provider Selection Design
|
||||
|
||||
- Date: 2026-07-28
|
||||
- Status: 승인된 설계, 구현 전
|
||||
- Status: 구현·전체 repository gate·독립 spec/quality review 완료
|
||||
- Scope: provider-neutral R2 control plane, explicit destination/provider selection, first
|
||||
`local-persistent` qualification provider
|
||||
- Parent:
|
||||
@@ -106,7 +106,9 @@ FILE_AND_DIRECTORY_SYNC
|
||||
fsr1.<route-token>.<file-id>.<check-digits>
|
||||
```
|
||||
|
||||
- `route-token`: startup에서 생성된 bounded destination route allowlist 값;
|
||||
- `route-token`: destination binding의 canonical policy digest에서 재시작 안정적으로 파생한
|
||||
bounded route allowlist 값. 형식은 `r` + digest의 첫 31 lowercase hex이며 startup에서 token
|
||||
collision을 거부한다;
|
||||
- `file-id`: CSPRNG 128-bit 이상;
|
||||
- `check-digits`: accidental truncation/corruption 검출;
|
||||
- provider locator, operation ID, tenant/user ID, host/path는 포함하지 않는다.
|
||||
@@ -146,6 +148,9 @@ app:
|
||||
|
||||
- `enabled=true`이면 destination과 provider가 각각 하나 이상 필요하다.
|
||||
- 모든 destination은 존재하는 provider 하나를 참조한다.
|
||||
- provider ID별로 provider/control/payload runtime을 정확히 하나만 만들며 같은 provider를
|
||||
참조하는 destination은 그 인스턴스를 공유한다. 서로 다른 provider ID가 같은 normalized
|
||||
root를 가리키면 동일 control namespace의 이중 소유가 되므로 startup에서 거부한다.
|
||||
- request destination에 binding이 없으면 producer 호출 전에 실패한다.
|
||||
- provider type의 기본값은 없다.
|
||||
- `local-persistent` root는 absolute, existing, pre-provisioned directory여야 한다.
|
||||
@@ -156,12 +161,34 @@ app:
|
||||
- container ephemeral 경로를 위한 `local-dev`는 별도 후속 profile이다. production 설정과
|
||||
같은 guarantee를 공유하지 않는다.
|
||||
- 기존 `ca-skeleton.fileserver.*`는 R1/legacy compatibility selector로만 남는다. 새 R2 설정과
|
||||
동시에 활성화되면 startup을 실패시킨다. 암묵 migration이나 precedence를 두지 않는다.
|
||||
동시에 활성화되면 어느 쪽 filesystem 초기화보다 먼저 startup을 실패시킨다. 양쪽 bean
|
||||
factory가 같은 ambiguity validator를 호출해 Spring bean 생성 순서에 의존하지 않으며, 암묵
|
||||
migration이나 conditional precedence를 두지 않는다.
|
||||
- R2 settings는 unknown field를 거부해 provider/destination 키 오타를 silent fallback으로
|
||||
취급하지 않는다.
|
||||
|
||||
## 7. Startup capability compilation
|
||||
|
||||
application traffic을 받기 전에 destination별 effective descriptor를 한 번 compile한다.
|
||||
|
||||
Descriptor compilation과 first reservation은 새 설정 키 없이 같은 canonical digest helper를
|
||||
사용한다.
|
||||
|
||||
- startup descriptor는 destination ID, provider ID, limits, required guarantees,
|
||||
format/encoder revision을 length-prefixed canonical encoding으로 직렬화한
|
||||
`effectivePolicyDigest`를 freeze한다;
|
||||
- ordered schema ID/version/column contract를 같은 canonical encoding 규칙으로 계산하는
|
||||
request별 `schemaDigest`는 first reservation에서 계산한다;
|
||||
- startup descriptor는 format/encoder revision과 canonical options의 `formatPolicyDigest`를
|
||||
freeze한다;
|
||||
- `r` + `effectivePolicyDigest`의 첫 31 lowercase hex로 만든 32-character deterministic route
|
||||
token.
|
||||
|
||||
문자열 단순 연결이나 JVM/JSON map iteration order에 digest를 의존시키지 않는다. 같은 startup
|
||||
allowlist 안에서 route token이 충돌하면 더 긴 prefix로 임의 복구하지 않고 startup을 실패시킨다.
|
||||
기존 operation은 journal에 freeze된 revision/digest/token으로만 복구하며 현재 설정으로 조용히
|
||||
재해석하지 않는다.
|
||||
|
||||
`local-persistent`는 다음을 모두 검증한다.
|
||||
|
||||
1. root와 모든 ancestor가 symbolic link가 아니다.
|
||||
@@ -206,7 +233,9 @@ data/<prefix>/<generated-file-name>
|
||||
```
|
||||
|
||||
모든 locator는 validated single segment 또는 adapter가 생성한 bounded relative segment다.
|
||||
Caller path를 받지 않는다.
|
||||
Caller path를 받지 않는다. Manifest/reference의 `internalLocator`는 generated filename 한
|
||||
segment만 저장하고, data shard는 `fileId`의 첫 두 hex에서 파생한다. 따라서 실제 lookup은
|
||||
`data/<file-id-prefix>/<internalLocator>`이며 control record에 slash를 저장하지 않는다.
|
||||
|
||||
### 8.1 Operation journal v2
|
||||
|
||||
@@ -267,6 +296,21 @@ relative locator로 direct lookup한다. Directory scan은 receipt restoration
|
||||
|
||||
순서로 갱신한다. 낮은 revision, fingerprint mismatch, newer schema는 자동 덮어쓰지 않는다.
|
||||
|
||||
Operation schema v2는 별도 `formatPolicyDigest` snapshot을 저장하지 않으므로 recovery는 저장된
|
||||
`effectivePolicyRevision`과 `effectivePolicyDigest`가 현재 compiled destination과 정확히 같을
|
||||
때만 현재 format-policy digest를 사용한다. Encoder/policy 변경으로 digest가 달라지면 과거
|
||||
format을 추정하지 않고 indeterminate로 중단한다. 여러 format revision에 대한 forward
|
||||
recovery는 non-secret policy snapshot을 포함하는 후속 operation schema에서만 지원한다.
|
||||
|
||||
Operation direct lookup은 같은 secure relative read에서 schema를 typed dispatch한다. Schema v2는
|
||||
현재 R2 record로만 decode/write하고, schema v1은 strict UTF-8 decode 후 canonical v1 re-encode
|
||||
byte equality를 만족하는 terminal compatibility record만 read-only로 반환한다. Unknown/newer
|
||||
schema, malformed UTF-8, non-canonical v1은 absent로 취급하지 않는다.
|
||||
|
||||
Crash qualification을 위해 control-plane fault context는 package-private로 record kind,
|
||||
record identity, 해당하는 경우 operation state/revision, force boundary를 함께 전달한다.
|
||||
Production 기본 callback은 no-op이며 runtime 설정이나 public bean으로 노출하지 않는다.
|
||||
|
||||
## 9. Publication ordering
|
||||
|
||||
```text
|
||||
@@ -290,6 +334,18 @@ J-PUBLISHED
|
||||
- terminal journal force 전에는 receipt를 반환하지 않는다.
|
||||
- target collision, digest mismatch 또는 root identity change는 자동 overwrite하지 않는다.
|
||||
- final data가 있어도 manifest/reference가 없으면 아직 terminal success가 아니다.
|
||||
- staging/data shard 생성, stage force, stable no-follow read/digest, exact delete는
|
||||
package-private `PayloadOperations`를 통해 `SecureDirectoryStream` 상대 연산으로 수행한다.
|
||||
Portable relative primitive가 없는 hard-link와 directory force만 private-owner boundary 안에서
|
||||
root/directory/file identity pre/post 검증으로 감싼다.
|
||||
- hard-link 뒤 journal 갱신 전에 중단된 `SEALED + matching data` 복구는 기존 data shard를 다시
|
||||
identity 검증하고 directory force한 뒤에만 `DATA_PUBLISHED`로 전이한다. 이미 존재하는 data를
|
||||
overwrite-capable publication 경로에 다시 넣지 않는다.
|
||||
- `WRITING` 저장 뒤 producer 또는 stage/write가 실패하면 partial stage를 exact cleanup하고
|
||||
unsealed `QUARANTINED` evidence를 남긴다. 원래 producer exception은 보존하고 cleanup/control
|
||||
failure는 suppressed로 연결한다. Retry 진입 시 기존 `WRITING` 또는 unsealed
|
||||
`QUARANTINED`가 보이면 producer를 다시 호출하지 않고 indeterminate/quarantine으로
|
||||
fail-closed한다.
|
||||
|
||||
## 10. Deterministic recovery
|
||||
|
||||
@@ -303,7 +359,9 @@ Recovery는 operation ID direct lookup으로 실행하며 startup full scan에
|
||||
| DATA_PUBLISHED + matching data | manifest publication 재개 |
|
||||
| MANIFEST_PUBLISHED + matching manifest/data | reference publication 재개 |
|
||||
| REFERENCE_PUBLISHED + all matching | terminal journal 완성 |
|
||||
| data digest mismatch | `QUARANTINED`, integrity failure |
|
||||
| non-terminal data/manifest/reference digest mismatch | `QUARANTINED`, integrity failure |
|
||||
| `PUBLISHED` artifact/metadata/receipt mismatch | terminal journal과 artifacts를 불변 보존하고 typed integrity/indeterminate |
|
||||
| required manifest/reference/data 누락 | 성공 복원 금지, fail-closed indeterminate/quarantine |
|
||||
| marker/manifest/reference schema newer | 보존 후 fail-fast/quarantine |
|
||||
| fingerprint conflict | typed conflict, 기존 artifact 보존 |
|
||||
| root/mount identity change | indeterminate, write/recovery 중단 |
|
||||
@@ -317,12 +375,35 @@ matching data + private manifest + reference
|
||||
> in-memory state
|
||||
```
|
||||
|
||||
모순이 있으면 임의 성공이나 삭제 대신 quarantine evidence를 기록한다.
|
||||
모순이 있으면 임의 성공이나 삭제를 하지 않는다. Non-terminal operation은 기존 operation
|
||||
journal을 `QUARANTINED`로 전이할 수 있다. 이미 `PUBLISHED`인 operation은 terminal
|
||||
receipt snapshot을 지우거나 journal을 덮지 않고 관련 data/manifest/reference도 보존한 채 typed
|
||||
integrity/indeterminate로 실패한다. 별도 immutable quarantine incident record는 후속 설계 전까지
|
||||
가정하지 않는다.
|
||||
|
||||
Recovery verifier는 operation, incoming request, data, manifest, reference, receipt snapshot의
|
||||
identity/digest/locator/count/time/guarantee를 모두 교차검증한다. Terminal receipt는 verified
|
||||
manifest/reference에서 재구성한 expected receipt와 전체 equality가 확인될 때만 반환한다.
|
||||
Operation record의 일부 필드만 맞거나 durability/publication guarantee, file version,
|
||||
format/media/charset가 다르면 terminal success가 아니다. Crash 뒤 먼저 발견한 immutable
|
||||
manifest/reference의 verified `publishedAt`은 새 clock 값으로 덮지 않고 recovery context로
|
||||
재사용한다. 새 attempt에만 현재 configured maximum을 적용하고, sealed recovery artifact는
|
||||
operation에 freeze된 exact byte size로 bounded inspection한다. Stage와 data가 함께 있으면
|
||||
digest equality만이 아니라 stable file key가 같은 hard-link인지 확인한 뒤에만 stage를
|
||||
exact-delete한다.
|
||||
|
||||
## 11. Compatibility
|
||||
|
||||
- R1 journal schema v1은 읽을 수 있어야 한다.
|
||||
- R1 compatibility는 별도 미설정 root나 동시에 활성화된 legacy bean이 아니다. Operator가 기존
|
||||
R1 root를 owner/mode/FileStore/sentinel 등 R2 attestation 조건에 맞춰 명시적으로
|
||||
pre-provision한 뒤, 그 root를 R2 destination으로 전환하는 in-place read-only migration이다.
|
||||
- R1과 R2 operation journal은 같은 hashed path를 사용하므로 secure relative typed schema
|
||||
dispatch로 schema v1을 읽고 schema v2만 쓴다.
|
||||
- R1 journal schema v1은 strict UTF-8와 canonical re-encode byte equality를 만족하는 terminal
|
||||
record만 읽을 수 있어야 한다.
|
||||
- R1 terminal receipt는 기존 `PROCESS_LOCAL_SYNC` 보장 그대로 복원한다.
|
||||
- R1 root-level artifact도 attested root의 `SecureDirectoryStream` 상대 no-follow bounded
|
||||
streaming inspection으로 journal의 byte size와 SHA-256을 확인한 뒤에만 receipt를 복원한다.
|
||||
- R1 artifact를 자동으로 R2 manifest/reference로 승격하지 않는다.
|
||||
- R2 writer는 journal v2만 생성한다.
|
||||
- 기존 overwrite-capable legacy port는 별도 root와 opt-in을 유지하며 R2 control plane에 접근하지
|
||||
@@ -334,9 +415,12 @@ matching data + private manifest + reference
|
||||
- 설정/보장 mismatch: startup failure;
|
||||
- destination 없음: producer 전 deterministic request failure;
|
||||
- stage 이전 capacity/validation failure: not applied;
|
||||
- stage/write failure: failed, partial stage는 recovery evidence가 아니면 정리;
|
||||
- stage/write failure: failed, partial stage는 recovery evidence가 아니면 exact cleanup하고
|
||||
unsealed `QUARANTINED`로 producer replay를 차단;
|
||||
- sealed 이후 filesystem timeout/IO/root identity change: indeterminate;
|
||||
- published data와 metadata 불일치: integrity/quarantine;
|
||||
- non-terminal published data와 metadata 불일치: integrity/quarantine;
|
||||
- terminal `PUBLISHED` data/metadata/receipt 불일치: terminal evidence 불변 보존 후 typed
|
||||
integrity/indeterminate;
|
||||
- journal/control record corruption: provider exception을 노출하지 않고 typed indeterminate;
|
||||
- guarantee를 낮춰 성공시키는 fallback은 없다.
|
||||
|
||||
@@ -348,6 +432,8 @@ matching data + private manifest + reference
|
||||
- R1/R2 simultaneous activation rejection;
|
||||
- reference grammar/check digits/forged route rejection;
|
||||
- journal v2, manifest, reference canonical round-trip;
|
||||
- deterministic route token collision rejection과 canonical policy/schema/format digest;
|
||||
- same operation path의 strict canonical R1 read-only/v2 write-only typed dispatch;
|
||||
- state revision과 fingerprint conflict;
|
||||
- achieved durability value invariants.
|
||||
|
||||
@@ -360,8 +446,10 @@ matching data + private manifest + reference
|
||||
- successful capability probe와 cleanup;
|
||||
- partial final visibility 0건;
|
||||
- same operation concurrency와 producer once;
|
||||
- unsealed `WRITING` failure quarantine와 retry producer 0회;
|
||||
- target collision no overwrite;
|
||||
- data/manifest/reference digest mismatch quarantine.
|
||||
- non-terminal data/manifest/reference digest mismatch quarantine;
|
||||
- terminal mismatch의 PUBLISHED journal/artifact 불변 보존과 typed integrity/indeterminate.
|
||||
|
||||
### 13.3 Crash qualification
|
||||
|
||||
@@ -390,6 +478,11 @@ terminal journal directory force
|
||||
|
||||
partial final, overwrite, 다른 receipt, silent guarantee downgrade는 허용하지 않는다.
|
||||
|
||||
같은 attested root와 operation ID에 대해 process A가 OS operation lock을 보유하는 동안 forked
|
||||
process B의 bounded non-blocking/timed acquire가 critical section에 진입하지 못하고, A의
|
||||
release 또는 강제 종료 뒤 B가 획득하는지도 별도로 증명한다. 이 증거는 동일 JVM stripe 테스트로
|
||||
대체하지 않는다.
|
||||
|
||||
### 13.4 플랫폼
|
||||
|
||||
- Linux/POSIX + `SecureDirectoryStream` + directory force qualification lane에서만
|
||||
@@ -413,3 +506,41 @@ partial final, overwrite, 다른 receipt, silent guarantee downgrade는 허용
|
||||
8. 문서와 receipt는 `local-persistent` qualification만 R2라고 표시한다.
|
||||
|
||||
후속 순서는 Phase 3 maintenance/resource limits, Phase 4 SFTP, Phase 5 shared-mounted/NFS evidence다.
|
||||
|
||||
## 15. 구현 및 readiness 판정
|
||||
|
||||
2026-07-28 구현은 다음 경계를 만족한다.
|
||||
|
||||
- application에는 provider/path/framework 타입이 없는 `FilePublicationPort`만 유지한다.
|
||||
- adapter 내부의 canonical operation/manifest/reference model, opaque reference, provider SPI,
|
||||
exact destination router는 provider-neutral control/selection boundary로 구현되었다.
|
||||
- `app.fileserver.enabled`는 disabled-default이며, enable 시 destination/provider를 exact
|
||||
compile한다. Unknown destination은 producer 호출 전에 실패하고 implicit local fallback은
|
||||
없다.
|
||||
- 같은 provider ID를 참조하는 destination은 하나의 provider/control/payload runtime을
|
||||
공유한다. 서로 다른 provider ID가 같은 normalized root를 소유하면 startup에서 실패한다.
|
||||
- R2 provider는 `local-persistent` 하나만 구현·qualification한다. Absolute/existing
|
||||
pre-provisioned root와 owner/mode/FileStore/sentinel/path/capability attestation이 모두
|
||||
성공해야 bean이 구성된다.
|
||||
- operation v2, private manifest, direct reference index, ordered force publication과
|
||||
deterministic recovery를 구현했다. Forked-process qualification은 각 force boundary와 OS
|
||||
operation lock을 대상으로 하며, focused/module/full gate 결과와 함께 완료 증거를 판정한다.
|
||||
- 기존 schema-v1 terminal record와 root-level R1 artifact는 strict UTF-8/canonical/direct
|
||||
read-only compatibility다. 원래 `PROCESS_LOCAL_SYNC` receipt만 복원하며 schema-v2 rewrite,
|
||||
manifest/reference 생성, `FILE_AND_DIRECTORY_SYNC` 자동 승격을 하지 않는다.
|
||||
|
||||
`FILE_AND_DIRECTORY_SYNC`는 attested local filesystem protocol에서 file과 관련 directory
|
||||
force가 성공했다는 의미다. Physical device, volatile storage-controller cache, volume replica,
|
||||
backup 또는 site 단위 power-loss protection을 주장하지 않는다. 그 보장은 Fileserver 코드가
|
||||
아니라 선택한 storage/deployment의 별도 evidence가 필요하다.
|
||||
|
||||
다음 capability는 구현되지 않았고 setting/env/bean으로 노출하지 않는다.
|
||||
|
||||
- `shared-mounted`/NFS multi-client correctness와 cross-node producer fencing;
|
||||
- SFTP SDK, connection/session pool, host-key/credential, remote reconciliation;
|
||||
- background reconcile/reaper, managed retention/delete;
|
||||
- quota reservation, backpressure, capacity admission;
|
||||
- Fileserver 전용 readiness/health, metrics, tracing, audit.
|
||||
|
||||
따라서 이 increment의 운영 claim은 “모든 Fileserver topology가 R2”가 아니라
|
||||
“strictly attested `local-persistent` profile만 R2”다.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1154
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,137 @@
|
||||
# Redis Cache Resilience Increment Design
|
||||
|
||||
**Status:** approved for implementation
|
||||
|
||||
**Parent:** `2026-07-26-redis-production-capability-design.md` §§15, 16, 18
|
||||
|
||||
## Goal
|
||||
|
||||
Complete one coherent production-facing cache increment on top of the current standalone R1 Redis
|
||||
runtime:
|
||||
|
||||
1. a framework-free cache-aside policy in `application-core`;
|
||||
2. bounded local single-flight and source bulkhead protection;
|
||||
3. deterministic TTL jitter plus soft/hard expiry and stale lookup semantics in the Redis adapter.
|
||||
|
||||
This increment does not promote Redis beyond standalone cache R1. Distributed refresh leases,
|
||||
generation invalidation, rate limiting, owner-safe locks, idempotency, sessions, Sentinel/Cluster,
|
||||
TLS/ACL and fault qualification remain later increments.
|
||||
|
||||
## Architecture boundary
|
||||
|
||||
- `application-core` owns lookup interpretation, source-result classification, cache-aside
|
||||
sequencing, stale-if-error, local coalescing and source admission policy.
|
||||
- `adapter:outbound:cache-redis` owns physical TTL, envelope timestamps, deterministic jitter,
|
||||
serialization and Redis command outcomes.
|
||||
- The application contract contains no Redis/Lettuce/Lua/Spring type.
|
||||
- Cache fallback never becomes unlimited source fallback. A miss, provider outage and waiter burst
|
||||
all pass through the same bounded source path.
|
||||
|
||||
## Application contract
|
||||
|
||||
`CacheSourceLoader<K,V>` returns a typed `SourceLoadOutcome<V>`:
|
||||
|
||||
- `Loaded(value, sourceRevision)`;
|
||||
- `AuthoritativeAbsent(reason, sourceRevision)`;
|
||||
- `TransientFailure(SourceFailure)`;
|
||||
- `PermanentFailure(SourceFailure)`;
|
||||
- `Cancelled`.
|
||||
|
||||
`SourceFailure` carries a bounded code and the original cause. It never serializes the cause message
|
||||
into Redis or metric tags. An unclassified thrown exception is rethrown unchanged and is never
|
||||
negative-cached or converted to stale success.
|
||||
|
||||
`CacheResult<V>` distinguishes:
|
||||
|
||||
- fresh cache hit;
|
||||
- source-loaded value and its cache-record outcome;
|
||||
- authoritative absence and its cache-record outcome;
|
||||
- stale fallback after a classified transient source failure;
|
||||
- source failure;
|
||||
- bounded overload/timeout rejection;
|
||||
- cancellation.
|
||||
|
||||
`CacheAsidePolicy` is immutable and constructed once per semantic region. It contains maximum
|
||||
in-flight source keys, waiter limit per key, source concurrency, admission wait, load deadline and
|
||||
whether transient source failure may serve stale.
|
||||
|
||||
## Cache-aside state machine
|
||||
|
||||
1. `Hit(FRESH)` returns immediately.
|
||||
2. `NegativeHit` returns immediately.
|
||||
3. `Hit(STALE)` retains the value and attempts a bounded refresh.
|
||||
4. `Miss`, an `IncompatibleSchema(QUARANTINE_AND_RELOAD)` carrying a usable opaque observation
|
||||
token, and `Unavailable` enter the same bounded source path. `FAIL_FAST` schema results and
|
||||
unobservable incompatible values are not overwritten.
|
||||
5. A local single-flight elects one leader per semantic key. Waiters share the typed source outcome.
|
||||
6. The leader must acquire the source bulkhead before calling the loader.
|
||||
7. A miss records with `ONLY_IF_ABSENT`. A stale or quarantined observation records with
|
||||
`ONLY_IF_OBSERVED`, which atomically compares the digest captured by lookup before replacing the
|
||||
value. No lookup-then-delete sequence is used, so a concurrent writer is never deleted.
|
||||
8. Only `AuthoritativeAbsent` records a negative entry, using the same absent/observed condition as
|
||||
a positive source result.
|
||||
9. `TransientFailure` may return the retained stale value when policy allows it.
|
||||
10. `PermanentFailure`, unclassified exceptions and cancellation are never hidden by negative cache.
|
||||
11. Entries are removed from the flight map after success or failure. In-flight keys and waiters are
|
||||
bounded; waiting uses a finite deadline and preserves thread interruption.
|
||||
|
||||
The loader is synchronous and cancellation is cooperative. Its token exposes deadline/interruption;
|
||||
the executor bounds admission and waiter time but cannot safely terminate arbitrary source code.
|
||||
|
||||
## Redis envelope and TTL policy
|
||||
|
||||
The positive envelope moves to version 2 and stores:
|
||||
|
||||
- source revision;
|
||||
- `softExpiresAt` epoch milliseconds;
|
||||
- `hardExpiresAt` epoch milliseconds;
|
||||
- payload and SHA-256 integrity digest.
|
||||
|
||||
Negative envelopes store only the hard expiry. Lookup behavior is:
|
||||
|
||||
- `now < softExpiresAt`: `Hit(FRESH)`;
|
||||
- `softExpiresAt <= now < hardExpiresAt`: `Hit(STALE)`;
|
||||
- `now >= hardExpiresAt`: `Miss(EXPIRED)`;
|
||||
- negative `now < hardExpiresAt`: `NegativeHit`;
|
||||
- expired negative: `Miss(EXPIRED)`.
|
||||
|
||||
Version 1 becomes an explicit retired schema result. Future versions and corrupt envelopes fail
|
||||
fast. Digest-valid retired/unknown envelopes carry an opaque observation token so an approved
|
||||
quarantine reload can compare-and-replace the exact observation. Structurally invalid current
|
||||
envelopes remain corrupt/fail-fast even when their digest is valid. Unknown envelopes remain typed
|
||||
incompatibility results and are not silently treated as misses. Envelope integrity is checked
|
||||
before the version byte is trusted.
|
||||
|
||||
The policy contains positive soft TTL, positive hard TTL, negative TTL, jitter ratio, minimum hard
|
||||
TTL and maximum value bytes. Construction rejects:
|
||||
|
||||
- non-positive or over-30-day TTLs;
|
||||
- soft TTL greater than hard TTL;
|
||||
- jitter outside `0.0..0.5`;
|
||||
- minimum hard TTL greater than either configured hard TTL.
|
||||
- configured hard TTL plus maximum positive jitter greater than 30 days.
|
||||
|
||||
Jitter is deterministic from the HMAC-derived physical key and the compiled policy revision. It
|
||||
uses a symmetric bounded factor. The actual positive soft/hard TTLs use the same factor so ordering
|
||||
is preserved. Physical Redis TTL equals the encoded hard expiry duration in the same `SET`.
|
||||
Negative TTL is jittered independently and also respects the hard minimum.
|
||||
|
||||
## Evidence
|
||||
|
||||
Tests must prove:
|
||||
|
||||
- fresh/negative hits do not call the source;
|
||||
- concurrent same-key misses call the loader once;
|
||||
- in-flight-key, waiter, bulkhead and deadline bounds;
|
||||
- completion/failure cleanup and exception/interruption behavior;
|
||||
- only authoritative absence is negative-cached;
|
||||
- stale is served only after a classified transient failure;
|
||||
- fresh/stale/expired boundaries with an injected `Clock`;
|
||||
- deterministic bounded jitter and hard minimum;
|
||||
- version 1/future/corrupt envelope behavior;
|
||||
- Redis physical TTL matches the encoded hard expiry.
|
||||
- observed replace reads only the trailing digest and never overwrites a concurrent writer;
|
||||
- the exact 16MiB opt-in payload is accepted while 16MiB+1 is rejected before dispatch;
|
||||
- mutation interruption restores the thread flag and maps to indeterminate certainty.
|
||||
|
||||
Focused checks run before the repository-wide architecture, dependency, env and public-path gates.
|
||||
@@ -0,0 +1,144 @@
|
||||
# Redis Distributed Rate-Limit Increment Design
|
||||
|
||||
**Status:** implemented as standalone R1
|
||||
|
||||
**Parent:** `2026-07-26-redis-production-capability-design.md` §§19–21
|
||||
|
||||
## Goal and readiness
|
||||
|
||||
Provide three selectable, bounded distributed rate-limit algorithms:
|
||||
|
||||
- fixed window;
|
||||
- sliding-window counter;
|
||||
- token bucket.
|
||||
|
||||
This increment is a standalone Redis R1 provider. It does not claim R2 topology/security/failover
|
||||
qualification and does not implement sliding log, GCRA, leaky bucket, evaluation dedup, hierarchical
|
||||
all-or-nothing policies or local emergency fallback.
|
||||
|
||||
## Ownership
|
||||
|
||||
- `shared-contract` owns the edge-enforcement semantic port and provider-neutral request, policy,
|
||||
decision and failure outcomes. Business quotas remain application use-case policy and do not use
|
||||
this port.
|
||||
- `adapter:outbound:cache-redis` owns Redis keys, atomic Lua programs, structured reply parsing,
|
||||
failure certainty and the provider implementation.
|
||||
- `app-bootstrap` owns the explicit provider/policy selection.
|
||||
- The existing inbound-web local limiter remains a compatibility path until a separate inbound
|
||||
migration. Its types do not cross into the Redis provider.
|
||||
|
||||
The rate-limit runtime does not reuse `app.cache.redis`, the cache connection or cache fail-open
|
||||
decorators. Coordination has different failure and deployment semantics.
|
||||
|
||||
## Shared semantic contract
|
||||
|
||||
`EdgeRateLimitPort.evaluate(RateLimitRequest)` accepts:
|
||||
|
||||
- bounded `policyId`;
|
||||
- already pseudonymized/bounded `subjectDigest`;
|
||||
- positive request cost;
|
||||
- optional evaluation ID (rejected in this non-deduplicating revision);
|
||||
- finite caller deadline.
|
||||
|
||||
`RateLimitPolicy` freezes policy ID/revision, one algorithm-specific parameter subtype, maximum
|
||||
cost, cleanup grace, maximum clock regression and `FAIL_CLOSED`. Construction rejects mismatched
|
||||
algorithm/parameters, arithmetic outside Lua's exact integer range and unsupported failure/dedup
|
||||
claims.
|
||||
|
||||
The outcome is one of:
|
||||
|
||||
- `Evaluated(decision)`;
|
||||
- `Unavailable(policyId, retryAfter, category)` for known pre-send/no-mutation failures and unsafe
|
||||
server clock;
|
||||
- `Indeterminate(policyId, retryAfter)` for post-dispatch uncertain mutation;
|
||||
- `Incompatible(policyId, category)` for state/program/reply mismatch.
|
||||
|
||||
`RateLimitDecision` includes allow/deny, limit, remaining, retry-after, reset-at, policy ID/revision,
|
||||
`GLOBAL_REDIS` source and certainty. Fixed window and token bucket are `CERTAIN`;
|
||||
sliding-window counter is `APPROXIMATE_ALGORITHM`.
|
||||
|
||||
## Atomic programs
|
||||
|
||||
Each v1 program uses one versioned hash key and calls Redis `TIME` exactly once.
|
||||
|
||||
```text
|
||||
rate-fixed-window-v1.lua
|
||||
rate-sliding-counter-v1.lua
|
||||
rate-token-bucket-v1.lua
|
||||
```
|
||||
|
||||
Every program returns exactly seven bounded scalar fields:
|
||||
|
||||
```text
|
||||
status, serverNowMillis, effectiveNowMillis,
|
||||
limit, remaining, retryAfterMillis, resetAtMillis
|
||||
```
|
||||
|
||||
Statuses are `ALLOWED`, `DENIED`, `CLOCK_UNSAFE`, `STATE_INCOMPATIBLE`, `INVALID`.
|
||||
Unknown arity/status/numeric syntax/range is a compatibility failure, never allow/fail-open.
|
||||
|
||||
Common rules:
|
||||
|
||||
- Redis server time drives enforcement;
|
||||
- small backward movement clamps to stored `lastObservedMillis`;
|
||||
- regression beyond policy threshold returns `CLOCK_UNSAFE` without consuming state;
|
||||
- policy/schema/algorithm mismatch returns `STATE_INCOMPATIBLE`;
|
||||
- denied requests do not consume quota;
|
||||
- state receives a finite TTL;
|
||||
- all arithmetic stays within `2^53-1`;
|
||||
- raw principal/IP/API-key/route never appears in the physical key.
|
||||
|
||||
The existing scalar Lua executor stays intact. A structured program path adds bounded MULTI reply
|
||||
support and uses `EVALSHA`, falling back to the exact compiled source only on `NOSCRIPT`.
|
||||
|
||||
## Algorithm rules
|
||||
|
||||
Fixed window stores window ID and consumed count. Allow increments only when
|
||||
`consumed + cost <= limit`; retry/reset points to the current window end.
|
||||
|
||||
Sliding counter stores previous/current window IDs and counts, using scale `1_000_000` and
|
||||
conservative ceiling weight. It reports approximate certainty and a bounded conservative retry.
|
||||
|
||||
Token bucket stores scaled tokens, last refill time and the sub-token division remainder. Refill is
|
||||
therefore independent of evaluation frequency, uses quotient/remainder arithmetic without an
|
||||
unsafe `numerator + denominator - 1` intermediate, and saturates at capacity. Denial does not
|
||||
subtract tokens; retry and full-reset use integer ceiling.
|
||||
|
||||
## Physical key
|
||||
|
||||
The existing canonical builder is reused with:
|
||||
|
||||
```text
|
||||
capability=rate
|
||||
region=<policyId>
|
||||
kind=state
|
||||
digest(policyId, policyRevision, algorithm, subjectDigest)
|
||||
```
|
||||
|
||||
Policy revision appears in both digest input and stored state. A policy revision therefore rolls to
|
||||
a new key while old state expires naturally.
|
||||
|
||||
## Runtime and composition
|
||||
|
||||
`app.rate-limit` is disabled by default. Enabling requires:
|
||||
|
||||
- `provider=redis`;
|
||||
- one default policy and an exact policy definition;
|
||||
- a dedicated Redis coordination endpoint and HMAC secret;
|
||||
- finite command/admission bounds.
|
||||
|
||||
Only `role=coordination` and `failure-policy=fail-closed` are accepted in v1. Disabled mode creates
|
||||
no connection, thread or semantic port. Cache Redis settings/beans are never an implicit fallback.
|
||||
|
||||
## Evidence
|
||||
|
||||
Unit tests cover contract bounds, policy arithmetic, key privacy/revision, structured reply
|
||||
validation, `NOSCRIPT`, boundary vectors, denial-no-consume, clock regression, pre/post-dispatch
|
||||
failure certainty and disabled composition. The explicit Redis 7.4 service lane executes all three
|
||||
programs, exact-boundary admission after a denied non-consuming request, excessive clock-regression
|
||||
state immutability, `TYPE` response normalization, token refill-remainder carry, malformed hash-state
|
||||
classification, cache `NX`, and observation-token compare-and-replace. Redis 7.4 is the minimum
|
||||
version declared by the program manifests until a lower-version service lane exists. The caller
|
||||
deadline is an admission precheck against the fixed command timeout; R1 does not claim per-command
|
||||
dynamic timeout or hard cancellation after dispatch. Missing TLS/ACL, Sentinel/Cluster, failover and
|
||||
persistence/eviction evidence keeps the provider at R1.
|
||||
Reference in New Issue
Block a user