From e1aa9cba3e4bfd499810c3fc3a380389c752bb85 Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Wed, 5 Aug 2026 20:31:56 +0100 Subject: [PATCH] fix(ozone): upgrade moderation.getRepo to an owning decoder tools.ozone.moderation.getRepo has the identical ref-output shape as getRecord (a ref to tools.ozone.moderation.defs#repoView), which now has a generated decoder since the ref-output-decoder fix. Moves it from the QR (raw wf_response) to the Q (owning decoder) wrapper in the shared X-macro table, matching getRecord's pattern instead of leaving it as the last stale QR-vs-Q wrapper on the list. --- docs/roadmap.md | 11 +- include/wolfram/ozone_admin_typed.h | 52 ++++---- src/agent/ozone_admin_typed.c | 193 ++++++++++++++-------------- test/test_ozone_admin_typed.c | 42 +++++- 4 files changed, 167 insertions(+), 131 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 3849c9f..e64e19f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -464,9 +464,9 @@ tested). For what's still ahead, see [Next planned work](#next-planned-work). inspection alone. `tools.ozone.set.deleteSet` and `.querySets` — named in the originating issue as missing — turned out to already be wired through the same X-macro table; verified rather than duplicated. - `tools.ozone.moderation.getRepo` has the identical ref-output shape as - `getRecord` and the same stale QR-vs-Q wrapper, left as a follow-up - since it wasn't part of this endpoint list. `internal.bsky.actor. + `tools.ozone.moderation.getRepo` has the identical ref-output shape as + `getRecord` and was upgraded from the stale QR-vs-Q wrapper to an owning + decoder alongside it. `internal.bsky.actor. getProfiles` stays unwrapped by design (internal namespace, excluded from the reference codegen too). @@ -506,11 +506,6 @@ tested). For what's still ahead, see [Next planned work](#next-planned-work). ## Next planned work -- `tools.ozone.moderation.getRepo` (`ozone_admin_typed.h`/`.c`) still returns - a raw `wf_response` even though its output.schema is a `ref` to an object - def that now has a generated decoder (the same shape and the same fix - `getRecord` got in item 62) — a small, low-risk follow-up upgrade from QR - to Q in the shared X-macro table. - Exercise the gated live example test (`test_examples_live`) in CI with real credentials (it SKIPs cleanly when `BSKY_HANDLE`/`BSKY_PASSWORD` are unset). - Continue evaluating upstream C libraries for server-side infrastructure diff --git a/include/wolfram/ozone_admin_typed.h b/include/wolfram/ozone_admin_typed.h index cd0ae83..9b43510 100644 --- a/include/wolfram/ozone_admin_typed.h +++ b/include/wolfram/ozone_admin_typed.h @@ -24,9 +24,9 @@ * to tools.ozone.moderation.defs#recordViewDetail; wf_lexgen only * generates a decoder for that shape as of the ref-output-decoder fix, so * this was QR/raw wf_response until then) - * - moderation.getRepo (QR, raw wf_response output — its output.schema - * is also a `ref` to a def with an owning decoder now, but upgrading it - * is not part of this pass; left as a follow-up) + * - moderation.getRepo (Q, owning output — its output.schema + * is a `ref` to tools.ozone.moderation.defs#repoView, which now has a + * generated decoder, so it uses the owning output pattern like getRecord) * - moderation.cancelScheduledActions (PR, raw wf_response output) * - moderation.scheduleAction (PR, raw wf_response output) * @@ -57,37 +57,37 @@ extern "C" { /* ------------------------------------------------------------------ */ /* Query with params, owning output decoder. */ -#define WF_OZONE_ADMIN_DECL_Q(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ - wf_lex_tools_ozone_##ns##_##genop##_main_output **out); \ - wf_status wf_ozone_parse_##ns##_##op( \ - const char *json, size_t json_len, \ +#define WF_OZONE_ADMIN_DECL_Q(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ + wf_lex_tools_ozone_##ns##_##genop##_main_output **out); \ + wf_status wf_ozone_parse_##ns##_##op( \ + const char *json, size_t json_len, \ wf_lex_tools_ozone_##ns##_##genop##_main_output **out); /* Procedure with input, owning output decoder. */ -#define WF_OZONE_ADMIN_DECL_P(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ - wf_lex_tools_ozone_##ns##_##genop##_main_output **out); \ - wf_status wf_ozone_parse_##ns##_##op( \ - const char *json, size_t json_len, \ +#define WF_OZONE_ADMIN_DECL_P(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ + wf_lex_tools_ozone_##ns##_##genop##_main_output **out); \ + wf_status wf_ozone_parse_##ns##_##op( \ + const char *json, size_t json_len, \ wf_lex_tools_ozone_##ns##_##genop##_main_output **out); /* Procedure with input, raw wf_response output. */ -#define WF_OZONE_ADMIN_DECL_PR(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ +#define WF_OZONE_ADMIN_DECL_PR(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ wf_response *out); /* Query with params, raw wf_response output. */ -#define WF_OZONE_ADMIN_DECL_QR(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ +#define WF_OZONE_ADMIN_DECL_QR(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ wf_response *out); #define WF_OZONE_ADMIN_ENDPOINTS \ @@ -97,7 +97,7 @@ extern "C" { X(moderation, searchRepos, search_repos, Q) \ X(moderation, listScheduledActions, list_scheduled_actions, P) \ X(moderation, getRecord, get_record, Q) \ - X(moderation, getRepo, get_repo, QR) \ + X(moderation, getRepo, get_repo, Q) \ X(moderation, cancelScheduledActions, cancel_scheduled_actions, PR) \ X(moderation, scheduleAction, schedule_action, PR) diff --git a/src/agent/ozone_admin_typed.c b/src/agent/ozone_admin_typed.c index 66b17ca..35ede76 100644 --- a/src/agent/ozone_admin_typed.c +++ b/src/agent/ozone_admin_typed.c @@ -10,12 +10,13 @@ * used by the existing ozone_typed.c wrappers (moderation.get/query, * team.listMembers, etc.). The Ozone service endpoint is resolved by the * agent's XRPC client host; no new auth path is invented. Endpoints whose - * lexicon defines no owning output decoder (getRepo, cancelScheduledActions, + * lexicon defines no owning output decoder (cancelScheduledActions, * scheduleAction) return the raw wf_response, freed by the caller with - * wf_response_free. getRecord moved from that group to an owning decoder once - * wf_lexgen gained ref-to-object output support: its output.schema is a `ref` - * to tools.ozone.moderation.defs#recordViewDetail, which wf_lexgen did not - * generate a decoder for until that fix landed. + * wf_response_free. getRecord and getRepo moved from that group to owning + * decoders once wf_lexgen gained ref-to-object output support: their + * output.schema is a `ref` to tools.ozone.moderation.defs#recordViewDetail or + * #repoView, which wf_lexgen did not generate decoders for until that fix + * landed. */ #include "wolfram/ozone_admin_typed.h" @@ -30,102 +31,102 @@ /* Generated-decode wrapper definitions */ /* ------------------------------------------------------------------ */ -#define WF_OZONE_ADMIN_DEF_Q(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ - wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ - if (!agent || !agent->client || !params || !out) { \ - return WF_ERR_INVALID_ARG; \ - } \ - *out = NULL; \ - wf_lex_tools_ozone_##ns##_##genop##_main_output *dec = NULL; \ - wf_agent_sync_auth(agent); \ - wf_response res = {0}; \ - wf_status st = wf_lex_tools_ozone_##ns##_##genop##_main_call( \ - agent->client, params, &res); \ - if (st != WF_OK) { \ - wf_response_free(&res); \ - return st; \ - } \ - st = wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ - res.body, res.body_len, &dec); \ - wf_response_free(&res); \ - if (st == WF_OK) { \ - *out = dec; \ - } \ - return st; \ - } \ - wf_status wf_ozone_parse_##ns##_##op( \ - const char *json, size_t json_len, \ - wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ - if (!json || !out) { \ - return WF_ERR_INVALID_ARG; \ - } \ - return wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ - json, json_len, out); \ +#define WF_OZONE_ADMIN_DEF_Q(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ + wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ + if (!agent || !agent->client || !params || !out) { \ + return WF_ERR_INVALID_ARG; \ + } \ + *out = NULL; \ + wf_lex_tools_ozone_##ns##_##genop##_main_output *dec = NULL; \ + wf_agent_sync_auth(agent); \ + wf_response res = {0}; \ + wf_status st = wf_lex_tools_ozone_##ns##_##genop##_main_call( \ + agent->client, params, &res); \ + if (st != WF_OK) { \ + wf_response_free(&res); \ + return st; \ + } \ + st = wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ + res.body, res.body_len, &dec); \ + wf_response_free(&res); \ + if (st == WF_OK) { \ + *out = dec; \ + } \ + return st; \ + } \ + wf_status wf_ozone_parse_##ns##_##op( \ + const char *json, size_t json_len, \ + wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ + if (!json || !out) { \ + return WF_ERR_INVALID_ARG; \ + } \ + return wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ + json, json_len, out); \ } -#define WF_OZONE_ADMIN_DEF_P(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ - wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ - if (!agent || !agent->client || !input || !out) { \ - return WF_ERR_INVALID_ARG; \ - } \ - *out = NULL; \ - wf_lex_tools_ozone_##ns##_##genop##_main_output *dec = NULL; \ - wf_agent_sync_auth(agent); \ - wf_response res = {0}; \ - wf_status st = wf_lex_tools_ozone_##ns##_##genop##_main_call( \ - agent->client, input, &res); \ - if (st != WF_OK) { \ - wf_response_free(&res); \ - return st; \ - } \ - st = wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ - res.body, res.body_len, &dec); \ - wf_response_free(&res); \ - if (st == WF_OK) { \ - *out = dec; \ - } \ - return st; \ - } \ - wf_status wf_ozone_parse_##ns##_##op( \ - const char *json, size_t json_len, \ - wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ - if (!json || !out) { \ - return WF_ERR_INVALID_ARG; \ - } \ - return wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ - json, json_len, out); \ +#define WF_OZONE_ADMIN_DEF_P(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ + wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ + if (!agent || !agent->client || !input || !out) { \ + return WF_ERR_INVALID_ARG; \ + } \ + *out = NULL; \ + wf_lex_tools_ozone_##ns##_##genop##_main_output *dec = NULL; \ + wf_agent_sync_auth(agent); \ + wf_response res = {0}; \ + wf_status st = wf_lex_tools_ozone_##ns##_##genop##_main_call( \ + agent->client, input, &res); \ + if (st != WF_OK) { \ + wf_response_free(&res); \ + return st; \ + } \ + st = wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ + res.body, res.body_len, &dec); \ + wf_response_free(&res); \ + if (st == WF_OK) { \ + *out = dec; \ + } \ + return st; \ + } \ + wf_status wf_ozone_parse_##ns##_##op( \ + const char *json, size_t json_len, \ + wf_lex_tools_ozone_##ns##_##genop##_main_output **out) { \ + if (!json || !out) { \ + return WF_ERR_INVALID_ARG; \ + } \ + return wf_lex_tools_ozone_##ns##_##genop##_main_output_decode_json( \ + json, json_len, out); \ } -#define WF_OZONE_ADMIN_DEF_PR(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ - wf_response *out) { \ - if (!agent || !agent->client || !input || !out) { \ - return WF_ERR_INVALID_ARG; \ - } \ - wf_agent_sync_auth(agent); \ - return wf_lex_tools_ozone_##ns##_##genop##_main_call( \ - agent->client, input, out); \ +#define WF_OZONE_ADMIN_DEF_PR(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_input *input, \ + wf_response *out) { \ + if (!agent || !agent->client || !input || !out) { \ + return WF_ERR_INVALID_ARG; \ + } \ + wf_agent_sync_auth(agent); \ + return wf_lex_tools_ozone_##ns##_##genop##_main_call(agent->client, \ + input, out); \ } -#define WF_OZONE_ADMIN_DEF_QR(ns, op, genop) \ - wf_status wf_ozone_##ns##_##op( \ - wf_agent *agent, \ - const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ - wf_response *out) { \ - if (!agent || !agent->client || !params || !out) { \ - return WF_ERR_INVALID_ARG; \ - } \ - wf_agent_sync_auth(agent); \ - return wf_lex_tools_ozone_##ns##_##genop##_main_call( \ - agent->client, params, out); \ +#define WF_OZONE_ADMIN_DEF_QR(ns, op, genop) \ + wf_status wf_ozone_##ns##_##op( \ + wf_agent *agent, \ + const wf_lex_tools_ozone_##ns##_##genop##_main_params *params, \ + wf_response *out) { \ + if (!agent || !agent->client || !params || !out) { \ + return WF_ERR_INVALID_ARG; \ + } \ + wf_agent_sync_auth(agent); \ + return wf_lex_tools_ozone_##ns##_##genop##_main_call(agent->client, \ + params, out); \ } #define WF_OZONE_ADMIN_ENDPOINTS \ @@ -135,7 +136,7 @@ X(moderation, searchRepos, search_repos, Q) \ X(moderation, listScheduledActions, list_scheduled_actions, P) \ X(moderation, getRecord, get_record, Q) \ - X(moderation, getRepo, get_repo, QR) \ + X(moderation, getRepo, get_repo, Q) \ X(moderation, cancelScheduledActions, cancel_scheduled_actions, PR) \ X(moderation, scheduleAction, schedule_action, PR) diff --git a/test/test_ozone_admin_typed.c b/test/test_ozone_admin_typed.c index dfbb22f..1283232 100644 --- a/test/test_ozone_admin_typed.c +++ b/test/test_ozone_admin_typed.c @@ -126,6 +126,23 @@ static char *build_get_record_json(void) { return out; } +/* Build a tools.ozone.moderation.getRepo body via cJSON. Its output.schema + * is a `ref` to tools.ozone.moderation.defs#repoView, so the JSON body IS + * the repoView object directly (no wrapper key) — required fields: did, + * handle, indexedAt, moderation, relatedRecords. */ +static char *build_get_repo_json(void) { + cJSON *root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "did", "did:plc:repo000000000000000000"); + cJSON_AddStringToObject(root, "handle", "repo.example.com"); + cJSON_AddItemToObject(root, "relatedRecords", cJSON_CreateArray()); + cJSON_AddStringToObject(root, "indexedAt", "2026-01-01T00:00:00.000Z"); + cJSON_AddItemToObject(root, "moderation", cJSON_CreateObject()); + + char *out = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + return out; +} + /* Build a tools.ozone.moderation.listScheduledActions body via cJSON. */ static char *build_list_scheduled_actions_json(void) { cJSON *root = cJSON_CreateObject(); @@ -259,6 +276,26 @@ int main(void) { } } + /* ---- generated-decode path: getRepo (ref-typed output) ---- */ + { + char *json = build_get_repo_json(); + WF_CHECK(json != NULL); + wf_lex_tools_ozone_moderation_get_repo_main_output *out = NULL; + wf_status st = + wf_ozone_parse_moderation_getRepo(json, strlen(json), &out); + free(json); + WF_CHECK(st == WF_OK); + WF_CHECK(out != NULL); + if (out) { + WF_CHECK(out->did != NULL && + strcmp(out->did, "did:plc:repo000000000000000000") == 0); + WF_CHECK(out->handle != NULL && + strcmp(out->handle, "repo.example.com") == 0); + WF_CHECK(out->related_records.count == 0); + wf_lex_tools_ozone_moderation_get_repo_main_output_free(out); + } + } + /* ---- generated-decode path: listScheduledActions ---- */ { char *json = build_list_scheduled_actions_json(); @@ -336,7 +373,10 @@ int main(void) { WF_ERR_INVALID_ARG); wf_lex_tools_ozone_moderation_get_repo_main_params grp2 = {0}; - WF_CHECK(wf_ozone_moderation_getRepo(agent, &grp2, NULL) == + wf_lex_tools_ozone_moderation_get_repo_main_output *gro = NULL; + WF_CHECK(wf_ozone_moderation_getRepo(NULL, &grp2, &gro) == + WF_ERR_INVALID_ARG); + WF_CHECK(wf_ozone_moderation_getRepo(agent, NULL, &gro) == WF_ERR_INVALID_ARG); wf_lex_tools_ozone_moderation_list_scheduled_actions_main_output *lo = -- 2.51.2