Complete Step 485: worker dispatch validation
This commit is contained in:
@@ -3244,4 +3244,13 @@ target_link_libraries(step484_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step485_test tests/step485_test.cpp)
|
||||
target_include_directories(step485_test PRIVATE src)
|
||||
target_link_libraries(step485_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
145
editor/src/WorkerDispatchValidator.h
Normal file
145
editor/src/WorkerDispatchValidator.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
// Step 485: Worker Dispatch + Result Validation
|
||||
// Models architect->worker handoff, result validation, retry budget, and escalation.
|
||||
|
||||
#include "PriorStepContextInjector.h"
|
||||
#include "ProjectConventionAnalyzer.h"
|
||||
#include "StepSpecExpander.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct WorkerSubmission {
|
||||
std::vector<SourceFileDraft> files;
|
||||
bool buildPassed = false;
|
||||
bool testsPassed = false;
|
||||
std::string errorOutput;
|
||||
};
|
||||
|
||||
struct WorkerDispatchInput {
|
||||
std::string stepMarkdown;
|
||||
std::string stepNumber;
|
||||
std::string componentName;
|
||||
std::string buildSystem = "cmake";
|
||||
std::string repoRoot = ".";
|
||||
std::vector<std::string> priorHeaders;
|
||||
};
|
||||
|
||||
struct DispatchAttemptReport {
|
||||
int attempt = 0;
|
||||
bool accepted = false;
|
||||
bool buildPassed = false;
|
||||
bool testsPassed = false;
|
||||
ConventionValidationReport conventionReport;
|
||||
std::string status;
|
||||
};
|
||||
|
||||
struct WorkerDispatchResult {
|
||||
StepSpec spec;
|
||||
StepContext context;
|
||||
ProjectConventions conventions;
|
||||
std::vector<DispatchAttemptReport> attempts;
|
||||
bool completed = false;
|
||||
bool escalated = false;
|
||||
int retryBudget = 3;
|
||||
int attemptsUsed = 0;
|
||||
std::string finalStatus;
|
||||
};
|
||||
|
||||
class WorkerDispatchValidator {
|
||||
public:
|
||||
static WorkerDispatchResult run(const WorkerDispatchInput& in,
|
||||
const std::vector<WorkerSubmission>& submissions,
|
||||
int retryBudget = 3) {
|
||||
WorkerDispatchResult out;
|
||||
out.retryBudget = retryBudget <= 0 ? 1 : retryBudget;
|
||||
|
||||
out.spec = StepSpecExpander::expand({
|
||||
in.stepMarkdown,
|
||||
in.stepNumber,
|
||||
in.componentName,
|
||||
in.buildSystem,
|
||||
{}, // conventions are extracted from repo state
|
||||
in.priorHeaders
|
||||
});
|
||||
out.context = PriorStepContextInjector::build(in.priorHeaders);
|
||||
out.conventions = ProjectConventionAnalyzer::extract(in.repoRoot);
|
||||
|
||||
if (submissions.empty()) {
|
||||
out.escalated = true;
|
||||
out.finalStatus = "No worker submissions provided";
|
||||
return out;
|
||||
}
|
||||
|
||||
const int maxAttempts = out.retryBudget < (int)submissions.size()
|
||||
? out.retryBudget : (int)submissions.size();
|
||||
for (int i = 0; i < maxAttempts; ++i) {
|
||||
DispatchAttemptReport report;
|
||||
report.attempt = i + 1;
|
||||
report.buildPassed = submissions[i].buildPassed;
|
||||
report.testsPassed = submissions[i].testsPassed;
|
||||
report.conventionReport =
|
||||
ProjectConventionAnalyzer::validate(out.conventions, submissions[i].files);
|
||||
|
||||
const bool conventionsOk = report.conventionReport.violations.empty();
|
||||
report.accepted = report.buildPassed && report.testsPassed && conventionsOk;
|
||||
report.status = classifyAttemptStatus(report, conventionsOk, submissions[i].errorOutput);
|
||||
out.attempts.push_back(report);
|
||||
out.attemptsUsed = i + 1;
|
||||
|
||||
if (report.accepted) {
|
||||
out.completed = true;
|
||||
out.finalStatus = "Step completed";
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
out.escalated = true;
|
||||
out.finalStatus = out.attemptsUsed >= out.retryBudget
|
||||
? "Retry budget exhausted; escalate to architect"
|
||||
: "All submissions consumed without completion";
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string buildWorkerBrief(const WorkerDispatchInput& in,
|
||||
const StepSpec& spec,
|
||||
const StepContext& context,
|
||||
const ProjectConventions& conventions) {
|
||||
std::ostringstream out;
|
||||
out << "Step " << in.stepNumber << ": " << in.componentName << "\n";
|
||||
out << "Complexity: " << complexityLabel(spec.complexity) << "\n";
|
||||
out << "Tests planned: " << spec.testPlan.size() << "\n";
|
||||
out << "Header target: " << spec.headerSkeleton.fileName << "\n";
|
||||
out << "Build target: " << spec.buildEntry.targetName << "\n";
|
||||
out << "Conventions: " << conventions.classNaming << ", "
|
||||
<< conventions.methodNaming << ", " << conventions.fileNaming << "\n";
|
||||
out << "Context brief: " << context.condensedBrief() << "\n";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string classifyAttemptStatus(const DispatchAttemptReport& report,
|
||||
bool conventionsOk,
|
||||
const std::string& errorOutput) {
|
||||
if (!report.buildPassed) {
|
||||
return "build_failed" + (errorOutput.empty() ? "" : ": " + errorOutput);
|
||||
}
|
||||
if (!report.testsPassed) {
|
||||
return "tests_failed" + (errorOutput.empty() ? "" : ": " + errorOutput);
|
||||
}
|
||||
if (!conventionsOk) return "convention_warnings";
|
||||
return "accepted";
|
||||
}
|
||||
|
||||
static std::string complexityLabel(StepComplexity c) {
|
||||
switch (c) {
|
||||
case StepComplexity::Trivial: return "trivial";
|
||||
case StepComplexity::Standard: return "standard";
|
||||
case StepComplexity::Complex: return "complex";
|
||||
case StepComplexity::Pipeline: return "pipeline";
|
||||
}
|
||||
return "standard";
|
||||
}
|
||||
};
|
||||
165
editor/tests/step485_test.cpp
Normal file
165
editor/tests/step485_test.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
// Step 485: Worker Dispatch + Result Validation Tests (unit, integration, negative)
|
||||
|
||||
#include "WorkerDispatchValidator.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
static WorkerDispatchInput sampleInput() {
|
||||
WorkerDispatchInput in;
|
||||
in.stepMarkdown = "Orchestrate worker dispatch pipeline and validate outputs.";
|
||||
in.stepNumber = "485";
|
||||
in.componentName = "WorkerDispatchValidator";
|
||||
in.repoRoot = ".";
|
||||
in.priorHeaders = {
|
||||
"editor/src/StepSpecExpander.h",
|
||||
"editor/src/ProjectConventionAnalyzer.h",
|
||||
"editor/src/PriorStepContextInjector.h"
|
||||
};
|
||||
return in;
|
||||
}
|
||||
|
||||
static SourceFileDraft goodHeader() {
|
||||
return {"editor/src/DispatchOutput.h", "struct DispatchOutput { int x; }; \n"};
|
||||
}
|
||||
|
||||
static SourceFileDraft goodTest() {
|
||||
return {"editor/tests/step999_test.cpp",
|
||||
"#define CHECK(a,b) do{}while(0)\nint main(){ CHECK(true, \"ok\"); return 0; }\n"};
|
||||
}
|
||||
|
||||
static SourceFileDraft goodCmake() {
|
||||
return {"editor/CMakeLists.txt",
|
||||
"add_executable(step999_test tests/step999_test.cpp)\n"};
|
||||
}
|
||||
|
||||
void test_dispatch_prepares_spec_context_and_conventions() {
|
||||
TEST(dispatch_prepares_spec_context_and_conventions);
|
||||
WorkerSubmission s{{goodHeader(), goodTest(), goodCmake()}, true, true, ""};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {s});
|
||||
CHECK(!out.spec.testPlan.empty(), "expected non-empty test plan");
|
||||
CHECK(!out.context.priorApis.empty(), "expected prior api context");
|
||||
CHECK(out.conventions.buildSystem == "cmake", "expected cmake conventions");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_accepts_submission_when_build_tests_and_conventions_pass() {
|
||||
TEST(accepts_submission_when_build_tests_and_conventions_pass);
|
||||
WorkerSubmission s{{goodHeader(), goodTest(), goodCmake()}, true, true, ""};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {s});
|
||||
CHECK(out.completed, "expected completed dispatch");
|
||||
CHECK(!out.escalated, "did not expect escalation");
|
||||
CHECK(out.attemptsUsed == 1, "expected one attempt");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_retries_after_test_failure_then_succeeds() {
|
||||
TEST(retries_after_test_failure_then_succeeds);
|
||||
WorkerSubmission bad{{goodHeader(), goodTest(), goodCmake()}, true, false, "1 failing test"};
|
||||
WorkerSubmission good{{goodHeader(), goodTest(), goodCmake()}, true, true, ""};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {bad, good}, 3);
|
||||
CHECK(out.completed, "expected success after retry");
|
||||
CHECK(out.attemptsUsed == 2, "expected two attempts used");
|
||||
CHECK(out.attempts[0].status.find("tests_failed") == 0, "first status should be tests_failed");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_escalates_when_retry_budget_exhausted() {
|
||||
TEST(escalates_when_retry_budget_exhausted);
|
||||
WorkerSubmission bad{{goodHeader(), goodTest(), goodCmake()}, false, false, "build break"};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {bad, bad, bad}, 2);
|
||||
CHECK(!out.completed, "should not complete");
|
||||
CHECK(out.escalated, "expected escalation");
|
||||
CHECK(out.attemptsUsed == 2, "should stop at retry budget");
|
||||
CHECK(out.finalStatus.find("Retry budget exhausted") != std::string::npos,
|
||||
"expected retry budget status");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_convention_violations_are_reported_and_not_accepted() {
|
||||
TEST(convention_violations_are_reported_and_not_accepted);
|
||||
SourceFileDraft badTest{"editor/tests/step999_test.cpp", "int main(){ return 0; }\n"};
|
||||
WorkerSubmission s{{goodHeader(), badTest, goodCmake()}, true, true, ""};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {s}, 1);
|
||||
CHECK(!out.completed, "should not accept convention violations");
|
||||
CHECK(out.escalated, "should escalate for architect decision");
|
||||
CHECK(out.attempts[0].conventionReport.hasViolation("TEST_ASSERTION_MISSING"),
|
||||
"expected test assertion violation");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_build_failure_status_contains_error_output() {
|
||||
TEST(build_failure_status_contains_error_output);
|
||||
WorkerSubmission s{{goodHeader(), goodTest(), goodCmake()}, false, false, "linker error"};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {s}, 1);
|
||||
CHECK(out.attempts[0].status.find("build_failed") == 0, "missing build_failed status");
|
||||
CHECK(out.attempts[0].status.find("linker error") != std::string::npos,
|
||||
"missing error payload");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_no_submissions_immediately_escalates_negative_case() {
|
||||
TEST(no_submissions_immediately_escalates_negative_case);
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {}, 3);
|
||||
CHECK(out.escalated, "expected escalation");
|
||||
CHECK(!out.completed, "should not complete");
|
||||
CHECK(out.finalStatus.find("No worker submissions") != std::string::npos,
|
||||
"missing no submission message");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_default_retry_budget_is_three() {
|
||||
TEST(default_retry_budget_is_three);
|
||||
WorkerSubmission bad{{goodHeader(), goodTest(), goodCmake()}, false, false, "x"};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {bad, bad, bad, bad});
|
||||
CHECK(out.retryBudget == 3, "default budget mismatch");
|
||||
CHECK(out.attemptsUsed == 3, "should use three attempts");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_worker_brief_contains_core_handoff_sections() {
|
||||
TEST(worker_brief_contains_core_handoff_sections);
|
||||
WorkerSubmission s{{goodHeader(), goodTest(), goodCmake()}, true, true, ""};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {s});
|
||||
auto brief = WorkerDispatchValidator::buildWorkerBrief(
|
||||
sampleInput(), out.spec, out.context, out.conventions);
|
||||
CHECK(brief.find("Complexity:") != std::string::npos, "missing complexity");
|
||||
CHECK(brief.find("Tests planned:") != std::string::npos, "missing test plan count");
|
||||
CHECK(brief.find("Context brief:") != std::string::npos, "missing context brief");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_acceptance_requires_both_build_and_tests_even_with_clean_conventions() {
|
||||
TEST(acceptance_requires_both_build_and_tests_even_with_clean_conventions);
|
||||
WorkerSubmission s{{goodHeader(), goodTest(), goodCmake()}, true, false, ""};
|
||||
auto out = WorkerDispatchValidator::run(sampleInput(), {s}, 1);
|
||||
CHECK(!out.completed, "must not complete when tests fail");
|
||||
CHECK(out.escalated, "expected escalation");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 485: Worker Dispatch + Result Validation Tests\n";
|
||||
|
||||
test_dispatch_prepares_spec_context_and_conventions(); // 1
|
||||
test_accepts_submission_when_build_tests_and_conventions_pass(); // 2
|
||||
test_retries_after_test_failure_then_succeeds(); // 3
|
||||
test_escalates_when_retry_budget_exhausted(); // 4
|
||||
test_convention_violations_are_reported_and_not_accepted(); // 5
|
||||
test_build_failure_status_contains_error_output(); // 6
|
||||
test_no_submissions_immediately_escalates_negative_case(); // 7
|
||||
test_default_retry_budget_is_three(); // 8
|
||||
test_worker_brief_contains_core_handoff_sections(); // 9
|
||||
test_acceptance_requires_both_build_and_tests_even_with_clean_conventions(); // 10
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||
<< " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
42
progress.md
42
progress.md
@@ -6988,3 +6988,45 @@ headers.
|
||||
- `editor/src/PriorStepContextInjector.h` within header-size limit (`125` <= `600`)
|
||||
- `editor/tests/step484_test.cpp` within test-file size guidance (`120` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 485: Worker Dispatch + Result Validation
|
||||
**Status:** PASS (10/10 tests)
|
||||
|
||||
Implements the architect->worker dispatch protocol model for Phase 23c:
|
||||
prepare spec/context/conventions, validate worker output, enforce retry
|
||||
budget, and escalate unresolved submissions to architect review.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/WorkerDispatchValidator.h` — dispatch orchestration model:
|
||||
- `WorkerDispatchInput`, `WorkerSubmission`, `DispatchAttemptReport`, `WorkerDispatchResult`
|
||||
- `run(...)`:
|
||||
- builds `StepSpec` via `StepSpecExpander`
|
||||
- builds `StepContext` via `PriorStepContextInjector`
|
||||
- extracts `ProjectConventions` via `ProjectConventionAnalyzer`
|
||||
- validates each worker submission (build/tests/conventions)
|
||||
- retries up to configurable budget (default `3`)
|
||||
- marks completion only when build+tests pass and no convention warnings
|
||||
- escalates on exhausted retries or missing submissions
|
||||
- `buildWorkerBrief(...)` condensed handoff text for worker prompts
|
||||
- `editor/tests/step485_test.cpp` — 10 tests (unit/integration/negative):
|
||||
- pre-dispatch artifact generation (spec/context/conventions)
|
||||
- successful first-attempt acceptance
|
||||
- retry-after-failure success path
|
||||
- retry-budget exhaustion escalation
|
||||
- convention-violation reporting path
|
||||
- build-failure status propagation
|
||||
- empty-submission negative case
|
||||
- default retry-budget behavior
|
||||
- worker brief content checks
|
||||
- acceptance gate requiring both build and tests
|
||||
- `editor/CMakeLists.txt` — `step485_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake --build editor/build-native --target step485_test step484_test` — PASS
|
||||
- `./editor/build-native/step485_test` — PASS (10/10)
|
||||
- `./editor/build-native/step484_test` — PASS (8/8) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/WorkerDispatchValidator.h` within header-size limit (`145` <= `600`)
|
||||
- `editor/tests/step485_test.cpp` within test-file size guidance (`165` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user