---
status: MULTI_REPLICA_DATA_PLANE_CANARY_PASSED_NO_PRODUCTION_ACTIVATION
fixed_pc_server_required: false
main_server_outage_continuity_proven: false
production_traffic_changed: false
database_changed: false
validator_changed: false
operator_signing_locked: true
---

# 불특정 다수 PC 노드 기반 서비스 생존 구조

## 1. 방향 정정

목표는 `ellia05`, `Standby B`처럼 특정 PC를 차기 서버로 고정하는 것이 아니다. 특정 hostname은 canary와 장애영역 측정을 위한 임시 관찰 단위일 뿐이다. 최종 구조에서는 조건을 충족한 임의의 참여 PC가 capability를 광고하고, 짧은 lease 동안 storage, read-only RPC, stateless service, recovery cache 역할을 얻는다. 노드가 사라지면 lease가 만료되고 다른 적격 노드가 역할과 복제본을 이어받는다.

블록체인에 PC의 파일시스템이나 Express 프로세스가 자동으로 올라가는 것은 아니다. 체인은 node identity, capability commitment, release hash, endpoint manifest, 데이터 hash와 write finality를 제공한다. 실제 계산·저장·HTTP 응답은 다수 PC가 수행하며 클라이언트가 결과와 manifest를 검증한다.

`config/resilienceNodeRoles.example.json`의 PC별 표는 bootstrap evidence로만 유지한다. production role을 hostname allowlist로 고정하지 않는다.

## 2. 현재 재사용 가능한 기반

- 인증된 node register와 heartbeat
- 저장공간·CPU·RAM·uptime inventory
- 신뢰도, 응답시간, 가용공간을 이용한 동적 storage node 선택
- 실패·offline·corrupt chunk의 다른 노드 재할당
- encrypted chunk hash 검증과 restore-upload
- 각 PC의 local gateway, chunk 목록과 cached decentralized manifest
- signed/hash-bound client update와 단일 설치 worker 보장
- canonical validator migration, direct peer tunnel과 validator 자동승격 금지

이 기반은 노드 프로그램의 배포·신원·상태·저장 실행 계층으로 재사용한다. 기존 validator 역할과 일반 service 역할은 계속 분리한다.

## 3. 현재 중앙 의존성

현재 구현은 아직 permissionless service network가 아니다.

1. PC의 decentralized manifest는 메인 `serverUrl`에서 받아 로컬에 cache한다. 메인이 사라진 뒤 새 상태를 합의하거나 peer에게 검증 가능하게 전파하지 못한다.
2. 하나의 chunk에는 하나의 `assignedNodeId`만 있다. 장애 후 재할당은 있지만 동시에 여러 failure domain에 복제된 상태가 아니다.
3. 기존 HMAC envelope는 장치별 서버 token을 공유한다. 불특정 peer와 앱이 offline public key로 검증할 공개 manifest 서명은 아니다.
4. capability heartbeat는 하드웨어 상태까지 확장됐지만 service/RPC 역할을 부여하는 만료형 lease와 challenge가 없다.
5. PC에는 저장 gateway와 validator helper가 있지만, 비밀이 없고 재현 가능하며 sandbox된 stateless service capsule이 없다.
6. 현재 앱의 authoritative state는 Mongo, Redis, local upload와 메인 서버의 provider secret에 의존한다.
7. Android/Web client가 blockchain manifest에서 여러 service/RPC endpoint를 고르고 결과를 교차 검증하지 않는다.

따라서 현재 메인 서버를 끄면 blockchain validator 일부가 살아 있어도 전체 애플리케이션 서비스는 계속되지 않는다.

## 4. 목표 프로토콜

```text
Android / Web client
  | built-in bootstrap 3+ / cached chain header
  | verify chain-anchored capability + endpoint manifest
  v
dynamic gateway/service pool (N nodes, no fixed hostname)
  |-- stateless signed release capsule
  |-- read results checked by quorum/commitment
  |-- signed idempotent write event
  v
BFT-finalized event and metadata commitments
  |                    |
  |                    +-- dynamic read-only RPC/full-node pool
  v
rebuildable projections + encrypted content-addressed chunks
  | replica 1 | replica 2 | replica 3 ... across failure domains
```

