docs(proofline): approve line 0009 specification

This commit is contained in:
Hermes Agent
2026-08-29 14:59:37 +09:00
parent 7af6bdcf93
commit 3f94c19162
6 changed files with 127 additions and 16 deletions
+18
View File
@@ -0,0 +1,18 @@
---
id: "ac-0026"
status: active
---
# Slot occupancy와 독립된 strategy state lifecycle
## Criterion
`trex-core`는 슬롯의 strategy-state map과 각 entry를 slot occupancy lifecycle과 독립적으로 보존해야 한다. Slot이 어떤 경로로든 `INACTIVE`가 되어도 일반 lifecycle transition은 strategy state를 삭제하지 않아야 하며, inactive 상태와 이후 재점유 상태에서 저장된 entry와 version을 다시 읽을 수 있어야 한다. 재점유 뒤 같은 entry에 성공한 write는 close 전 version에 이어서 증가해야 한다. Strategy-state persistence schema, application model, store protocol, read filtering, `strategy_state.set` request/response 및 state CAS에는 `occupancy_seq`나 `expected_occupancy_seq`가 없어야 하며, occupancy 변경만으로 state를 숨기거나 write를 `occupancy_changed`로 거부해서는 안 된다. Slot 자체의 `occupancy_seq`, active-slot write gate, opaque data size limit, slot transition의 나머지 원자성과 별도 파괴적 전체 DB reset은 변경하지 않는다.
## Verification
- 보존 조건: active slot의 instance entry를 version 1 이상으로 저장한 뒤 전략 매도, safety-sell, forced-close 및 operator close를 포함한 각 `→ INACTIVE` 경로를 실행해도 저장소 entry와 version이 삭제되지 않는다. Inactive slot 조회와 이후 재점유 조회에서 같은 data·version이 보이고, 재점유 뒤 기존 version을 `expected_version`으로 사용한 성공 write가 version을 1 증가시킨다.
- 격리 조건: 한 slot의 lifecycle transition이 해당 slot map의 일부 또는 전체 entry를 삭제하거나 다른 slot의 state를 변경하면 FAIL이다. Close-delete hook 제거 전후로 slot update, transition log, broker·ledger side effect와 transition failure 원자성이 기존 계약에서 달라지지 않아야 한다.
- Occupancy 분리 조건: strategy-state migration/schema, dataclass, in-memory/PostgreSQL store contract, read path와 write RPC payload/response에 `occupancy_seq` 또는 `expected_occupancy_seq`가 남거나, occupancy mismatch read filtering 또는 `occupancy_changed` reject path가 실행되면 FAIL이다. Slot domain의 자체 `occupancy_seq`와 증가 규칙은 그대로 유지되어야 한다.
- 경계 조건: inactive slot에 대한 신규 state write는 계속 `slot_inactive`로 거부되고 기존 state는 변경되지 않는다. Size limit 초과 write도 `state_too_large`로 거부되며 보존 중인 entry를 손상시키지 않는다. 명시적 전체 DB reset은 기존 범위의 데이터를 계속 제거할 수 있다.
- 검사 방법: InMemory와 PostgreSQL fixtures에서 close 경로별 보존, inactive read, 재점유, version 연속성, write gate와 size limit parity를 검증한다. 기존 slot transition 회귀 테스트, persistence migration/round-trip 테스트, production daemon wiring, 전체 repository test와 정적 검사를 실행한다.
+19
View File
@@ -0,0 +1,19 @@
---
id: "ac-0027"
status: active
---
# Strategy-instance별 state identity와 version 격리
## Criterion
`trex-core`는 각 slot의 strategy state를 `data[strategy_instance_id] = {version, data, updated_at}` 형태의 하나의 slot-scoped map으로 저장해야 한다. `strategy_instance_id`는 entry의 저장·조회·CAS key이며 entry에 별도의 `strategy_id` metadata를 저장해서는 안 된다. `strategy_state.set`은 `{slot_id, strategy_instance_id, expected_version, data}`를 받아 required non-empty string instance ID, active slot, opaque data size limit와 해당 entry의 version만 검증해야 한다. Entry가 없을 때 `expected_version=0`인 성공 write는 version 1 entry를 만들고, 이후 성공 write는 그 entry의 version만 1 증가시켜야 한다. 같은 slot의 서로 다른 instance entry와 version은 서로 격리되어야 하며, 서로 다른 entry에 대한 동시 성공 write는 어느 entry도 유실하지 않고 모두 보존해야 한다. 기존 state에 대한 `strategy_id → strategy_instance_id` mapping·backfill·귀속 정책과 core-side instance registry를 도입해서는 안 된다.
## Verification
- 저장 형태: 한 slot에 instance A와 B를 저장하면 persistence round-trip 결과가 A·B key 아래 각각 `{version, data, updated_at}`를 보존하고 entry 내부에 `strategy_id`, `occupancy_seq` 또는 다른 identity metadata를 포함하지 않는다. Inner `data`는 입력한 opaque string과 byte 단위로 동일해야 한다.
- Version 조건: 새 A에 `expected_version=0` write는 A version 1을 만들고 후속 성공 write는 A만 증가시킨다. B의 version과 data는 변하지 않는다. 존재하는 A에 `expected_version=0` 또는 stale version을 보내면 `version_conflict`로 거부하고 A·B 어느 entry도 변경하지 않는다.
- 동시성 조건: 같은 slot의 A와 B에 동시에 유효한 write를 수행하면 둘 다 성공하고 두 entry의 새 data와 version이 모두 남아야 한다. 같은 entry에 경쟁하는 write는 version CAS에 따라 최대 한 건만 해당 expected version에서 성공하며 lost update가 없어야 한다.
- Identity 조건: 같은 전략 종류의 서로 다른 `strategy_instance_id`는 독립 entry를 사용하고 다른 ID의 state를 상속하지 않는다. 재시작 뒤 같은 ID를 사용하는 owner는 기존 entry를 이어 사용한다. 누락·비문자열·빈 ID write는 non-retryable `bad_request`로 거부되어 map을 만들거나 변경하지 않는다.
- 비변경 조건: active-slot gate와 data size limit를 우회하지 않으며 `slot_inactive`·`state_too_large`·`bad_request:*` 거부가 다른 entry를 변경하지 않는다. `correlation_id` 재사용만으로 write가 deduplicate되어서는 안 된다.
- 검사 방법: InMemory/PostgreSQL 공통 contract suite, 실제 PostgreSQL row-lock 또는 동등한 원자적 update fixture, migration 및 restart round-trip에서 create/update/conflict, same-type instance 격리, A·B 동시 write, same-entry 경쟁, malformed ID와 다른 entry 무변경을 검증한다.
+19
View File
@@ -0,0 +1,19 @@
---
id: "ac-0028"
status: active
---
# 전략·operator slots RPC의 strategy state 가시성
## Criterion
`trex-core`의 `slots.req`는 전략 요청과 operator 조회의 strategy-state 가시성을 구분해야 한다. `spy=false`인 전략용 요청은 required non-empty string `strategy_instance_id`를 받아 각 반환 slot에서 `data[request.strategy_instance_id]`와 정확히 일치하는 entry 하나만 `strategy_state: {version, data, updated_at}` 단일 object로 제공해야 한다. 요청 ID에 해당하는 entry가 없으면 `strategy_state` field를 생략하고 다른 instance의 ID·state 또는 전체 map을 대신 반환해서는 안 된다. 누락·비문자열·빈 ID는 RPC-level error나 새 envelope 없이 `slots=[]`, `available_slots=null`, `degraded=false`, `reason="bad_request:strategy_instance_id"`, `message="strategy_instance_id must be a non-empty string"`인 non-retryable response를 반환해야 한다. `spy=true`인 operator 요청은 `strategy_instance_id`를 요구하지 않고 기존 filter·slot 조회와 watchdog 비갱신 동작을 유지하며, 각 반환 slot의 `strategy_state`에 전체 `strategy_instance_id → {version, data, updated_at}` map을 제공해야 한다. Operator 요청에 `strategy_instance_id`가 포함되어도 map을 특정 entry로 제한해서는 안 된다.
## Verification
- 전략 조회: 같은 slot map에 A와 B가 있을 때 `spy=false, strategy_instance_id=A` 요청은 `strategy_state`에 A의 `{version, data, updated_at}`만 반환하고 B의 ID·data와 outer map을 serialized payload 어디에도 포함하지 않는다. B 요청은 B만 반환하며 존재하지 않는 C 요청은 slot row의 `strategy_state`를 생략한다.
- 잘못된 전략 요청: 누락, `null`, boolean, number, collection 및 빈 문자열 ID 각각에서 exact 기존 envelope인 `slots=[]`, `available_slots=null`, `degraded=false`, `reason="bad_request:strategy_instance_id"`, `message="strategy_instance_id must be a non-empty string"`을 반환한다. Slot, state 또는 다른 instance ID가 노출되거나 watchdog이 갱신되면 FAIL이다.
- Operator 조회: `strategy_instance_id` 없이 `spy=true`로 기존 `universe`와 `slots` CLI가 성공하고 filter 결과와 watchdog 비갱신을 유지한다. A·B state가 있는 slot row의 `strategy_state`는 A와 B를 각각 `{version, data, updated_at}` entry로 포함한 전체 map이어야 한다. 같은 operator 요청에 A ID를 추가해도 B가 제거되지 않아야 한다.
- 기존 계약 조합: AC-0023에 따라 제외된 slot은 strategy-state 가시성 형식을 적용하기 전에 계속 제외되고 warn-store failure는 기존 fail-closed 결과를 유지한다. 포함되는 holding slot의 broker-authoritative quantity와 unavailable metadata는 AC-0022를 유지하며 state 선택 때문에 바뀌지 않는다.
- Wire 경계: 전략 응답의 단일 `strategy_state` object 안에는 `strategy_instance_id` key나 collection을 두지 않는다. Operator map의 key만 instance ID를 나타내며 각 value에는 identity metadata를 중복 저장하지 않는다. 별도 operator/diagnostic API, core-side instance registry 또는 caller 인증을 새로 요구하지 않는다.
- 검사 방법: slots handler contract tests에서 전략 A/B/absent/malformed 요청과 operator no-ID/ID-present 요청을 serialized response 기준으로 검증한다. Operator CLI, watchdog, reconciliation exclusion, broker quantity, InMemory/PostgreSQL read parity와 production daemon handler wiring 회귀 테스트를 함께 실행한다.
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"schema_version": 1, "schema_version": 1,
"next_line_number": 10, "next_line_number": 10,
"next_ac_number": 26 "next_ac_number": 29
} }
+16 -15
View File
@@ -1,6 +1,6 @@
--- ---
id: "dcy-0009" id: "dcy-0009"
status: draft status: confirmed
--- ---
# Persistent strategy-instance state lifecycle and identity # Persistent strategy-instance state lifecycle and identity
@@ -35,18 +35,19 @@ status: draft
## Scope ## Scope
- lifecycle과 identity 변경을 하나의 Line으로 다룬다. - 이번 Line은 strategy state의 lifecycle을 slot occupancy에서 분리하고, state namespace의 identity를 `strategy_instance_id`로 전환하는 변경을 함께 다룬다.
- AC admission은 version-independent 동작 세 건의 **create**로 분류한다. - 이 변경의 Acceptance Criteria는 version-independent 동작을 검증하는 서로 독립적인 신규 AC 세 건으로 분리한다.
- close·재점유를 넘어 strategy state와 version을 삭제하지 않고 보존하는 lifecycle criterion - close·재점유를 넘어 strategy state와 version을 삭제하지 않고 보존하는 lifecycle criterion
- slot-scoped state map을 `strategy_instance_id`로 keying하고 같은 `strategy_id`의 instance별 state·version 격리를 보장하는 identity criterion - 슬롯별 state map에서 `strategy_instance_id`를 각 state entry의 key로 사용하고 같은 `strategy_id`의 instance별 state·version 격리를 보장하는 identity criterion
- `strategy_instance_id`를 전략용 request-scoped identity로 사용하여 `spy=false`인 `slots.req`가 해당 ID의 state만 반환하고 누락·비문자열·빈 ID 요청을 fail-closed하되 `spy=true` operator 조회는 기존 동작을 유지하는 criterion - `strategy_instance_id`를 전략용 request-scoped identity로 사용하여 `spy=false`인 `slots.req`가 해당 ID의 state만 반환하고 누락·비문자열·빈 ID 요청을 fail-closed하되, `spy=true`인 operator 조회는 `strategy_instance_id`를 요구하지 않고 watchdog을 갱신하지 않으며 각 슬롯의 전체 strategy-state map을 `strategy_instance_id`별 entry로 반환하는 criterion
- 기존 active AC의 `update`, `retire`, `satisfy`, `release evidence` 대상은 없다. 특히 `AC-0023`의 reconciliation warning 판정·slot 제외·intent blocking·clear 권한은 변경하지 않는다. Legacy `dcy-028`, REQ-073/074/079~082 및 runtime/interface baseline 문구는 새 AC가 승인된 뒤 approved change로 정합화할 대상이다. - 기존 active AC의 `update`, `retire`, `satisfy`, `release evidence` 대상은 없다. 특히 `AC-0023`의 reconciliation warning 판정·slot 제외·intent blocking·clear 권한은 변경하지 않는다.
- Legacy Discovery와 REQ는 업데이트 대상이 아니다. 현재 `docs/runtime-flows.md`와 `docs/interface-contract.md`의 strategy-state baseline 문구만 새 AC가 승인된 뒤 approved change로 정합화한다.
- 어떤 경로의 `→ INACTIVE` 전이에서도 일반 lifecycle hook이 slot-scoped strategy-state map이나 그 entry를 자동 삭제하지 않게 한다. Map은 inactive와 이후 재점유 상태에서도 유지하며 현재 occupancy와 무관하게 다시 볼 수 있게 한다. - 어떤 경로의 `→ INACTIVE` 전이에서도 일반 lifecycle hook이 slot-scoped strategy-state map이나 그 entry를 자동 삭제하지 않게 한다. Map은 inactive와 이후 재점유 상태에서도 유지하며 현재 occupancy와 무관하게 다시 볼 수 있게 한다.
- Core는 opaque `data`를 파싱하거나 전략별 session·reserve·peak의 유효성을 판단하지 않는다. State의 session identity와 내용을 이어 쓸지 초기화할지는 전략이 결정한다. - Core는 opaque `data`를 파싱하거나 전략별 session·reserve·peak의 유효성을 판단하지 않는다. State의 session identity와 내용을 이어 쓸지 초기화할지는 전략이 결정한다.
- `occupancy_seq`는 slot-scoped strategy-state persistence schema, `StrategyStateEntry`, store protocol, read filtering, `strategy_state.set` request/response와 state CAS에서 제거한다. Slot 자체의 `occupancy_seq`와 증가 규칙은 이 Line에서 변경하지 않지만 strategy-state persistence와 write 승인에는 사용하지 않는다. - `occupancy_seq`는 slot-scoped strategy-state persistence schema, `StrategyStateEntry`, store protocol, read filtering, `strategy_state.set` request/response와 state CAS에서 제거한다. Slot 자체의 `occupancy_seq`와 증가 규칙은 이 Line에서 변경하지 않지만 strategy-state persistence와 write 승인에는 사용하지 않는다.
- State write는 `expected_version`과 slot map의 `data[strategy_instance_id].version`만 비교한다. 해당 entry가 없을 때 `expected_version=0`, 성공 시 그 entry의 version만 기존 값에서 1 증가하며 close·reopen은 version을 초기화하지 않는다. 다른 instance entry와 version은 변경하지 않는다. 같은 slot에서 서로 다른 instance entry를 동시에 갱신해도 각 성공 write가 모두 보존되고 어느 entry도 유실되지 않아야 한다. Slot active-state 검사와 inner opaque data size limit는 유지한다. - State write는 `expected_version`과 slot map의 `data[strategy_instance_id].version`만 비교한다. 해당 entry가 없을 때 `expected_version=0`, 성공 시 그 entry의 version만 기존 값에서 1 증가하며 close·reopen은 version을 초기화하지 않는다. 다른 instance entry와 version은 변경하지 않는다. 같은 slot에서 서로 다른 instance entry를 동시에 갱신해도 각 성공 write가 모두 보존되고 어느 entry도 유실되지 않아야 한다. Slot active-state 검사와 inner opaque data size limit는 유지한다.
- 일반 lifecycle에는 strategy-state 삭제 동작을 두지 않는다. 기존의 파괴적 전체 DB reset은 별도 운영 기능으로 유지한다. - 일반 lifecycle에는 strategy-state 삭제 동작을 두지 않는다. 기존의 파괴적 전체 DB reset은 별도 운영 기능으로 유지한다.
- Persistence, in-memory model, dataclass, store protocol과 PostgreSQL query/update는 slot별 하나의 state map을 소유하고 그 map을 `strategy_instance_id`로 keying하도록 전환한다. 각 entry는 `{version, data, updated_at}`를 가진다. `strategy_instance_id`가 entry의 저장·조회 key이며 별도의 `strategy_id` metadata를 entry에 중복 저장하지 않는다. 여기서 inner `data`만 전략이 소유하는 opaque string이며 outer map과 entry envelope은 core contract다. 아래 JSON은 설명용 응답 예시가 아니라 entry 전체를 그대로 보존하는 canonical persistence shape이다. - Persistence, in-memory model, dataclass, store protocol과 PostgreSQL query/update는 슬롯별로 하나의 state map을 소유하고 `strategy_instance_id`를 각 state entry의 key로 사용하도록 전환한다. 각 entry는 `{version, data, updated_at}`를 가진다. `strategy_instance_id`가 entry의 저장·조회 key이며 별도의 `strategy_id` metadata를 entry에 중복 저장하지 않는다. 여기서 inner `data`만 전략이 소유하는 opaque string이며 outer map과 entry envelope은 core contract다. 아래 JSON은 설명용 응답 예시가 아니라 entry 전체를 그대로 보존하는 canonical persistence shape이다.
```json ```json
{ {
@@ -66,11 +67,10 @@ status: draft
``` ```
- 같은 `strategy_id`를 사용하는 서로 다른 `strategy_instance_id`는 동일 slot map에서도 독립된 entry와 version을 가져야 한다. 재시작 후 같은 instance ID를 사용하는 owner는 기존 entry를 이어 읽고, 다른 instance ID는 이를 상속하지 않는다. - 같은 `strategy_id`를 사용하는 서로 다른 `strategy_instance_id`는 동일 slot map에서도 독립된 entry와 version을 가져야 한다. 재시작 후 같은 instance ID를 사용하는 owner는 기존 entry를 이어 읽고, 다른 instance ID는 이를 상속하지 않는다.
- Strategy runtime 설정은 `strategy_instance_id`를 required non-empty string field로 가지며 startup에서 누락·비문자열·빈 값을 검증한다. 검증 실패 시 RPC worker와 decision cycle을 시작하지 않고 configuration error로 종료한다. 시작 후에는 이 값을 정규화하거나 변형하지 않는 immutable identity로 사용하며 요청별 override를 허용하지 않는다. 이 startup fail-closed는 strategy runtime이 소유하는 consumer contract이며, 이 Line의 trex-core 구현 범위에는 포함하지 않는다. - `strategy_state.set`은 `strategy_instance_id`를 필수 field로 받으며, core는 누락·비문자열·빈 값을 state 저장 없이 non-retryable `bad_request`로 거부한다. 별도의 core-side registry나 `instance_key ↔ strategy_instance_id` 목록 검증은 도입하지 않는다.
- `strategy_state.set`은 검증된 runtime 설정의 `strategy_instance_id`를 필수 field로 전송하고, core는 누락·비문자열·빈 값을 state 저장 없이 non-retryable `bad_request`로 거부한다. 별도의 core-side registry나 `instance_key ↔ strategy_instance_id` 목록 검증은 도입하지 않는다. - 저장 순서는 `slots.req` read → 전략의 opaque state 계산 → `strategy_state.set` write다. 변경 후 write payload는 `{slot_id, strategy_instance_id, expected_version, data}`이며, `expected_version`은 직전 `slots.req`의 자기 `strategy_state.version`을 사용하고 entry가 없을 때만 `0`을 사용한다. Core는 active slot·해당 entry의 version CAS·inner data size limit를 모두 통과한 경우에만 slot map의 `data[strategy_instance_id]`를 create/update하고 증가한 version을 반환한다.
- 저장 순서는 `slots.req` read → 전략의 opaque state 계산 → `strategy_state.set` write다. 변경 후 write payload는 #78 그대로 `{slot_id, strategy_instance_id, expected_version, data}`이며, `expected_version`은 직전 `slots.req`의 자기 `strategy_state.version`을 사용하고 entry가 없을 때만 `0`을 사용한다. Core는 active slot·해당 entry의 version CAS·inner data size limit를 모두 통과한 경우에만 slot map의 `data[strategy_instance_id]`를 create/update하고 증가한 version을 반환한다.
- `version_conflict`이면 전략은 추정한 값으로 재전송하지 않고 다음 `slots.req`에서 자기 state를 다시 읽어 재계산한다. `occupancy_changed` reason은 계약에서 제거한다. `slot_inactive`, `state_too_large`, `bad_request:*`는 해당 write를 적용하지 않는다. RPC `correlation_id`는 저장 멱등성 key로 사용하지 않는다. - `version_conflict`이면 전략은 추정한 값으로 재전송하지 않고 다음 `slots.req`에서 자기 state를 다시 읽어 재계산한다. `occupancy_changed` reason은 계약에서 제거한다. `slot_inactive`, `state_too_large`, `bad_request:*`는 해당 write를 적용하지 않는다. RPC `correlation_id`는 저장 멱등성 key로 사용하지 않는다.
- `spy=false`인 전략용 `slots.req` 요청은 검증된 runtime 설정의 `strategy_instance_id`를 필수 field로 전송한다. 누락·비문자열·빈 값이면 core는 RPC-level error나 새 envelope을 만들지 않고 기존 `slots.req` response envelope으로 다음 non-retryable `bad_request`를 반환한다. `reason="bad_request:strategy_instance_id"`가 retry하지 않을 configuration error의 machine-readable 판별값이며, `slots=[]`이므로 어떤 slot이나 `strategy_state`도 노출하지 않는다. - `spy=false`인 전략용 `slots.req` 요청은 `strategy_instance_id`를 필수 field로 받는다. 누락·비문자열·빈 값이면 core는 RPC-level error나 새 envelope을 만들지 않고 기존 `slots.req` response envelope으로 다음 non-retryable `bad_request`를 반환한다. `reason="bad_request:strategy_instance_id"`가 retry하지 않을 configuration error의 machine-readable 판별값이며, `slots=[]`이므로 어떤 slot이나 `strategy_state`도 노출하지 않는다.
```json ```json
{ {
@@ -95,11 +95,11 @@ status: draft
} }
``` ```
- 이 single-object response는 #79의 canonical wire contract다. 응답의 `strategy_state` 안에는 `strategy_instance_id` key나 다른 instance를 열거하는 collection을 두지 않는다. - 이 single-object response는 canonical wire contract다. 응답의 `strategy_state` 안에는 `strategy_instance_id` key나 다른 instance를 열거하는 collection을 두지 않는다.
- 같은 `instance_key`에 여러 strategy instance가 연결되어도 요청 A에는 `data[A]`만, 요청 B에는 `data[B]`만 반환한다. Core는 이를 위해 strategy-instance 목록 registry를 유지하지 않고 요청의 필수 `strategy_instance_id`를 map lookup key로만 사용한다. - 같은 `instance_key`에 여러 strategy instance가 연결되어도 요청 A에는 `data[A]`만, 요청 B에는 `data[B]`만 반환한다. Core는 이를 위해 strategy-instance 목록 registry를 유지하지 않고 요청의 필수 `strategy_instance_id`를 map lookup key로만 사용한다.
- `spy=true`인 operator `slots.req`는 `strategy_instance_id`를 요구하지 않고 기존 filter·slot 조회와 watchdog 비갱신 동작을 유지한다. Operator 응답의 각 `slots[]` row에는 `strategy_state` field를 포함하지 않으며, 요청에 `strategy_instance_id`가 있더라도 전체 map이나 특정 entry를 노출하지 않는다. 전체 strategy-state 조회는 별도 operator/diagnostic API를 신설하지 않는다는 Out of Scope를 유지한다. - `spy=true`인 operator `slots.req`는 `strategy_instance_id`를 요구하지 않고 기존 filter·slot 조회와 watchdog 비갱신 동작을 유지한다. Operator 응답의 각 `slots[]` row에는 해당 슬롯의 전체 strategy-state map을 `strategy_instance_id`별 entry로 구성한 `strategy_state` field를 포함한다. 요청에 `strategy_instance_id`가 있더라도 operator 조회 결과를 특정 entry로 제한하지 않는다. 이 동작은 기존 `slots.req`를 사용하며 별도 operator/diagnostic API를 신설하지 않는다.
- 기존 state에 대해 별도의 `strategy_id → strategy_instance_id` mapping, backfill 또는 instance 귀속 정책을 두지 않는다. 변경된 slot-scoped `data[strategy_instance_id]` persistence contract를 그대로 적용하며, 이를 별도 migration 결정 사항으로 다시 열지 않는다. - 기존 state에 대해 별도의 `strategy_id → strategy_instance_id` mapping, backfill 또는 instance 귀속 정책을 두지 않는다. 변경된 slot-scoped `data[strategy_instance_id]` persistence contract를 그대로 적용하며, 이를 별도 migration 결정 사항으로 다시 열지 않는다.
- Verification은 close 후 map 보존, inactive/read visibility, 재점유 후 entry version 연속 증가, state persistence·model·RPC에서 `occupancy_seq` 부재, entry별 version conflict 거부와 다른 entry 무변경, 같은 종류의 두 instance 격리, 같은 slot의 서로 다른 A·B entry에 대한 동시 성공 write가 모두 보존되는 조건, 동일 slot map에 A·B state가 있을 때 전략 요청 A에는 `data[A]`만/전략 요청 B에는 `data[B]`만 반환되고 다른 ID와 전체 map이 비노출되는 조건, 요청 ID에 해당하는 state가 없을 때 `strategy_state` field 생략, 정상 configured ID read/write, 요청별 ID override 불가, 누락·비문자열·빈 `spy=false` 요청 ID가 기존 envelope의 `slots=[]`, `degraded=false`, `reason="bad_request:strategy_instance_id"`로 반환되어 slot·state를 노출하지 않는 조건과 같은 malformed state-write ID의 저장 거부, `strategy_instance_id` 없는 `spy=true` operator CLI의 기존 slot 조회 성공·watchdog 비갱신·모든 row에서 `strategy_state` 비노출, InMemory/PostgreSQL parity 및 production daemon wiring을 포함한다. Strategy runtime의 startup fail-closed는 consumer repository의 contract verification 대상으로 남긴다. - Verification은 close 후 map 보존, inactive/read visibility, 재점유 후 entry version 연속 증가, state persistence·model·RPC에서 `occupancy_seq` 부재, entry별 version conflict 거부와 다른 entry 무변경, 같은 종류의 두 instance 격리, 같은 slot의 서로 다른 A·B entry에 대한 동시 성공 write가 모두 보존되는 조건, 동일 slot map에 A·B state가 있을 때 전략 요청 A에는 `data[A]`만/전략 요청 B에는 `data[B]`만 반환되고 다른 ID와 전체 map이 비노출되는 조건, 요청 ID에 해당하는 state가 없을 때 `strategy_state` field 생략, 누락·비문자열·빈 `spy=false` 요청 ID가 기존 envelope의 `slots=[]`, `degraded=false`, `reason="bad_request:strategy_instance_id"`로 반환되어 slot·state를 노출하지 않는 조건과 같은 malformed state-write ID의 저장 거부, `strategy_instance_id` 없는 `spy=true` operator CLI의 기존 slot 조회 성공·watchdog 비갱신·각 슬롯의 전체 strategy-state map 반환, InMemory/PostgreSQL parity 및 production daemon wiring을 포함한다.
## Out of Scope ## Out of Scope
@@ -110,6 +110,7 @@ status: draft
- Operator/diagnostic용 전체 strategy-state 조회 API 신설. - Operator/diagnostic용 전체 strategy-state 조회 API 신설.
- Strategy-state를 audit/event store로 전환하거나 row history, exactly-once write, multi-writer merge를 도입하는 기능. - Strategy-state를 audit/event store로 전환하거나 row history, exactly-once write, multi-writer merge를 도입하는 기능.
- `AC-0023`·`AC-0024`·`AC-0025`의 reconciliation warning 및 알림 lifecycle 변경. - `AC-0023`·`AC-0024`·`AC-0025`의 reconciliation warning 및 알림 lifecycle 변경.
- Strategy consumer 통합. Consumer는 통합 전제 조건으로 runtime 설정에 required non-empty string `strategy_instance_id`를 두고 startup에서 누락·비문자열·빈 값을 검증해야 한다. 검증 실패 시 RPC worker와 decision cycle을 시작하지 않고 configuration error로 종료하며, 시작 후에는 ID를 정규화·변형하거나 요청별로 override하지 않아야 한다. 이 동작과 검증은 각 consumer repository가 소유한다.
- TVR-v2 daemon 또는 다른 전략 repository의 구현·배포. 이 Line은 trex-core persistence와 wire contract만 소유한다. - TVR-v2 daemon 또는 다른 전략 repository의 구현·배포. 이 Line은 trex-core persistence와 wire contract만 소유한다.
## Risks and Unknowns ## Risks and Unknowns
@@ -118,7 +119,7 @@ status: draft
- Close-delete와 occupancy-filtered read를 제거하면 slot-scoped map과 그 entry가 장기간 남는다. 같은 `strategy_instance_id`의 반복 write는 새 entry를 늘리지 않고 기존 entry를 갱신한다. - Close-delete와 occupancy-filtered read를 제거하면 slot-scoped map과 그 entry가 장기간 남는다. 같은 `strategy_instance_id`의 반복 write는 새 entry를 늘리지 않고 기존 entry를 갱신한다.
- Lifecycle, key/wire와 strategy configuration 변경을 부분적으로 배포하면 기존 consumer와 새 core의 contract가 어긋날 수 있으므로 core와 strategy consumer를 lockstep으로 배포해야 한다. - Lifecycle, key/wire와 strategy configuration 변경을 부분적으로 배포하면 기존 consumer와 새 core의 contract가 어긋날 수 있으므로 core와 strategy consumer를 lockstep으로 배포해야 한다.
- `strategy_instance_id` startup 검증이 일부 strategy entrypoint에서 누락되면 잘못된 설정으로 실행될 수 있다. 실제 production entrypoint가 worker 생성 전에 동일한 validation을 거치는지 contract test로 고정해야 한다. - Consumer가 통합 전제 조건인 `strategy_instance_id` startup 검증과 불변 사용을 구현하지 않으면 잘못된 namespace로 요청할 수 있다. 이 위험의 완화와 contract test는 각 consumer repository가 소유한다.
- `slots.req` handler가 storage의 전체 map을 먼저 조립한 뒤 consumer 쪽에서 필터링하면 wire 노출은 해소되지 않는다. Core의 조회 또는 response 조립 단계에서 요청 ID 하나만 선택하고 serialized payload에 다른 ID가 없는지 검증해야 한다. - `slots.req` handler가 storage의 전체 map을 먼저 조립한 뒤 consumer 쪽에서 필터링하면 wire 노출은 해소되지 않는다. Core의 조회 또는 response 조립 단계에서 요청 ID 하나만 선택하고 serialized payload에 다른 ID가 없는지 검증해야 한다.
- Strategy-state에서 occupancy fence를 제거하므로 close·reopen 자체는 이전 read에서 계산된 write를 거부하는 근거가 아니다. Core는 state 내용을 해석하지 않으며 `strategy_instance_id` namespace와 version CAS만 보장한다. Occupancy/session 전환에 따라 data를 초기화해야 하는 전략은 opaque state 내부 규칙으로 처리해야 한다. - Strategy-state에서 occupancy fence를 제거하므로 close·reopen 자체는 이전 read에서 계산된 write를 거부하는 근거가 아니다. Core는 state 내용을 해석하지 않으며 `strategy_instance_id` namespace와 version CAS만 보장한다. Occupancy/session 전환에 따라 data를 초기화해야 하는 전략은 opaque state 내부 규칙으로 처리해야 한다.
- `SlotService.transit_slot()`의 close-delete hook을 제거할 때 slot transition의 나머지 원자성과 실패 동작이 바뀌지 않아야 한다. - `SlotService.transit_slot()`의 close-delete hook을 제거할 때 slot transition의 나머지 원자성과 실패 동작이 바뀌지 않아야 한다.
+54
View File
@@ -0,0 +1,54 @@
---
id: "req-0009"
status: approved
discovery: "dcy-0009"
criteria:
create:
- "ac-0026"
- "ac-0027"
- "ac-0028"
update: []
retire: []
satisfy: []
---
# Persistent strategy-instance state lifecycle and identity
## Objective
`trex-core`의 opaque strategy state를 slot occupancy lifecycle과 `strategy_id` namespace에서 분리한다. 상태를 슬롯별 `strategy_instance_id` entry로 영속화하여 close와 재점유 뒤에도 같은 instance가 기존 state와 version을 이어 쓰게 하고, 서로 다른 instance의 state와 CAS를 격리한다. 전략용 `slots.req`에는 요청한 instance의 state 하나만 제공하고 operator `spy=true` 조회에는 슬롯의 전체 instance map을 제공하되, 각 경로의 identity 검증·watchdog·가시성 계약을 명확히 유지한다.
## Scope
- 어떤 경로로 slot이 `INACTIVE`가 되어도 일반 lifecycle hook이 해당 슬롯의 strategy-state map이나 entry를 자동 삭제하지 않으며, inactive 및 재점유 뒤에도 state와 version을 보존한다.
- Strategy-state persistence, application model, store protocol, read filtering, write RPC와 CAS에서 `occupancy_seq` 및 `expected_occupancy_seq`를 제거한다. Slot 자체의 `occupancy_seq`와 증가 규칙은 유지한다.
- 슬롯별 strategy-state를 `data[strategy_instance_id] = {version, data, updated_at}` 형태의 map으로 저장한다. Entry에는 `strategy_id`를 중복 저장하지 않으며 inner `data`만 core가 해석하지 않는 opaque string이다.
- `strategy_state.set` 입력을 `{slot_id, strategy_instance_id, expected_version, data}`로 정의한다. 해당 instance entry의 version만 CAS하고, 서로 다른 instance의 성공한 동시 write를 모두 보존한다.
- `spy=false` 전략용 `slots.req`는 required non-empty string `strategy_instance_id`를 받고, 각 slot에서 요청 ID와 정확히 일치하는 entry 하나만 `strategy_state: {version, data, updated_at}`로 반환한다. 해당 entry가 없으면 `strategy_state`를 생략한다.
- 누락·비문자열·빈 `strategy_instance_id`를 받은 전략용 `slots.req`는 기존 response envelope에서 `slots=[]`, `available_slots=null`, `degraded=false`, `reason="bad_request:strategy_instance_id"`, `message="strategy_instance_id must be a non-empty string"`을 반환한다. 같은 malformed ID의 state write는 저장 없이 non-retryable `bad_request`로 거부한다.
- `spy=true` operator `slots.req`는 `strategy_instance_id`를 요구하지 않고 기존 filter·slot 조회와 watchdog 비갱신 동작을 유지한다. 각 slot row에는 전체 strategy-state map을 `strategy_instance_id`별 entry로 반환하며, 요청에 ID가 있어도 특정 entry로 제한하지 않는다.
- Persistence와 RPC 변경을 InMemory/PostgreSQL parity, production daemon wiring 및 repository-local automated tests로 검증한다.
## Constraints
- State write는 active slot 검사, inner opaque data size limit 및 해당 entry의 `expected_version` CAS를 모두 통과해야 한다. Entry가 없을 때만 `expected_version=0`이며 성공할 때 해당 entry의 version만 1 증가한다.
- `version_conflict`는 write를 적용하지 않으며 다른 entry를 변경하지 않는다. Consumer는 추정 version으로 재전송하지 않고 다음 `slots.req`에서 자기 state를 다시 읽어 재계산한다.
- `occupancy_changed`는 strategy-state reject reason에서 제거한다. `slot_inactive`, `state_too_large`, `bad_request:*`는 write를 적용하지 않고 RPC `correlation_id`를 저장 멱등성 key로 사용하지 않는다.
- Core는 opaque state의 session 의미, 만료, 초기화 또는 merge를 판단하지 않으며 core-side strategy-instance registry, `instance_key ↔ strategy_instance_id` 목록 및 caller 인증을 도입하지 않는다.
- 기존 state에 대한 `strategy_id → strategy_instance_id` mapping, backfill 또는 instance 귀속 정책을 도입하지 않고 신규 persistence contract를 그대로 적용한다.
- 같은 `strategy_instance_id`는 재시작 뒤에도 같은 논리적 전략 instance가 사용하는 안정적 identity다. Consumer 통합은 각 consumer repository가 소유하며 required non-empty string 설정, startup fail-closed, 불변 사용 및 요청별 override 금지를 전제한다.
- AC-0023의 reconciliation warning 판정·slot 제외·intent blocking·clear 권한과 AC-0022의 strategy-facing broker quantity 계약을 변경하지 않는다.
- Legacy Discovery와 REQ는 업데이트하지 않는다. 새 AC 승인 뒤 현재 `docs/runtime-flows.md`와 `docs/interface-contract.md`의 strategy-state baseline 문구만 approved change에 맞게 정합화한다.
- Normative Design Documents: 없음. 이 REQ와 AC-0026~AC-0028이 이번 specification의 normative behavior를 소유한다.
## Non-Goals
- `profit_buffer`, portfolio 공통 모델, slot occupancy state machine 또는 Slot 자체의 `occupancy_seq`와 증가 규칙 변경
- TVR-v2 reserve·cost basis·processed execution, TSL peak/session 또는 다른 전략의 opaque `data` schema 정의
- Core가 strategy state의 session 만료·occupancy 귀속·초기화 여부를 판단하는 기능
- Core-side instance registry, strategy 목록 저장, caller 인증, AMQP producer 권한 또는 queue topology 변경
- 별도 operator/diagnostic strategy-state API 신설
- Strategy-state history, audit/event store, exactly-once write 또는 multi-writer merge
- Strategy consumer repository의 구현·배포 및 production rollout
- 기존 파괴적 전체 DB reset 제거 또는 변경
- Legacy Discovery·REQ 및 AC-0023~AC-0025의 lifecycle·본문 변경