Skip to content

Commit abf5126

Browse files
authored
Merge pull request #198 from runcycles/fix/getreservation-committed-metadata
fix(reservation): surface committed metadata on getReservation (v0.1.25.34, closes #197)
2 parents 851db21 + d2b69c4 commit abf5126

7 files changed

Lines changed: 258 additions & 39 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ cycles-protocol-service/cycles-protocol-service-model/.project
5151

5252
# Claude Code local state (not for commit)
5353
.claude/scheduled_tasks.lock
54+
.codex-worktrees/

AUDIT.md

Lines changed: 182 additions & 37 deletions
Large diffs are not rendered by default.

cycles-protocol-service/cycles-protocol-service-api/src/test/java/io/runcycles/protocol/api/ReservationLifecycleIntegrationTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,37 @@ void shouldReturnReleasedAmountWhenActualLessThanReserved() {
265265
assertThat(resp.getBody().get("released")).isNotNull();
266266
}
267267

268+
@Test
269+
void shouldSurfaceCommittedMetadataOnGetReservation() {
270+
// End-to-end round-trip (real Redis + commit.lua, not mocked): a commit
271+
// carrying metadata persists committed_metadata_json, and getReservation
272+
// returns it as committed_metadata (cycles-server#197).
273+
String reservationId = createReservationAndGetId(TENANT_A, API_KEY_SECRET_A, 1000);
274+
Map<String, Object> body = commitBody(800);
275+
body.put("metadata", Map.of("request_id", "req-abc-123"));
276+
post("/v1/reservations/" + reservationId + "/commit", API_KEY_SECRET_A, body);
277+
278+
ResponseEntity<Map> resp = get("/v1/reservations/" + reservationId, API_KEY_SECRET_A);
279+
280+
assertThat(resp.getStatusCode().value()).isEqualTo(200);
281+
@SuppressWarnings("unchecked")
282+
Map<String, Object> committedMetadata = (Map<String, Object>) resp.getBody().get("committed_metadata");
283+
assertThat(committedMetadata).isNotNull().containsEntry("request_id", "req-abc-123");
284+
}
285+
286+
@Test
287+
void shouldOmitCommittedMetadataWhenCommitHadNone() {
288+
// NON_NULL wire omission: a commit without metadata leaves the field
289+
// absent from the JSON entirely, not present-as-null.
290+
String reservationId = createReservationAndGetId(TENANT_A, API_KEY_SECRET_A, 1000);
291+
post("/v1/reservations/" + reservationId + "/commit", API_KEY_SECRET_A, commitBody(800));
292+
293+
ResponseEntity<Map> resp = get("/v1/reservations/" + reservationId, API_KEY_SECRET_A);
294+
295+
assertThat(resp.getStatusCode().value()).isEqualTo(200);
296+
assertThat(resp.getBody().containsKey("committed_metadata")).isFalse();
297+
}
298+
268299
@Test
269300
void shouldRejectCommitWithUnitMismatch() {
270301
String reservationId = createReservationAndGetId(TENANT_A, API_KEY_SECRET_A, 1000);

cycles-protocol-service/cycles-protocol-service-data/src/main/java/io/runcycles/protocol/data/repository/RedisReservationRepository.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1915,7 +1915,16 @@ private ReservationDetail buildReservationSummary(Map<String, String> fields) th
19151915
metadata = objectMapper.readValue(metadataJson, Map.class);
19161916
}
19171917

1918-
ReservationDetail detail = new ReservationDetail(committed, finalizedAtMs, metadata);
1918+
// Parse commit-time metadata if present. commit.lua persists the COMMIT
1919+
// request's metadata as committed_metadata_json; surface it as
1920+
// committed_metadata so it is readable, not write-only (cycles-server#197).
1921+
Map<String, Object> committedMetadata = null;
1922+
String committedMetadataJson = fields.get("committed_metadata_json");
1923+
if (committedMetadataJson != null && !committedMetadataJson.isEmpty()) {
1924+
committedMetadata = objectMapper.readValue(committedMetadataJson, Map.class);
1925+
}
1926+
1927+
ReservationDetail detail = new ReservationDetail(committed, finalizedAtMs, metadata, committedMetadata);
19191928
detail.setReservationId(fields.get("reservation_id"));
19201929
detail.setStatus(Enums.ReservationStatus.valueOf(stateStr));
19211930
detail.setIdempotencyKey(fields.get("idempotency_key"));

cycles-protocol-service/cycles-protocol-service-data/src/test/java/io/runcycles/protocol/data/repository/RedisReservationCrudTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,38 @@ void shouldParseMetadata() {
150150
assertThat(detail.getMetadata()).containsEntry("model", "gpt-4");
151151
}
152152

153+
@Test
154+
void shouldParseCommittedMetadata() {
155+
// commit.lua persists the COMMIT request's metadata as
156+
// committed_metadata_json; getReservation must surface it as
157+
// committed_metadata (cycles-server#197), distinct from reserve metadata.
158+
when(jedisPool.getResource()).thenReturn(jedis);
159+
doNothing().when(jedis).close();
160+
Map<String, String> fields = reservationFields("res-cmeta", "COMMITTED");
161+
fields.put("metadata_json", "{\"phase\":\"reserve\"}");
162+
fields.put("committed_metadata_json", "{\"request_id\":\"req-abc-123\"}");
163+
when(jedis.hgetAll("reservation:res_res-cmeta")).thenReturn(fields);
164+
165+
ReservationDetail detail = repository.getReservationById("res-cmeta");
166+
167+
assertThat(detail.getCommittedMetadata()).isNotNull();
168+
assertThat(detail.getCommittedMetadata()).containsEntry("request_id", "req-abc-123");
169+
// reserve-time metadata stays distinct
170+
assertThat(detail.getMetadata()).containsEntry("phase", "reserve");
171+
}
172+
173+
@Test
174+
void shouldOmitCommittedMetadataWhenAbsent() {
175+
when(jedisPool.getResource()).thenReturn(jedis);
176+
doNothing().when(jedis).close();
177+
Map<String, String> fields = reservationFields("res-nocmeta", "ACTIVE");
178+
when(jedis.hgetAll("reservation:res_res-nocmeta")).thenReturn(fields);
179+
180+
ReservationDetail detail = repository.getReservationById("res-nocmeta");
181+
182+
assertThat(detail.getCommittedMetadata()).isNull();
183+
}
184+
153185
@Test
154186
void shouldThrowOnCorruptedData() {
155187
when(jedisPool.getResource()).thenReturn(jedis);

cycles-protocol-service/cycles-protocol-service-model/src/main/java/io/runcycles/protocol/model/ReservationDetail.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ public class ReservationDetail extends ReservationSummary {
1313
@Valid @JsonProperty("committed") private Amount committed;
1414
@JsonProperty("finalized_at_ms") private Long finalizedAtMs;
1515
@JsonProperty("metadata") private Map<String, Object> metadata;
16+
@JsonProperty("committed_metadata") private Map<String, Object> committedMetadata;
1617
}

cycles-protocol-service/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<module>cycles-protocol-service-api</module>
1919
</modules>
2020
<properties>
21-
<revision>0.1.25.33</revision>
21+
<revision>0.1.25.34</revision>
2222
<java.version>21</java.version>
2323
<maven.compiler.source>21</maven.compiler.source>
2424
<maven.compiler.target>21</maven.compiler.target>

0 commit comments

Comments
 (0)