feat(validation): staged-verification funnel — contract + execution gates
Gate 2 (contract) and Gate 4 (execution) of the 4-gate staged-verification design, plus the two gate-quality fixes found in live QA. - Gate 2: KindContractTable (kind→assertions registry, universal floor, additive project overrides) + KindInference (path→kind, pure/replay-safe) + ContractAssertion model; ContractGateEvaluatedEvent (registered); ContractAssertionEvaluator seam + FileSystemContractEvaluator (FS/TEXT, COMPILER skipped); runContractGate in runPostStageGates — a stage that produces file_written artifacts must have every declared+written file exist/non-empty/parse, else retryable hand-back with per-assertion feedback. - Option A: ExecutionPlanCompiler puts concrete (non-glob) plan `writes` into StageConfig.expectedFiles, so a declared-but-unwritten file fails file_exists. - Gate 4: BuildExpectation enum (none|module|project|tests) + plan field + closed-set compiler validation; runExecutionGate resolves the alias against the bound project profile commands and runs it as a deterministic gate. - Live-QA fixes: react_entry kind so main.tsx/index.tsx aren't required to export a default component (was an unwinnable false-positive); JSONC tolerance (allowComments/allowTrailingComma) so tsconfig.json passes valid_json. Seams are null on the replay harness (no-op) — recorded events are truth (#9).
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
||||
package com.correx.core.transitions.graph
|
||||
|
||||
/**
|
||||
* Per-stage execution-gate scope (staged-verification design §2) — the closed set a planner picks
|
||||
* from to say how far the project should be runnable after this stage. Resolves at runtime to a
|
||||
* concrete command drawn from the bound project profile's command aliases, so the command itself
|
||||
* stays stack-specific and operator-owned while the *scope* is a fixed vocabulary the planner can't
|
||||
* invent.
|
||||
*
|
||||
* [commandAlias] names the `[project.commands]` alias to run for this scope (null = no gate):
|
||||
* NONE → nothing (early stages where the project is not yet runnable), MODULE → typecheck,
|
||||
* PROJECT → build, TESTS → test. A stage whose profile lacks the alias skips the gate with a warning
|
||||
* rather than failing — the vocabulary degrades safely when the operator hasn't configured commands.
|
||||
*/
|
||||
enum class BuildExpectation(val commandAlias: String?) {
|
||||
NONE(null),
|
||||
MODULE("typecheck"),
|
||||
PROJECT("build"),
|
||||
TESTS("test"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
/** Parses a plan's `build_expectation` string; null for an unrecognized value so the compiler rejects it. */
|
||||
fun fromPlan(raw: String?): BuildExpectation? = when (raw?.trim()?.lowercase()) {
|
||||
null, "", "none" -> NONE
|
||||
"module" -> MODULE
|
||||
"project" -> PROJECT
|
||||
"tests", "test" -> TESTS
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,5 +28,15 @@ data class StageConfig(
|
||||
// A non-clean command fails the stage retryably with its output fed back, so only
|
||||
// static-clean output reaches the downstream reviewer. Empty = no static-first step.
|
||||
val staticAnalysis: List<String> = emptyList(),
|
||||
// Execution-gate scope for this stage (staged-verification §2). NONE = no build gate (default,
|
||||
// e.g. early scaffold/types stages); MODULE/PROJECT/TESTS resolve to the bound project profile's
|
||||
// typecheck/build/test command and run it as a deterministic Gate 4 after the stage produces.
|
||||
val buildExpectation: BuildExpectation = BuildExpectation.NONE,
|
||||
// Concrete (non-glob) workspace-relative paths this stage declares it will produce (Gate 2
|
||||
// Option A, staged-verification §3.2). The contract gate checks each declared file exists and
|
||||
// satisfies its inferred-kind contract, so a file the plan promised but the stage never wrote
|
||||
// FAILS the gate — catching missing files, which the runtime write-manifest alone (it only
|
||||
// inspects files that WERE written) cannot. Derived from the concrete entries of PlanStage.writes.
|
||||
val expectedFiles: List<String> = emptyList(),
|
||||
val metadata: Map<String, String> = emptyMap(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user