547 lines
26 KiB
Markdown
547 lines
26 KiB
Markdown
# Fileserver R2 Control Plane and Provider Selection Design
|
|
|
|
- Date: 2026-07-28
|
|
- Status: 구현·전체 repository gate·독립 spec/quality review 완료
|
|
- Scope: provider-neutral R2 control plane, explicit destination/provider selection, first
|
|
`local-persistent` qualification provider
|
|
- Parent:
|
|
[Fileserver Production Capability Deep Design](2026-07-26-fileserver-production-capability-design.md)
|
|
|
|
## 1. 목표
|
|
|
|
현재 `LocalFilePublicationAdapter`의 single-node process-restart R1을 운영 topology의 기본값으로
|
|
승격하지 않는다. 이번 increment는 다음을 구현한다.
|
|
|
|
1. application에는 기존 provider-neutral `FilePublicationPort`만 유지한다.
|
|
2. adapter 내부에 destination binding, provider descriptor, durable operation/manifest/reference
|
|
control plane을 둔다.
|
|
3. 활성화된 Fileserver는 정확한 destination과 provider를 명시해야 하며 implicit local fallback을
|
|
금지한다.
|
|
4. 첫 qualification provider로 pre-provisioned persistent filesystem을 사용하는
|
|
`local-persistent`를 구현한다.
|
|
5. `shared-mounted`와 `sftp`가 같은 control-plane state machine을 재사용할 수 있게 하되 이번
|
|
increment에서 가짜 provider나 동작하지 않는 bean을 만들지 않는다.
|
|
|
|
`local-persistent`는 container writable layer나 임시 디렉터리를 의미하지 않는다. 단일 노드 또는
|
|
node-attached persistent volume과 private owner boundary가 증명된 환경만 대상으로 한다.
|
|
|
|
## 2. 비범위
|
|
|
|
이번 increment에 포함하지 않는다.
|
|
|
|
- NFS 또는 다른 shared mount의 multi-client correctness;
|
|
- SFTP SDK, connection pool, credential, OpenSSH qualification;
|
|
- cross-node producer fencing;
|
|
- background reaper, retention delete, quota reservation;
|
|
- metrics/tracing/health implementation;
|
|
- optional content read/delete/list API;
|
|
- object storage. Object storage는 별도 outbound leaf의 책임이다.
|
|
|
|
이 항목은 seam만 만들지 않는다. 실제 semantic provider를 구현하는 후속 increment에서만
|
|
dependency, bean, setting을 추가한다.
|
|
|
|
## 3. 검토한 접근
|
|
|
|
### A. 현재 local adapter를 바로 R2로 표시
|
|
|
|
설정과 change surface는 작지만 provider selector, terminal manifest, opaque-reference direct
|
|
lookup과 strict startup evidence가 없다. R2를 과장하므로 선택하지 않는다.
|
|
|
|
### B. Local, NFS, SFTP를 동시에 구현
|
|
|
|
최종 기능은 많지만 서로 다른 보장과 real-service CI가 한 change surface에 결합된다. NFS와
|
|
OpenSSH 인프라가 없으면 검증되지 않은 provider가 남으므로 선택하지 않는다.
|
|
|
|
### C. Provider-neutral control plane + local-persistent 첫 qualification
|
|
|
|
공통 state machine과 binding을 먼저 고정하고 한 provider를 실제 crash/security 테스트로
|
|
qualification한다. 이후 provider가 control-plane 계약을 재사용하면서도 각자의 보장을 별도로
|
|
증명할 수 있다. 이 접근을 선택한다.
|
|
|
|
## 4. 계층과 모듈 경계
|
|
|
|
```text
|
|
application-core
|
|
FilePublicationPort
|
|
FilePublishRequest
|
|
FilePublishReceipt
|
|
|
|
|
v
|
|
adapter:outbound:fileserver
|
|
RoutingFilePublicationAdapter
|
|
|
|
|
+-- DestinationBindingRegistry
|
|
+-- FilePublicationProviderRegistry
|
|
+-- DurablePublicationCoordinator
|
|
+-- ProviderControlPlane
|
|
|
|
|
+-- LocalPersistentPublicationProvider
|
|
```
|
|
|
|
- application/domain에는 provider ID, filesystem path, manifest locator, Spring 또는 NIO 타입을
|
|
추가하지 않는다.
|
|
- `RoutingFilePublicationAdapter`만 production `FilePublicationPort` bean이다.
|
|
- provider와 control-plane SPI는 fileserver package 내부 타입이다. 범용 filesystem/SDK API를
|
|
public bean으로 노출하지 않는다.
|
|
- `shared-mounted`와 `sftp` 타입 값은 구현 전까지 accepted setting으로 등록하지 않는다.
|
|
|
|
## 5. Application 계약 변경
|
|
|
|
기존 request와 opaque reference를 유지한다. R2 provider가 달성한 보장을 정확히 보고할 수 있도록
|
|
`FilePublishReceipt.DurabilityGuarantee`에 다음 값만 추가한다.
|
|
|
|
```text
|
|
FILE_AND_DIRECTORY_SYNC
|
|
```
|
|
|
|
이 값은 startup probe와 process-crash qualification을 모두 통과한 provider만 반환한다.
|
|
호출한 sync가 물리 device, volume replica 또는 storage-controller power-loss protection까지
|
|
완료됐다는 뜻은 아니다. 그 축은 deployment/storage evidence로 별도 판정한다.
|
|
`PROCESS_LOCAL_SYNC` 또는 `PROVIDER_ACK_ONLY`를 요구 보장보다 약한 상태에서 자동으로 R2 값으로
|
|
올리지 않는다.
|
|
|
|
새 opaque reference 형식은 다음 의미를 가지되 application은 내부 segment를 해석하지 않는다.
|
|
|
|
```text
|
|
fsr1.<route-token>.<file-id>.<check-digits>
|
|
```
|
|
|
|
- `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는 포함하지 않는다.
|
|
|
|
Reference는 authorization token이 아니다. authorization은 application use case의 책임이다.
|
|
|
|
## 6. 명시적 설정과 선택
|
|
|
|
새 canonical prefix는 `app.fileserver`다.
|
|
|
|
```yaml
|
|
app:
|
|
fileserver:
|
|
enabled: false
|
|
destinations:
|
|
local-export:
|
|
provider-ref: local-primary
|
|
required-publication: unique-atomic-create
|
|
required-durability: file-and-directory-sync
|
|
maximum-rows: 1000000
|
|
maximum-encoded-bytes: 1073741824
|
|
providers:
|
|
local-primary:
|
|
type: local-persistent
|
|
root-directory: ${APP_FILESERVER_LOCAL_ROOT:}
|
|
auto-create: false
|
|
strict-path-security: true
|
|
expected-file-store-name: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_NAME:}
|
|
expected-file-store-type: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_TYPE:}
|
|
mount-sentinel-name: .ca-fileserver-volume
|
|
mount-sentinel-sha256: ${APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256:}
|
|
expected-owner: ${APP_FILESERVER_LOCAL_EXPECTED_OWNER:}
|
|
maximum-root-mode: "0750"
|
|
```
|
|
|
|
규칙:
|
|
|
|
- `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여야 한다.
|
|
- `auto-create=true`는 `local-persistent`에서 거부한다.
|
|
- root와 mount sentinel은 operator가 미리 만든다. Root attestation이 끝난 뒤 adapter가 private
|
|
top-level control/data directory와 bounded hash shard를 restrictive POSIX creation mode로
|
|
생성할 수 있으며, 생성할 때마다 parent identity와 directory sync를 확인한다.
|
|
- container ephemeral 경로를 위한 `local-dev`는 별도 후속 profile이다. production 설정과
|
|
같은 guarantee를 공유하지 않는다.
|
|
- 기존 `ca-skeleton.fileserver.*`는 R1/legacy compatibility selector로만 남는다. 새 R2 설정과
|
|
동시에 활성화되면 어느 쪽 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가 아니다.
|
|
2. root real path가 설정 absolute path와 일치한다.
|
|
3. configured owner와 실제 owner가 일치한다.
|
|
4. POSIX permission이 configured maximum보다 넓지 않고 group/world writable이 아니다.
|
|
5. `FileStore.name()`과 `type()`이 설정 값과 일치한다.
|
|
6. mount sentinel이 regular no-follow file이고 configured SHA-256와 일치한다.
|
|
7. data, staging, operations, manifests, references, quarantine directory가 같은
|
|
`FileStore`에 있다.
|
|
8. control directory는 private owner boundary이며 symlink가 아니다.
|
|
9. `SecureDirectoryStream`을 열 수 있다.
|
|
10. exclusive create, file force, hard-link create, directory force가 private probe directory에서
|
|
성공한다.
|
|
|
|
Probe artifact는 unique name만 사용하며 successful cleanup과 parent directory force까지
|
|
완료해야 한다. Probe 실패는 capability downgrade가 아니라 startup failure다.
|
|
|
|
JDK가 directory-relative hard-link primitive를 제공하지 않으므로 hard-link publish는 다음
|
|
boundary에서만 허용한다.
|
|
|
|
- root/control/data directories가 adapter owner 전용이고 untrusted writer가 없음;
|
|
- publish 직전과 직후 root identity, directory file key, mount sentinel을 다시 확인;
|
|
- target은 CSPRNG unique name;
|
|
- pre/post identity가 바뀌면 성공을 반환하지 않고 `PUBLISH_INDETERMINATE`;
|
|
- privileged host administrator 또는 same-owner malicious process와의 경쟁은 guarantee 범위가
|
|
아니며 deployment isolation requirement로 기록한다.
|
|
|
|
untrusted writer가 같은 root에 entry를 만들 수 있는 환경은 strict local R2가 아니다.
|
|
|
|
## 8. Durable control plane
|
|
|
|
```text
|
|
.ca-fileserver/
|
|
operations/<prefix>/<operation-id>.json
|
|
manifests/<prefix>/<file-id>.json
|
|
references/<prefix>/<file-id>.json
|
|
staging/<prefix>/<operation-id>.part
|
|
quarantine/
|
|
probe/
|
|
data/<prefix>/<generated-file-name>
|
|
```
|
|
|
|
모든 locator는 validated single segment 또는 adapter가 생성한 bounded relative segment다.
|
|
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
|
|
|
|
필수 필드:
|
|
|
|
```text
|
|
schemaVersion
|
|
stateRevision
|
|
state
|
|
operationId
|
|
requestFingerprint
|
|
effectivePolicyRevision
|
|
effectivePolicyDigest
|
|
destinationId
|
|
providerId
|
|
fileId
|
|
routeToken
|
|
publishedFileName
|
|
stageFileName
|
|
byteSize
|
|
rowCount
|
|
columnCount
|
|
sha256
|
|
formulaMitigatedCount
|
|
manifestDigest
|
|
referenceDigest
|
|
createdAt
|
|
sealedAt
|
|
publishedAt
|
|
lastFailureCode
|
|
receiptSnapshot
|
|
```
|
|
|
|
State는 `WRITING`, `SEALED`, `DATA_PUBLISHED`, `MANIFEST_PUBLISHED`,
|
|
`REFERENCE_PUBLISHED`, `PUBLISHED`, `QUARANTINED`다.
|
|
|
|
### 8.2 Private manifest v1
|
|
|
|
Manifest는 operation/file/provider/reference/fingerprint, schema·format·policy digest, byte/count,
|
|
SHA-256, achieved guarantees, internal relative locator를 기록한다. Absolute path, raw row/cell,
|
|
credential, raw tenant/user ID는 저장하지 않는다.
|
|
|
|
### 8.3 Reference index v1
|
|
|
|
Reference index는 opaque `file-id`에서 operation ID, file version, manifest digest와 internal
|
|
relative locator로 direct lookup한다. Directory scan은 receipt restoration의 authority가 아니다.
|
|
|
|
### 8.4 Record update
|
|
|
|
각 control record는:
|
|
|
|
1. sibling private temp file을 `CREATE_NEW`;
|
|
2. bounded canonical JSON encoding;
|
|
3. file `force(true)`;
|
|
4. same-directory atomic replace;
|
|
5. parent directory force;
|
|
6. read-back schema/revision/digest verification;
|
|
|
|
순서로 갱신한다. 낮은 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
|
|
J-WRITING
|
|
-> stage stream/force
|
|
J-SEALED
|
|
-> exclusive hard-link data publish
|
|
-> data directory force
|
|
J-DATA_PUBLISHED
|
|
-> private manifest publish/force
|
|
J-MANIFEST_PUBLISHED
|
|
-> reference index publish/force
|
|
J-REFERENCE_PUBLISHED
|
|
-> terminal journal + receipt snapshot publish/force
|
|
J-PUBLISHED
|
|
-> receipt return
|
|
```
|
|
|
|
- Producer는 accepted attempt에서 최대 한 번 호출한다.
|
|
- `SEALED` 이후 retry/recovery는 staged bytes만 사용한다.
|
|
- 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
|
|
|
|
Recovery는 operation ID direct lookup으로 실행하며 startup full scan에 의존하지 않는다.
|
|
|
|
| 확인된 상태 | 조치 |
|
|
| --- | --- |
|
|
| terminal journal + matching manifest/reference/data | 저장된 receipt 복원 |
|
|
| SEALED + valid stage, data 없음 | data publication부터 재개 |
|
|
| SEALED + matching data | manifest publication부터 재개 |
|
|
| DATA_PUBLISHED + matching data | manifest publication 재개 |
|
|
| MANIFEST_PUBLISHED + matching manifest/data | reference publication 재개 |
|
|
| REFERENCE_PUBLISHED + all matching | terminal journal 완성 |
|
|
| 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 중단 |
|
|
|
|
Truth priority:
|
|
|
|
```text
|
|
matching data + private manifest + reference
|
|
> terminal operation record
|
|
> non-terminal operation record
|
|
> in-memory state
|
|
```
|
|
|
|
모순이 있으면 임의 성공이나 삭제를 하지 않는다. 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 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에 접근하지
|
|
않는다.
|
|
- R1과 R2 selector가 동시에 활성화되면 ambiguous composition으로 startup을 실패시킨다.
|
|
|
|
## 12. Failure semantics
|
|
|
|
- 설정/보장 mismatch: startup failure;
|
|
- destination 없음: producer 전 deterministic request failure;
|
|
- stage 이전 capacity/validation failure: not applied;
|
|
- stage/write failure: failed, partial stage는 recovery evidence가 아니면 exact cleanup하고
|
|
unsealed `QUARANTINED`로 producer replay를 차단;
|
|
- sealed 이후 filesystem timeout/IO/root identity change: indeterminate;
|
|
- 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은 없다.
|
|
|
|
## 13. 테스트와 증거
|
|
|
|
### 13.1 Unit/contract
|
|
|
|
- exact destination/provider selection과 no-default;
|
|
- 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.
|
|
|
|
### 13.2 Local integration
|
|
|
|
- pre-provisioned root requirement;
|
|
- owner/mode/FileStore/sentinel mismatch startup failure;
|
|
- symlink ancestor/control/data rejection;
|
|
- staging/final/control same `FileStore`;
|
|
- successful capability probe와 cleanup;
|
|
- partial final visibility 0건;
|
|
- same operation concurrency와 producer once;
|
|
- unsealed `WRITING` failure quarantine와 retry producer 0회;
|
|
- target collision no overwrite;
|
|
- non-terminal data/manifest/reference digest mismatch quarantine;
|
|
- terminal mismatch의 PUBLISHED journal/artifact 불변 보존과 typed integrity/indeterminate.
|
|
|
|
### 13.3 Crash qualification
|
|
|
|
Forked JVM helper를 사용해 다음 force boundary 직후 process를 강제 종료하고 새 JVM에서 같은
|
|
operation을 재시도한다.
|
|
|
|
```text
|
|
J-WRITING
|
|
stage force
|
|
J-SEALED
|
|
data link
|
|
data directory force
|
|
manifest force
|
|
manifest directory force
|
|
reference force
|
|
reference directory force
|
|
terminal journal force
|
|
terminal journal directory force
|
|
```
|
|
|
|
각 boundary에서 결과는 다음 중 하나여야 한다.
|
|
|
|
- producer 재실행 없이 동일 receipt 복원;
|
|
- verified sealed bytes로 publication 완성;
|
|
- typed indeterminate/quarantine.
|
|
|
|
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에서만
|
|
`FILE_AND_DIRECTORY_SYNC`을 검증한다.
|
|
- capability가 없는 일반 unit-test filesystem에서는 R1 보장만 테스트하며 R2 service test를
|
|
skip 성공으로 처리하지 않는다.
|
|
|
|
## 14. 완료 기준
|
|
|
|
이번 increment의 완료는 “Fileserver 전체가 모든 운영환경에서 R2”라는 뜻이 아니다.
|
|
|
|
완료를 주장하려면:
|
|
|
|
1. provider 기본값 없이 exact binding이 동작한다.
|
|
2. `local-persistent` startup probe가 모든 required capability를 증명한다.
|
|
3. terminal manifest/reference direct lookup이 구현된다.
|
|
4. 모든 publication force boundary의 crash test가 deterministic result를 낸다.
|
|
5. strict path/mount identity/security tests가 통과한다.
|
|
6. public path와 clean architecture gate가 통과한다.
|
|
7. R1 compatibility artifact를 R2로 자동 승격하지 않는다.
|
|
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”다.
|