// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module Crafter.Review:Schema_impl; import :Schema; import :Json; import std; namespace Crafter::Review::Schema { namespace { template Json Strings(const std::array& values) { Json out = Json::array(); for (const std::string_view value : values) out.push_back(value); return out; } Json String(std::string_view description = {}) { Json out = {{"type", "string"}}; if (!description.empty()) out["description"] = description; return out; } Json StringList(std::string_view description) { return {{"type", "array"}, {"items", {{"type", "string"}}}, {"description", description}}; } Json Verdict(std::string_view description) { Json out = {{"type", "string"}, {"enum", Strings(VerdictEnum)}}; if (!description.empty()) out["description"] = description; return out; } } Json Finding(bool kernel) { Json contests = { {"type", "object"}, {"additionalProperties", false}, {"required", {"id", "why"}}, {"properties", { {"id", String()}, {"why", String("NEW evidence the disposition's recorded grounds do not survive")}, }}, }; return { {"type", "object"}, {"additionalProperties", false}, {"required", {"finding", "evidence", "proposedAction", "items"}}, {"properties", { {"finding", String(kernel ? "the defect - by your lens's charter question (objective wrongness, or a predicted reviewer comment)" : "the defect - by your lens's charter question (objective wrongness)")}, {"evidence", String(kernel ? "file:line, msgid, or verbatim quote - no vibes" : "file:line, a verbatim quote, the steps taken in the application and what was observed (a screenshot or snapshot path) - no vibes")}, {"proposedAction", String()}, {"items", StringList("the INVENTORY unit ids this finding concerns, copied exactly; a finding that spans units lists them all")}, {"dischargedBy", String("id of a disposition listed under DISPOSITIONS IN FORCE that discharges this finding (you verified its grounds); omit when none applies")}, {"contests", contests}, }}, }; } Json Note(bool kernel) { Json properties = { {"kind", {{"type", "string"}, {"enum", kernel ? Strings(KernelNoteKinds) : Strings(NoteKinds)}}}, {"note", String()}, {"evidence", String()}, {"action", String(kernel ? "what the orchestrator does with it (bank / ledger / confirm at send / nothing)" : "what the orchestrator does with it (bank / record in the manifest / check at push time / nothing)")}, {"id", String(kernel ? "disposition-review and refutation-verified only: the disposition id" : "refutation-verified only: the disposition id")}, }; if (kernel) properties["wouldComment"] = {{"type", "boolean"}, {"description", "disposition-review only: would this reviewer comment on the discharged item as the artifact stands?"}}; properties["items"] = StringList("INVENTORY unit ids this note concerns"); return { {"type", "object"}, {"additionalProperties", false}, {"required", {"kind", "note", "evidence", "action"}}, {"properties", properties}, }; } Json Coverage(bool kernel) { return { {"type", "object"}, {"additionalProperties", false}, {"required", {"item", "verdict", "basis"}}, {"properties", { {"item", String("an INVENTORY unit id, verbatim")}, {"verdict", {{"type", "string"}, {"enum", kernel ? Strings(KernelCoverageVerdicts) : Strings(CoverageVerdicts)}}}, {"basis", String(kernel ? "what this verdict rests on: the source fetched / tree ref / units crossed / your own message-id - or the literal string \"no record\"" : "what this verdict rests on: the tree ref, the command run, the steps taken in the application and the evidence file - or the literal string \"no record\"")}, }}, }; } Json BlockSchema(bool kernel) { return { {"type", "object"}, {"additionalProperties", false}, {"required", {"verdict", "findings", "notes", "coverage"}}, {"properties", { {"verdict", Verdict("FIX FIRST iff any finding is undischarged - the pipeline derives the same and fails closed on disagreement")}, {"findings", {{"type", "array"}, {"items", Finding(kernel)}}}, {"notes", {{"type", "array"}, {"items", Note(kernel)}}}, {"coverage", {{"type", "array"}, {"items", Coverage(kernel)}, {"minItems", 1}, {"maxItems", 1}, {"description", "exactly one entry: the unit this pass covers"}}}, }}, }; } Json WholeSchema(bool kernel) { Json properties = { {"verdict", Verdict({})}, {"findings", {{"type", "array"}, {"items", Finding(kernel)}}}, {"notes", {{"type", "array"}, {"items", Note(kernel)}}}, {"coverage", {{"type", "array"}, {"items", Coverage(kernel)}, {"description", "one entry per unit you were asked to cover"}}}, {"summary", String(kernel ? "5-10 sentences: the artifact as you judged it and the constraints the block passes must respect; a persona adds its calibration digest (pet issues, phrasing, imperatives found in its own mail)" : "5-10 sentences: the range as you judged it and the constraints the block passes must respect (the functional lens: how to reach each touched screen, the login used, what the block passes must not touch)")}, }; if (kernel) { properties["predictedReview"] = String("persona lens only: the review this person would send, in their voice"); properties["notMine"] = StringList("persona lens only: unit ids that are not your tree, file or decision (each must carry a basis in coverage)"); } return { {"type", "object"}, {"additionalProperties", false}, {"required", {"verdict", "findings", "notes", "coverage", "summary"}}, {"properties", properties}, }; } std::string SchemaJson(std::string_view kind, std::string_view mode) { const bool kernel = mode == "reply" || mode == "series"; return DumpCompactSorted(kind == "block" ? BlockSchema(kernel) : WholeSchema(kernel)); } }