본문으로 건너뛰기
cccskills
무료GitHub에 공개

protobuf-schema-evolution

Reserved-field rules and field-number lookup for evolving AppSettings, plus the Rust/Kotlin diagnostics wire contract. Use before assigning or removing a proto field. DataStore mapping: protobuf-datastore.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md5.8 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Protobuf Schema Evolution

Why This Matters

AppSettings is serialized as protobuf bytes and persisted on disk via Jetpack DataStore. When the app updates, new code must read bytes written by every older version. A field-number collision silently corrupts data -- the user loses their configuration with no error and no recovery path.

The diagnostics engine adds a second dimension: Kotlin and Rust exchange JSON wire messages whose schemas must stay in lockstep. A field mismatch causes silent data loss or deserialization failures at runtime.

AppSettings Proto

Schema: core/data/model/src/main/proto/app_settings.proto

Never trust a cached reserved-number list or highest-field-number in this skill, in a prior PR, or from memory -- both grow with every schema change and a stale number is exactly the silent-corruption risk this skill exists to prevent. Before assigning any new field number, run both of these against the live schema:

grep -n reserved core/data/model/src/main/proto/app_settings.proto
grep -oE '= [0-9]+;' core/data/model/src/main/proto/app_settings.proto | grep -oE '[0-9]+' | sort -n | tail -1

The first command prints every reserved block (there may be more than one, each with a numbers line and a names line); the second prints the highest field number currently declared anywhere in the file. Assign the next number strictly above that highest number, and confirm it does not fall inside any reserved range first.

Rules

  1. Never reuse a field number. Proto3 identifies fields by number. If field 15 was desync_method and you assign 15 to something new, existing persisted bytes silently decode as the wrong field.

  2. Reserve removed fields. Add both the number AND name to reserved. This makes protoc reject future reuse at compile time.

  3. Use the next available number. Do not fill gaps from past removals.

  4. Set defaults in AppSettingsSerializer. Every new field needs a default in AppSettingsSerializer.defaultValue (core/data/.../AppSettingsSerializer.kt). Proto3 zero-values (0, false, "") are implicit; if the real default differs (e.g., proxy_port = 1080), the serializer is the only place it gets set.

  5. Validate string enums on read. Proto stores enum-like fields as plain strings ("vpn", "proxy"). Fall back to a safe default on unrecognized values.

Diagnostics Wire Contract

Rust (ripdpi-diagnostics-contracts, re-exported by ripdpi-monitor-engine) and Kotlin exchange JSON wire messages. Schema version is tracked by a constant that must be equal on both sides:

  • Rust: DIAGNOSTICS_ENGINE_SCHEMA_VERSION in native/rust/crates/ripdpi-diagnostics-contracts/src/wire.rs
  • Kotlin: DiagnosticsEngineSchemaVersion in core/diagnostics/.../contract/engine/EngineContract.kt

DiagnosticsContractGovernanceTest enforces equality by parsing the Rust source with Regex("""DIAGNOSTICS_ENGINE_SCHEMA_VERSION:\s*u32\s*=\s*(\d+)""").

Wire types (all in wire.rs / EngineContract.kt):

RustKotlinDirection
EngineScanRequestWireEngineScanRequestWireKotlin -> Rust
EngineScanReportWireEngineScanReportWireRust -> Kotlin
EngineProgressWireEngineProgressWireRust -> Kotlin

Golden Contract Tests

Three test files enforce Kotlin/Rust wire compatibility using shared fixtures in diagnostics-contract-fixtures/:

  • DiagnosticsWireContractTest (Kotlin) -- asserts schema version and field manifests (diagnostics_progress_fields.json, diagnostics_scan_report_fields.json) match Kotlin wire classes.
  • DiagnosticsContractGovernanceTest (Kotlin) -- decodes shared fixtures through Kotlin serializers, asserts schema version matches Rust, verifies bundled catalog matches committed fixture.
  • contract_fixtures.rs (Rust) -- decodes the same fixtures through serde_json, asserts schema version, verifies outcome taxonomy covers all emitted native outcome tokens.

After a wire change: update structs in Rust/Kotlin, run Rust tests to regenerate golden files (assert_contract_fixture writes on mismatch in update mode), then run Kotlin tests. Commit updated fixtures with the code.

Migration Patterns

Adding an AppSettings field

  1. Run the reserved/highest-number lookup above and pick the next available number. Never reuse a number cached in this skill, a past PR, or memory.
  2. Add to app_settings.proto.
  3. Set default in AppSettingsSerializer.defaultValue.
  4. Map in UI state conversions (toUiState(), toConfigDraft()).
  5. Existing users get proto3 zero-value on upgrade -- handle gracefully if the zero-value is not the desired default.

Removing an AppSettings field

  1. Remove field definition from app_settings.proto.
  2. Add field number to reserved numbers list.
  3. Add field name to reserved names list.
  4. Remove all Kotlin references (serializer, UI, ViewModel).

Adding a diagnostics wire field

  1. Add field with #[serde(default)] in Rust wire struct.
  2. Add defaulted field in Kotlin @Serializable data class.
  3. Regenerate golden fixture JSONs.
  4. If breaking (renamed/removed field, changed semantics), bump DIAGNOSTICS_ENGINE_SCHEMA_VERSION in both Rust and Kotlin.

Checklist for Schema PRs

  • No reused field numbers in app_settings.proto
  • Removed fields added to both reserved lines (numbers AND names)
  • Default set in AppSettingsSerializer.defaultValue
  • Wire changes have #[serde(default)] / Kotlin defaults
  • Schema version bumped if wire change is breaking
  • Golden contract fixtures updated
  • Both Rust (cargo test --locked -p ripdpi-monitor-engine) and Kotlin contract tests pass

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 19일 업데이트

ADB-based RIPDPI device/emulator debugging: build/install/launch, logcat filtering, fixture port-forwarding, instrumented tests, crash/ANR triage. Use when debugging on a real device or emulator.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 19일 업데이트

RIPDPI Appium launch-contract reference: start routes and permission/service/data presets. Use when a test launches to the wrong screen or a new automation route is added. General flakiness: appium-test-debug.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 19일 업데이트

RIPDPI Appium test authoring: page objects, resource-id locators, assertions, wait tiers. Use when writing a new Appium test or page object, or adding coverage for a new screen.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 19일 업데이트

RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 19일 업데이트

Add or modify a GitHub Actions job. Use when editing a file under .github/workflows/. Not for running a workflow locally (local-ci-act) or release signing (release-signing).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 19일 업데이트

po4yka의 스킬 모두 보기

이 스킬의 문제 신고