// 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() { 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("the defect - by your lens's charter question (objective wrongness)")}, {"evidence", String("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() { return { {"type", "object"}, {"additionalProperties", false}, {"required", {"kind", "note", "evidence", "action"}}, {"properties", { {"kind", {{"type", "string"}, {"enum", Strings(NoteKinds)}}}, {"note", String()}, {"evidence", String()}, {"action", String("what the orchestrator does with it (bank / record in the manifest / check at push time / nothing)")}, {"id", String("refutation-verified only: the disposition id")}, {"items", StringList("INVENTORY unit ids this note concerns")}, }}, }; } Json Coverage() { return { {"type", "object"}, {"additionalProperties", false}, {"required", {"item", "verdict", "basis"}}, {"properties", { {"item", String("an INVENTORY unit id, verbatim")}, {"verdict", {{"type", "string"}, {"enum", Strings(CoverageVerdicts)}}}, {"basis", String("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() { 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()}}}, {"notes", {{"type", "array"}, {"items", Note()}}}, {"coverage", {{"type", "array"}, {"items", Coverage()}, {"minItems", 1}, {"maxItems", 1}, {"description", "exactly one entry: the unit this pass covers"}}}, }}, }; } Json WholeSchema() { return { {"type", "object"}, {"additionalProperties", false}, {"required", {"verdict", "findings", "notes", "coverage", "summary"}}, {"properties", { {"verdict", Verdict({})}, {"findings", {{"type", "array"}, {"items", Finding()}}}, {"notes", {{"type", "array"}, {"items", Note()}}}, {"coverage", {{"type", "array"}, {"items", Coverage()}, {"description", "one entry per unit you were asked to cover"}}}, {"summary", String("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)")}, }}, }; } std::string SchemaJson(std::string_view kind) { return DumpCompactSorted(kind == "block" ? BlockSchema() : WholeSchema()); } }