### Node identity와 admission

- node identity는 device public key와 chain participant identity를 결합한다.
- hostname, 소유자 이름 또는 IP를 production allowlist로 사용하지 않는다.
- heartbeat, protocol version, release hash, capacity와 storage challenge를 통과한 노드만 짧은 capability lease를 얻는다.
- stale node는 lease를 잃고 replica와 endpoint pool에서 제거된다.
- Sybil 공격은 participant identity, rate limit, reliability score, resource proof와 failure-domain diversity로 제한한다.

### Storage

- 불특정 PC에는 plaintext, Mongo dump, user key, operator key를 보내지 않는다.
- client 또는 trusted owner boundary에서 먼저 암호화한 content-addressed chunk만 보낸다.
- chunk별 동시 replica 기본값은 3, 최소 failure domain은 2로 한다.
- 한 replica가 사라져도 중앙 원본 없이 다른 replica에서 새 노드로 복구할 수 있어야 한다.
- chain에는 ciphertext hash, replica receipt commitment, retention epoch만 기록하고 개인정보는 기록하지 않는다.

### RPC

- 일반 full-node RPC는 validator 투표권과 분리한다.
- 클라이언트는 최소 3개 endpoint 중 2개의 block/hash 결과가 일치하는지 확인한다.
- public transaction submission, 외부 전송과 operator signing은 현재 잠금을 유지한다.
- validator set 변경은 기존 canonical governance 절차로만 수행하며 capability가 좋다는 이유로 자동 승격하지 않는다.

### Service execution

- 첫 service capsule은 read-only, stateless, secret-free로 제한한다.
- 동일 source에서 재현 가능한 artifact와 hash를 만들고 chain commitment와 일치할 때만 실행한다.
- filesystem read-only, CPU/RAM/시간 제한, outbound allowlist와 worker fence를 적용한다.
- push, reward, settlement, deletion, payment, provider API 호출은 초기 capsule에서 모두 끈다.
- write 요청은 특정 Mongo primary에 직접 쓰지 않고 사용자 서명과 idempotency key를 가진 event로 만든 뒤 BFT finality를 얻는다.
- 각 node의 DB는 authoritative 원장이 아니라 event로 다시 만들 수 있는 projection/cache가 된다.

### Discovery와 client failover

- DNS는 편의 경로이며 source of truth가 아니다.
- 앱에 서로 다른 failure domain의 bootstrap endpoint를 최소 3개 넣고 마지막 검증 manifest를 cache한다.
- peer manifest는 chain commitment, expiry, protocol version과 rollback counter를 검증한다.
- 메인 서버가 없어도 node끼리 manifest를 교환하고 앱이 healthy endpoint를 회전해야 한다.

## 5. 단계 전환

기존 Phase 1~7을 다음 vertical slice 순서로 보정한다.

1. **다중 replica encrypted storage**: 단일 `assignedNodeId`를 replica receipt set으로 확장하고 중앙 원본 없는 복구를 시험한다.
2. **메인 독립 manifest**: 공개키 검증, chain anchoring, peer 교환, expiry와 rollback 방지를 구현한다.
3. **동적 read-only RPC pool**: capability lease와 client 2-of-3 read 검증을 구현한다.
4. **stateless read service capsule**: 무비밀·worker-fenced package를 임의 적격 노드에서 실행한다.
5. **signed event write path**: 중앙 Mongo write를 기능별로 BFT event와 rebuildable projection으로 옮긴다.
6. **client endpoint 생존성**: Web/Android가 DNS 또는 메인 API 없이도 manifest를 검증하고 노드를 선택한다.
7. **외부 provider 기능 분리**: Firebase, 결제, OpenAI 등 중앙 secret이 필요한 기능은 threshold gateway나 교체 가능한 adapter가 준비될 때까지 제한 기능으로 표시한다.

