mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
refactor(validate): separate factual proof from semantic judgment
This commit is contained in:
@@ -28,10 +28,21 @@
|
||||
"gate": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "lane", "argv", "backing"],
|
||||
"required": ["id", "lane", "proof_kind", "argv", "backing"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1, "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
||||
"lane": { "enum": ["mandatory", "diagnostic", "release"] },
|
||||
"proof_kind": {
|
||||
"enum": [
|
||||
"syntax",
|
||||
"schema",
|
||||
"identity",
|
||||
"paths",
|
||||
"generated_drift",
|
||||
"executable_assertion",
|
||||
"evidence_integrity"
|
||||
]
|
||||
},
|
||||
"argv": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
|
||||
@@ -141,9 +141,19 @@
|
||||
"preflightError": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["code", "detail"],
|
||||
"required": ["code", "defect_class", "detail"],
|
||||
"properties": {
|
||||
"code": { "type": "string", "minLength": 1 },
|
||||
"defect_class": {
|
||||
"enum": [
|
||||
"candidate_integrity",
|
||||
"evidence_integrity",
|
||||
"registry_integrity",
|
||||
"toolchain_integrity",
|
||||
"validator_integrity",
|
||||
"request_integrity"
|
||||
]
|
||||
},
|
||||
"detail": { "type": "string", "minLength": 1 }
|
||||
}
|
||||
},
|
||||
@@ -161,10 +171,21 @@
|
||||
"gateExecution": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "lane", "candidate", "attribution"],
|
||||
"required": ["id", "lane", "proof_kind", "candidate", "attribution"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"lane": { "enum": ["mandatory", "diagnostic", "release"] },
|
||||
"proof_kind": {
|
||||
"enum": [
|
||||
"syntax",
|
||||
"schema",
|
||||
"identity",
|
||||
"paths",
|
||||
"generated_drift",
|
||||
"executable_assertion",
|
||||
"evidence_integrity"
|
||||
]
|
||||
},
|
||||
"candidate": { "$ref": "#/$defs/factualResult" },
|
||||
"baseline": { "$ref": "#/$defs/factualResult" },
|
||||
"attribution": { "enum": ["not_applicable", "candidate_introduced", "pre_existing"] }
|
||||
|
||||
@@ -72,10 +72,21 @@
|
||||
"selectedGate": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "lane", "entry_sha256"],
|
||||
"required": ["id", "lane", "proof_kind", "entry_sha256"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"lane": { "enum": ["mandatory", "diagnostic", "release"] },
|
||||
"proof_kind": {
|
||||
"enum": [
|
||||
"syntax",
|
||||
"schema",
|
||||
"identity",
|
||||
"paths",
|
||||
"generated_drift",
|
||||
"executable_assertion",
|
||||
"evidence_integrity"
|
||||
]
|
||||
},
|
||||
"entry_sha256": { "$ref": "#/$defs/digest" }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -243,15 +243,18 @@ add_step() {
|
||||
}
|
||||
|
||||
if [[ "${#SOURCE_SKILLS[@]}" -gt 0 ]]; then
|
||||
audit_cmd=""
|
||||
integrity_cmd=""
|
||||
for source_skill in "${SOURCE_SKILLS[@]}"; do
|
||||
printf -v source_target '%q' "skills/$source_skill"
|
||||
if [[ -n "$audit_cmd" ]]; then
|
||||
audit_cmd+=" && "
|
||||
if [[ -n "$integrity_cmd" ]]; then
|
||||
integrity_cmd+=" && "
|
||||
fi
|
||||
audit_cmd+="bash skills/heal-skill/scripts/audit.sh --strict $source_target"
|
||||
integrity_cmd+="bash skills/heal-skill/scripts/heal.sh --check --strict $source_target"
|
||||
done
|
||||
add_step "changed skill deep conformance|$audit_cmd|$audit_cmd"
|
||||
# Changed-scope release proof owns structural package integrity only. Content
|
||||
# quality and trigger usefulness are semantic evidence for the independent
|
||||
# validator; they must not become a blocking keyword or prose-score gate.
|
||||
add_step "changed skill structural integrity|$integrity_cmd|$integrity_cmd"
|
||||
fi
|
||||
|
||||
if $NEED_CONTEXT_MAP; then
|
||||
|
||||
@@ -951,8 +951,8 @@
|
||||
{
|
||||
"name": "validate",
|
||||
"source_skill": "skills/validate",
|
||||
"source_hash": "f9e93a7eba879fbc19dd23100524778a5239f9ee18ca1500c0821796e041ca83",
|
||||
"generated_hash": "014ac9fd434556b30080d39ad570f371d062c431a4066ccb1a44b88c13abc388"
|
||||
"source_hash": "37a96331e00095a5938604c80d705aaedd7796116c7c725efc2ea564c642656b",
|
||||
"generated_hash": "6c4777ac971820b8ad164c28fdff6739d1a1f4a85ea76660d286e815de0ed812"
|
||||
},
|
||||
{
|
||||
"name": "workflow-builder",
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"generator": "codex-sync",
|
||||
"source_skill": "skills/validate",
|
||||
"layout": "modular",
|
||||
"source_hash": "f9e93a7eba879fbc19dd23100524778a5239f9ee18ca1500c0821796e041ca83",
|
||||
"generated_hash": "014ac9fd434556b30080d39ad570f371d062c431a4066ccb1a44b88c13abc388"
|
||||
"source_hash": "37a96331e00095a5938604c80d705aaedd7796116c7c725efc2ea564c642656b",
|
||||
"generated_hash": "6c4777ac971820b8ad164c28fdff6739d1a1f4a85ea76660d286e815de0ed812"
|
||||
}
|
||||
|
||||
@@ -52,6 +52,13 @@ though it retains baseline attribution. Green mandatory proof routes to one
|
||||
fresh validator by default. Inventory size is never a rigor or validator-count
|
||||
signal.
|
||||
|
||||
Every factual registry entry declares one closed `proof_kind`: syntax, schema,
|
||||
identity, paths, generated drift, executable assertion, or evidence integrity.
|
||||
Semantic prose scores and exact-wording preferences are reviewer evidence, not
|
||||
factual gate kinds. Missing backing is a typed `registry_integrity` defect;
|
||||
neither that defect nor a semantic observation may be mislabeled as candidate
|
||||
proof.
|
||||
|
||||
The portable freezer, runner, and receipt verifier is
|
||||
`python3 skills/validate/scripts/validation-request.py freeze|run|check-receipt --help`.
|
||||
|
||||
|
||||
@@ -119,6 +119,24 @@ skill count, changed-file count, or any other inventory count never selects
|
||||
rigor or validator count. Risk and explicit mode selection are separate policy
|
||||
inputs outside this request foundation.
|
||||
|
||||
### Deterministic and semantic boundary
|
||||
|
||||
Factual registry entries declare exactly one `proof_kind`: `syntax`, `schema`,
|
||||
`identity`, `paths`, `generated_drift`, `executable_assertion`, or
|
||||
`evidence_integrity`. The request and receipt retain that kind so consumers can
|
||||
audit what the command was allowed to prove. A registry entry cannot declare
|
||||
semantic prose quality, usefulness, preferred wording, or reviewer judgment as
|
||||
machine proof.
|
||||
|
||||
Missing or stale gate registration, entry identity, or backing is a typed
|
||||
`registry_integrity` defect and stops before execution. It is not evidence that
|
||||
the candidate failed. Deterministic gates may still verify that a semantic
|
||||
verdict is present, schema-valid, independently authored, and bound to the
|
||||
candidate; only the independent reviewer decides whether its reasoning is
|
||||
correct.
|
||||
An advisory semantic observation never becomes deterministic authority. It
|
||||
never blocks delivery by being promoted into a strict prose score.
|
||||
|
||||
The closed wire formats are:
|
||||
|
||||
- `schemas/validation-candidate.v1.schema.json`
|
||||
|
||||
@@ -19,3 +19,11 @@ Feature: Validate emits immutable proof only
|
||||
Given the author and validator identities are equal
|
||||
When the verdict would otherwise be PASS
|
||||
Then independence is waived and the verdict cannot satisfy independent proof
|
||||
|
||||
Scenario: Factual proof does not impersonate semantic judgment
|
||||
Given a frozen factual-gate registry and a pinned candidate
|
||||
When deterministic pre-validation runs
|
||||
Then each gate proves one declared factual proof kind
|
||||
And missing backing is classified as registry integrity
|
||||
And prose quality and exact wording remain independent-review evidence
|
||||
And no advisory semantic observation becomes a blocking deterministic gate
|
||||
|
||||
@@ -351,6 +351,37 @@ def semantic_id(
|
||||
return digest_bytes(canonical_bytes(identity))
|
||||
|
||||
|
||||
def defect_class(code: str) -> str:
|
||||
"""Name the owning integrity boundary for a fail-closed preflight defect."""
|
||||
if code in {
|
||||
"missing_gate_registry",
|
||||
"invalid_gate_registry",
|
||||
"unknown_gate",
|
||||
"duplicate_gate",
|
||||
"gate_not_factual_json",
|
||||
"registry_entry_changed",
|
||||
"missing_registry_backing",
|
||||
"missing_mandatory_gate",
|
||||
}:
|
||||
return "registry_integrity"
|
||||
if code in {"candidate_mutated", "base_not_ancestor"}:
|
||||
return "candidate_integrity"
|
||||
if code in {
|
||||
"missing_acceptance",
|
||||
"missing_claim_dependency",
|
||||
"stale_claim_dependency",
|
||||
"missing_evidence",
|
||||
"claim_dependency_digest_mismatch",
|
||||
"semantic_identity_changed",
|
||||
}:
|
||||
return "evidence_integrity"
|
||||
if code == "missing_toolchain":
|
||||
return "toolchain_integrity"
|
||||
if code in {"validator_not_independent", "invalid_validator_route"}:
|
||||
return "validator_integrity"
|
||||
return "request_integrity"
|
||||
|
||||
|
||||
def validate_registry(value: Any) -> dict[str, dict[str, Any]]:
|
||||
validate_schema("validation-gate-registry.v1.schema.json", value)
|
||||
by_id: dict[str, dict[str, Any]] = {}
|
||||
@@ -455,6 +486,7 @@ def freeze(repo: Path, spec_path: Path, output: Path) -> int:
|
||||
{
|
||||
"id": gate_id,
|
||||
"lane": entry["lane"],
|
||||
"proof_kind": entry["proof_kind"],
|
||||
"entry_sha256": digest_bytes(canonical_bytes(entry)),
|
||||
}
|
||||
)
|
||||
@@ -649,7 +681,13 @@ def blocked_receipt(
|
||||
"request_sha256": request_digest,
|
||||
"candidate": request["candidate"],
|
||||
"validator_route": request["validator"]["route"],
|
||||
"preflight_errors": [{"code": error.code, "detail": error.detail}],
|
||||
"preflight_errors": [
|
||||
{
|
||||
"code": error.code,
|
||||
"defect_class": defect_class(error.code),
|
||||
"detail": error.detail,
|
||||
}
|
||||
],
|
||||
"gate_executions": [],
|
||||
"model_spend_allowed": False,
|
||||
"disposition": "BLOCK",
|
||||
@@ -688,8 +726,13 @@ def validate_receipt_invariants(
|
||||
)
|
||||
return
|
||||
|
||||
expected_gates = [(item["id"], item["lane"]) for item in request["selected_gates"]]
|
||||
actual_gates = [(item["id"], item["lane"]) for item in executions]
|
||||
expected_gates = [
|
||||
(item["id"], item["lane"], item["proof_kind"])
|
||||
for item in request["selected_gates"]
|
||||
]
|
||||
actual_gates = [
|
||||
(item["id"], item["lane"], item["proof_kind"]) for item in executions
|
||||
]
|
||||
if actual_gates != expected_gates:
|
||||
raise RequestError(
|
||||
"receipt_invariant", "gate executions do not exactly match selected gates"
|
||||
@@ -811,6 +854,7 @@ def execute(repo: Path, request_path: Path, output: Path) -> int:
|
||||
raise RequestError("unknown_gate", selected["id"])
|
||||
if (
|
||||
entry["lane"] != selected["lane"]
|
||||
or entry["proof_kind"] != selected["proof_kind"]
|
||||
or digest_bytes(canonical_bytes(entry)) != selected["entry_sha256"]
|
||||
):
|
||||
raise RequestError("registry_entry_changed", selected["id"])
|
||||
@@ -886,6 +930,7 @@ def execute(repo: Path, request_path: Path, output: Path) -> int:
|
||||
execution: dict[str, Any] = {
|
||||
"id": selected["id"],
|
||||
"lane": selected["lane"],
|
||||
"proof_kind": selected["proof_kind"],
|
||||
"candidate": candidate_result,
|
||||
"attribution": "not_applicable",
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@ though it retains baseline attribution. Green mandatory proof routes to one
|
||||
fresh validator by default. Inventory size is never a rigor or validator-count
|
||||
signal.
|
||||
|
||||
Every factual registry entry declares one closed `proof_kind`: syntax, schema,
|
||||
identity, paths, generated drift, executable assertion, or evidence integrity.
|
||||
Semantic prose scores and exact-wording preferences are reviewer evidence, not
|
||||
factual gate kinds. Missing backing is a typed `registry_integrity` defect;
|
||||
neither that defect nor a semantic observation may be mislabeled as candidate
|
||||
proof.
|
||||
|
||||
The portable freezer, runner, and receipt verifier is
|
||||
`python3 skills/validate/scripts/validation-request.py freeze|run|check-receipt --help`.
|
||||
|
||||
|
||||
@@ -119,6 +119,24 @@ skill count, changed-file count, or any other inventory count never selects
|
||||
rigor or validator count. Risk and explicit mode selection are separate policy
|
||||
inputs outside this request foundation.
|
||||
|
||||
### Deterministic and semantic boundary
|
||||
|
||||
Factual registry entries declare exactly one `proof_kind`: `syntax`, `schema`,
|
||||
`identity`, `paths`, `generated_drift`, `executable_assertion`, or
|
||||
`evidence_integrity`. The request and receipt retain that kind so consumers can
|
||||
audit what the command was allowed to prove. A registry entry cannot declare
|
||||
semantic prose quality, usefulness, preferred wording, or reviewer judgment as
|
||||
machine proof.
|
||||
|
||||
Missing or stale gate registration, entry identity, or backing is a typed
|
||||
`registry_integrity` defect and stops before execution. It is not evidence that
|
||||
the candidate failed. Deterministic gates may still verify that a semantic
|
||||
verdict is present, schema-valid, independently authored, and bound to the
|
||||
candidate; only the independent reviewer decides whether its reasoning is
|
||||
correct.
|
||||
An advisory semantic observation never becomes deterministic authority. It
|
||||
never blocks delivery by being promoted into a strict prose score.
|
||||
|
||||
The closed wire formats are:
|
||||
|
||||
- `schemas/validation-candidate.v1.schema.json`
|
||||
|
||||
@@ -19,3 +19,11 @@ Feature: Validate emits immutable proof only
|
||||
Given the author and validator identities are equal
|
||||
When the verdict would otherwise be PASS
|
||||
Then independence is waived and the verdict cannot satisfy independent proof
|
||||
|
||||
Scenario: Factual proof does not impersonate semantic judgment
|
||||
Given a frozen factual-gate registry and a pinned candidate
|
||||
When deterministic pre-validation runs
|
||||
Then each gate proves one declared factual proof kind
|
||||
And missing backing is classified as registry integrity
|
||||
And prose quality and exact wording remain independent-review evidence
|
||||
And no advisory semantic observation becomes a blocking deterministic gate
|
||||
|
||||
@@ -351,6 +351,37 @@ def semantic_id(
|
||||
return digest_bytes(canonical_bytes(identity))
|
||||
|
||||
|
||||
def defect_class(code: str) -> str:
|
||||
"""Name the owning integrity boundary for a fail-closed preflight defect."""
|
||||
if code in {
|
||||
"missing_gate_registry",
|
||||
"invalid_gate_registry",
|
||||
"unknown_gate",
|
||||
"duplicate_gate",
|
||||
"gate_not_factual_json",
|
||||
"registry_entry_changed",
|
||||
"missing_registry_backing",
|
||||
"missing_mandatory_gate",
|
||||
}:
|
||||
return "registry_integrity"
|
||||
if code in {"candidate_mutated", "base_not_ancestor"}:
|
||||
return "candidate_integrity"
|
||||
if code in {
|
||||
"missing_acceptance",
|
||||
"missing_claim_dependency",
|
||||
"stale_claim_dependency",
|
||||
"missing_evidence",
|
||||
"claim_dependency_digest_mismatch",
|
||||
"semantic_identity_changed",
|
||||
}:
|
||||
return "evidence_integrity"
|
||||
if code == "missing_toolchain":
|
||||
return "toolchain_integrity"
|
||||
if code in {"validator_not_independent", "invalid_validator_route"}:
|
||||
return "validator_integrity"
|
||||
return "request_integrity"
|
||||
|
||||
|
||||
def validate_registry(value: Any) -> dict[str, dict[str, Any]]:
|
||||
validate_schema("validation-gate-registry.v1.schema.json", value)
|
||||
by_id: dict[str, dict[str, Any]] = {}
|
||||
@@ -455,6 +486,7 @@ def freeze(repo: Path, spec_path: Path, output: Path) -> int:
|
||||
{
|
||||
"id": gate_id,
|
||||
"lane": entry["lane"],
|
||||
"proof_kind": entry["proof_kind"],
|
||||
"entry_sha256": digest_bytes(canonical_bytes(entry)),
|
||||
}
|
||||
)
|
||||
@@ -649,7 +681,13 @@ def blocked_receipt(
|
||||
"request_sha256": request_digest,
|
||||
"candidate": request["candidate"],
|
||||
"validator_route": request["validator"]["route"],
|
||||
"preflight_errors": [{"code": error.code, "detail": error.detail}],
|
||||
"preflight_errors": [
|
||||
{
|
||||
"code": error.code,
|
||||
"defect_class": defect_class(error.code),
|
||||
"detail": error.detail,
|
||||
}
|
||||
],
|
||||
"gate_executions": [],
|
||||
"model_spend_allowed": False,
|
||||
"disposition": "BLOCK",
|
||||
@@ -688,8 +726,13 @@ def validate_receipt_invariants(
|
||||
)
|
||||
return
|
||||
|
||||
expected_gates = [(item["id"], item["lane"]) for item in request["selected_gates"]]
|
||||
actual_gates = [(item["id"], item["lane"]) for item in executions]
|
||||
expected_gates = [
|
||||
(item["id"], item["lane"], item["proof_kind"])
|
||||
for item in request["selected_gates"]
|
||||
]
|
||||
actual_gates = [
|
||||
(item["id"], item["lane"], item["proof_kind"]) for item in executions
|
||||
]
|
||||
if actual_gates != expected_gates:
|
||||
raise RequestError(
|
||||
"receipt_invariant", "gate executions do not exactly match selected gates"
|
||||
@@ -811,6 +854,7 @@ def execute(repo: Path, request_path: Path, output: Path) -> int:
|
||||
raise RequestError("unknown_gate", selected["id"])
|
||||
if (
|
||||
entry["lane"] != selected["lane"]
|
||||
or entry["proof_kind"] != selected["proof_kind"]
|
||||
or digest_bytes(canonical_bytes(entry)) != selected["entry_sha256"]
|
||||
):
|
||||
raise RequestError("registry_entry_changed", selected["id"])
|
||||
@@ -886,6 +930,7 @@ def execute(repo: Path, request_path: Path, output: Path) -> int:
|
||||
execution: dict[str, Any] = {
|
||||
"id": selected["id"],
|
||||
"lane": selected["lane"],
|
||||
"proof_kind": selected["proof_kind"],
|
||||
"candidate": candidate_result,
|
||||
"attribution": "not_applicable",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
setup() {
|
||||
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
|
||||
}
|
||||
|
||||
@test "factual registry admits only declared deterministic proof kinds" {
|
||||
run env REPO_ROOT="$REPO_ROOT" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
root = Path(os.environ["REPO_ROOT"])
|
||||
schema = json.loads(
|
||||
(root / "schemas/validation-gate-registry.v1.schema.json").read_text()
|
||||
)
|
||||
validator = Draft202012Validator(schema)
|
||||
allowed = {
|
||||
"syntax",
|
||||
"schema",
|
||||
"identity",
|
||||
"paths",
|
||||
"generated_drift",
|
||||
"executable_assertion",
|
||||
"evidence_integrity",
|
||||
}
|
||||
for proof_kind in sorted(allowed):
|
||||
registry = {
|
||||
"schema_version": 1,
|
||||
"gates": [{
|
||||
"id": "fact",
|
||||
"lane": "mandatory",
|
||||
"proof_kind": proof_kind,
|
||||
"argv": ["bash", "scripts/fact.sh", "--json"],
|
||||
"backing": [{"path": "scripts/fact.sh", "sha256": "0" * 64}],
|
||||
}],
|
||||
}
|
||||
errors = list(validator.iter_errors(registry))
|
||||
if errors:
|
||||
raise SystemExit(f"allowed proof kind {proof_kind!r} was rejected: {errors[0].message}")
|
||||
|
||||
semantic = {
|
||||
"schema_version": 1,
|
||||
"gates": [{
|
||||
"id": "semantic-prose-score",
|
||||
"lane": "mandatory",
|
||||
"proof_kind": "semantic_prose",
|
||||
"argv": ["bash", "scripts/score-prose.sh", "--json"],
|
||||
"backing": [{"path": "scripts/score-prose.sh", "sha256": "0" * 64}],
|
||||
}],
|
||||
}
|
||||
if not list(validator.iter_errors(semantic)):
|
||||
raise SystemExit("semantic prose was accepted as deterministic proof")
|
||||
PY
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "missing registry backing has an explicit registry-integrity class" {
|
||||
run env REPO_ROOT="$REPO_ROOT" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
schema = json.loads(
|
||||
(Path(os.environ["REPO_ROOT"]) / "schemas/validation-receipt.v1.schema.json").read_text()
|
||||
)
|
||||
error = schema["$defs"]["preflightError"]
|
||||
required = set(error["required"])
|
||||
if "defect_class" not in required:
|
||||
raise SystemExit("preflight errors do not require a defect class")
|
||||
enum = set(error["properties"]["defect_class"]["enum"])
|
||||
if "registry_integrity" not in enum:
|
||||
raise SystemExit("registry_integrity is not a declared defect class")
|
||||
PY
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "changed-scope gate runs structural integrity without semantic scoring" {
|
||||
run bash "$REPO_ROOT/scripts/regen-changed-scope.sh" --list \
|
||||
--file skills/validate/SKILL.md
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"changed skill structural integrity"* ]]
|
||||
[[ "$output" == *"skills/heal-skill/scripts/heal.sh --check --strict skills/validate"* ]]
|
||||
[[ "$output" != *"skills/heal-skill/scripts/audit.sh"* ]]
|
||||
[[ "$output" != *"deep conformance"* ]]
|
||||
}
|
||||
|
||||
@test "equivalent prose rephrasing cannot fail deterministic Validate proof" {
|
||||
local scratch="$BATS_TEST_TMPDIR/rephrased-validate"
|
||||
mkdir -p "$scratch/skills" "$scratch/schemas"
|
||||
cp -R "$REPO_ROOT/skills/validate" "$scratch/skills/validate"
|
||||
cp "$REPO_ROOT/schemas/verdict.v1.schema.json" \
|
||||
"$REPO_ROOT"/schemas/validation-*.schema.json "$scratch/schemas/"
|
||||
|
||||
for document in \
|
||||
"$scratch/skills/validate/SKILL.md" \
|
||||
"$scratch/skills/validate/references/canonical-validation-protocol.md"; do
|
||||
awk '
|
||||
!/Semantic prose scores/ &&
|
||||
!/exact-wording preferences/ &&
|
||||
!/advisory semantic observation/ &&
|
||||
!/deterministic authority/
|
||||
' "$document" >"$document.rephrased"
|
||||
mv "$document.rephrased" "$document"
|
||||
done
|
||||
printf '\nEquivalent wording: machines establish facts; independent reviewers judge meaning.\n' \
|
||||
>>"$scratch/skills/validate/SKILL.md"
|
||||
printf '\nEquivalent wording: advisory comments about meaning cannot authorize or block delivery.\n' \
|
||||
>>"$scratch/skills/validate/references/canonical-validation-protocol.md"
|
||||
|
||||
run bash "$scratch/skills/validate/scripts/validate.sh"
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"validate skill contract: PASS"* ]]
|
||||
}
|
||||
@@ -870,7 +870,7 @@ PY
|
||||
[[ "$output" == *'repo-runtime'* ]]
|
||||
}
|
||||
|
||||
@test "L2: changed-scope rejects shallow-green evidence when deep audit is non-PASS" {
|
||||
@test "L2: changed-scope keeps semantic skill scoring off the deterministic gate" {
|
||||
local scratch="$BATS_TEST_TMPDIR/release-waist-root"
|
||||
local scan_root="$BATS_TEST_TMPDIR/release-waist-scan"
|
||||
local shallow_bats="$BATS_TEST_TMPDIR/shallow-count.bats"
|
||||
@@ -879,6 +879,20 @@ PY
|
||||
prepare_builder_root "$scratch"
|
||||
write_conforming_skill "$scratch/skills/shallow-green" shallow-green \
|
||||
"'Shallow checks pass. Triggers: \"shallow green\".'" incomplete
|
||||
cat >>"$scratch/docs/contracts/skill-dispositions.yaml" <<'YAML'
|
||||
- skill: shallow-green
|
||||
domain: "BC3 Loop"
|
||||
hexagonal_role: supporting
|
||||
disposition: keep
|
||||
kind: skill
|
||||
runtime_targets: [claude, codex]
|
||||
parity_policy: required
|
||||
capability_class: execution
|
||||
path: skills/shallow-green/SKILL.md
|
||||
aliases: []
|
||||
supersedes: null
|
||||
rationale: "Fixture proves semantic scoring stays out of deterministic changed-scope checks."
|
||||
YAML
|
||||
mkdir -p "$scan_root"
|
||||
cp -R "$scratch/skills/shallow-green" "$scan_root/shallow-green"
|
||||
|
||||
@@ -903,13 +917,13 @@ BATS
|
||||
|
||||
run bash "$scratch/scripts/regen-changed-scope.sh" --check \
|
||||
--file skills/shallow-green/SKILL.md
|
||||
[[ "$status" -ne 0 ]]
|
||||
[[ "$output" == *'changed skill deep conformance'* ]]
|
||||
[[ "$output" == *'output-spec-explicit'* ]]
|
||||
[[ "$output" == *'bash skills/heal-skill/scripts/audit.sh --strict skills/shallow-green'* ]]
|
||||
[[ "$status" -eq 0 ]]
|
||||
[[ "$output" == *'changed skill structural integrity'* ]]
|
||||
[[ "$output" == *'All clean. No findings.'* ]]
|
||||
[[ "$output" != *'output-spec-explicit'* ]]
|
||||
}
|
||||
|
||||
@test "L2: changed-scope does not deep-audit redirect-only runtime packages" {
|
||||
@test "L2: changed-scope does not inspect redirect-only runtime packages" {
|
||||
local scratch="$BATS_TEST_TMPDIR/redirect-scope-root"
|
||||
|
||||
prepare_builder_root "$scratch"
|
||||
@@ -917,15 +931,15 @@ BATS
|
||||
run bash "$scratch/scripts/regen-changed-scope.sh" --list \
|
||||
--file skills/pre-mortem/SKILL.md
|
||||
[[ "$status" -eq 0 ]]
|
||||
[[ "$output" != *'changed skill deep conformance'* ]]
|
||||
[[ "$output" != *'audit.sh --strict skills/pre-mortem'* ]]
|
||||
[[ "$output" != *'changed skill structural integrity'* ]]
|
||||
[[ "$output" != *'heal.sh --check --strict skills/pre-mortem'* ]]
|
||||
[[ "$output" == *'codex'* ]]
|
||||
[[ "$output" == *'registry'* ]]
|
||||
}
|
||||
|
||||
@test "L0: local and CI release waists invoke the canonical deep audit" {
|
||||
grep -Fq 'skills/heal-skill/scripts/audit.sh --strict' \
|
||||
@test "L0: changed-scope release waist invokes structural integrity, not prose scoring" {
|
||||
grep -Fq 'skills/heal-skill/scripts/heal.sh --check --strict' \
|
||||
"$REPO_ROOT/scripts/regen-changed-scope.sh"
|
||||
! grep -Fq 'skills/heal-skill/scripts/audit.sh --strict' \
|
||||
"$REPO_ROOT/scripts/regen-changed-scope.sh"
|
||||
grep -Fq 'skills/heal-skill/scripts/audit.sh --strict' \
|
||||
"$REPO_ROOT/cli/internal/gates/checks/seed.go"
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ GATE
|
||||
gates: [{
|
||||
id: "fact",
|
||||
lane: "mandatory",
|
||||
proof_kind: "executable_assertion",
|
||||
argv: ["bash", "scripts/factual-gate.sh", $count, "--json"],
|
||||
backing: [{path: $backing_path, sha256: $backing_sha}]
|
||||
}]
|
||||
@@ -153,6 +154,7 @@ add_nonbinding_gate() {
|
||||
.gates += [{
|
||||
id: $gate_id,
|
||||
lane: $lane,
|
||||
proof_kind: "executable_assertion",
|
||||
argv: ["bash", "scripts/factual-gate.sh", $count, $status_file, "--json"],
|
||||
backing: [{path: "scripts/factual-gate.sh", sha256: $backing_sha}]
|
||||
}]
|
||||
@@ -182,6 +184,7 @@ check_bad_receipt() {
|
||||
(.candidate.semantic_id | length) == 64 and
|
||||
(.candidate.changed_surfaces | length) >= 1 and
|
||||
.validator.route == "single_fresh" and
|
||||
.selected_gates[0].proof_kind == "executable_assertion" and
|
||||
(.validator | has("inventory_count") | not)
|
||||
' "$REQUEST"
|
||||
|
||||
@@ -192,6 +195,7 @@ check_bad_receipt() {
|
||||
.disposition == "READY" and
|
||||
.next_action == "VALIDATE_SINGLE_FRESH" and
|
||||
(.gate_executions | length) == 1 and
|
||||
.gate_executions[0].proof_kind == "executable_assertion" and
|
||||
.gate_executions[0].candidate.status == "PASS"
|
||||
' "$RECEIPT"
|
||||
[ "$(wc -l <"$COUNT_FILE" | tr -d ' ')" -eq 1 ]
|
||||
@@ -214,6 +218,7 @@ check_bad_receipt() {
|
||||
.gates += [{
|
||||
id: "fact-two",
|
||||
lane: "mandatory",
|
||||
proof_kind: "schema",
|
||||
argv: ["bash", "scripts/factual-gate.sh", $count, "--json"],
|
||||
backing: [{path: "scripts/factual-gate.sh", sha256: $backing_sha}]
|
||||
}]
|
||||
@@ -231,7 +236,10 @@ check_bad_receipt() {
|
||||
run_request
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
jq -e '.gate_executions | map(.id) == ["fact", "fact-two"]' "$RECEIPT"
|
||||
jq -e '
|
||||
(.gate_executions | map(.id)) == ["fact", "fact-two"] and
|
||||
(.gate_executions | map(.proof_kind)) == ["executable_assertion", "schema"]
|
||||
' "$RECEIPT"
|
||||
[ "$(wc -l <"$COUNT_FILE" | tr -d ' ')" -eq 2 ]
|
||||
}
|
||||
|
||||
@@ -283,7 +291,11 @@ check_bad_receipt() {
|
||||
run_request
|
||||
|
||||
[ "$status" -eq 1 ]
|
||||
jq -e '.model_spend_allowed == false and .preflight_errors[0].code == "missing_registry_backing"' "$RECEIPT"
|
||||
jq -e '
|
||||
.model_spend_allowed == false and
|
||||
.preflight_errors[0].code == "missing_registry_backing" and
|
||||
.preflight_errors[0].defect_class == "registry_integrity"
|
||||
' "$RECEIPT"
|
||||
[ ! -s "$COUNT_FILE" ]
|
||||
}
|
||||
|
||||
@@ -686,13 +698,31 @@ check_bad_receipt() {
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "receipt rejects a proof kind that differs from the frozen registry" {
|
||||
seed_repo PASS PASS
|
||||
freeze_request
|
||||
[ "$status" -eq 0 ]
|
||||
run_request
|
||||
[ "$status" -eq 0 ]
|
||||
jq '.gate_executions[0].proof_kind = "schema"' "$RECEIPT" >"$BAD_RECEIPT"
|
||||
|
||||
check_bad_receipt
|
||||
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"gate executions do not exactly match selected gates"* ]]
|
||||
}
|
||||
|
||||
@test "receipt rejects contradictory READY preflight evidence" {
|
||||
seed_repo PASS PASS
|
||||
freeze_request
|
||||
[ "$status" -eq 0 ]
|
||||
run_request
|
||||
[ "$status" -eq 0 ]
|
||||
jq '.preflight_errors = [{code: "stale_claim_dependency", detail: "claim.txt"}]' \
|
||||
jq '.preflight_errors = [{
|
||||
code: "stale_claim_dependency",
|
||||
defect_class: "evidence_integrity",
|
||||
detail: "claim.txt"
|
||||
}]' \
|
||||
"$RECEIPT" >"$BAD_RECEIPT"
|
||||
|
||||
check_bad_receipt
|
||||
|
||||
Reference in New Issue
Block a user