각 단계는 고정 PC 수가 아니라 적격 node 수, replica 수, failure-domain 수와 검증 proof로 통과 여부를 결정한다.

## 6. 안전 경계

- arbitrary node에 production Mongo URI, Firebase account, API secret, TLS private key, validator/operator private key를 전달하지 않는다.
- node가 자기 capability를 주장한 사실만 믿지 않고 challenge와 결과 hash를 검증한다.
- 한 node의 응답을 authoritative result로 사용하지 않는다.
- service node capability와 validator voting 권한을 결합하지 않는다.
- 자동 traffic/write 승격은 storage 복구, manifest 독립성, quorum read와 split-brain test가 모두 끝난 뒤 별도 gate로 연다.

## 7. 지금의 판정

`ellia05` canary는 임의의 non-validator PC가 signed client를 받고 hardware/storage capability를 보고할 수 있다는 첫 증거다. 이 PC가 항상 server가 된다는 뜻은 아니다. 같은 protocol을 설치한 새 PC는 hostname과 무관하게 검증을 거쳐 pool에 들어와야 한다.

현재는 `main_server_outage_continuity_proven=false`다. 가장 먼저 구현할 실제 분산 기능은 서비스 복사본이 아니라 **ciphertext chunk의 동시 다중 복제와 메인 서버 없이 검증 가능한 manifest 교환**이다. 이것이 성공해야 임의 service node가 필요한 상태와 release를 안전하게 찾을 수 있다.

읽기 전용 gap 판정은 다음 명령으로 재현한다.

```powershell
node D:\aisfor-chain\scripts\resilience-permissionless-readiness.js
```

## 8. 2026-08-05 multi-replica staged implementation

첫 vertical slice의 server-side 호환 구현을 `E:\aisfor`에 staging했다.

- `models/DistributedChunk.js`: chunk 하나에 여러 node receipt를 담는 replica schema, 목표/건강 replica 수와 aggregate 상태 추가
- `services/distributedReplicaPolicy.js`: hostname과 무관한 distinct node 선택, feature-off 호환 replica 수, replica 상태 집계
- `routes/sync.js`: node별 task/download/stored/verify/restore receipt, 실패 node 비활성화와 새 node 재보충 경로
- 기존 PC Sync client의 task URL과 receipt body는 유지해 protocol 호환
- storage proof event key에 node ID를 포함해 동일 chunk의 여러 receipt가 서로 덮어쓰지 않도록 분리

활성화 flag는 `AISFOR_NODE_MULTI_REPLICA_ENABLED`이며 미설정 기본값은 기존 single replica다. 목표 수는 `AISFOR_NODE_REPLICA_COUNT`로 제한하며 2~5 범위, 현재 정책 목표는 3이다. 목표 수만큼 서로 다른 eligible node를 고르지 못하면 새 replicated chunk 생성은 fail-closed 한다.

현재 production `.env`에는 활성화 flag 선언이 없으므로 일반 사용자 chunk에 대한 다중 복제는 시작되지 않았다.

2026-08-05 제한 canary에서 production user data가 아닌 4 KiB synthetic ciphertext를 사용해 세 node receipt, 한 node의 논리 상실, 제4 node 재보충, 중앙 cache 보류 상태의 PC restore-upload와 plaintext round trip을 모두 증명했다. 비식별 증거는 `docs/resilience/evidence/multi-replica-canary-redacted.json`, 상세 판정은 `docs/resilience/06-MULTI_REPLICA_LIVE_CANARY_KO.md`에 있다.

이 시험 동안 replica 할당과 재보충 control plane은 메인 AISFOR에서 실행됐다. 따라서 통과 범위는 **실제 다중 storage data plane**이며 `main_server_outage_continuity_proven`은 여전히 false다. 다음 gate는 메인 독립 manifest 교환과 lease/repair 결정이다.
