fix: reject modal P1 scope claims

This commit is contained in:
2026-08-09 21:21:40 +02:00
parent 3583659600
commit c24ffcd238
2 changed files with 145 additions and 58 deletions
+117 -44
View File
@@ -545,22 +545,48 @@ elif mutation == "p1-scope-inversion":
1,
)
elif mutation.startswith("p1-append-"):
claims = {
"p1-append-acquisition": "P1 owns Evidence acquisition.",
"p1-append-materialization": "P1 owns Evidence materialization.",
"p1-append-extraction": "P1 owns Evidence extraction.",
"p1-append-preprocessing": "P1 owns Evidence preprocessing.",
"p1-append-embeddings": "P1 owns Evidence embeddings.",
"p1-append-qdrant-writes": "P1 owns Evidence Qdrant writes.",
"p1-append-indexing": "P1 owns Evidence indexing.",
"p1-append-active": "P1 owns Evidence `ACTIVE` publication.",
"p1-append-retention": "P1 owns Evidence retention.",
"p1-append-gc": "P1 owns Evidence GC.",
operations = {
"acquisition": ("acquire Evidence", "acquires Evidence", "Evidence acquisition"),
"materialization": (
"materialize Evidence", "materializes Evidence", "Evidence materialization"
),
"extraction": ("extract Evidence", "extracts Evidence", "Evidence extraction"),
"preprocessing": (
"preprocess Evidence", "preprocesses Evidence", "Evidence preprocessing"
),
"embeddings": ("create embeddings", "creates embeddings", "Evidence embeddings"),
"qdrant-writes": (
"write embeddings to Qdrant", "writes embeddings to Qdrant",
"Evidence Qdrant writes",
),
"indexing": ("index Evidence", "indexes Evidence", "Evidence indexing"),
"active": ("publish `ACTIVE`", "publishes `ACTIVE`", "Evidence `ACTIVE` publication"),
"retention": ("retain Evidence", "retains Evidence", "Evidence retention"),
"gc": ("garbage-collect Evidence", "garbage-collects Evidence", "Evidence GC"),
}
claim = claims.get(mutation)
if claim is None:
forms = (
"base", "third-person", "can", "may", "must", "will", "should",
"adverb-before-modal", "adverb-after-modal", "ownership",
)
suffix = mutation.removeprefix("p1-append-")
operation = next((name for name in operations if suffix.startswith(f"{name}-")), None)
form = suffix.removeprefix(f"{operation}-") if operation else ""
if operation is None or form not in forms:
raise SystemExit(f"unknown P1 append mutation: {mutation}")
changed = original + f"\n{claim}\n"
base, third_person, ownership = operations[operation]
claims = {
"base": f"P1 does {base}.",
"third-person": f"P1 {third_person}.",
"can": f"P1 can {base}.",
"may": f"P1 may {base}.",
"must": f"P1 must {base}.",
"will": f"P1 will {base}.",
"should": f"P1 should {base}.",
"adverb-before-modal": f"P1 directly may {base}.",
"adverb-after-modal": f"P1 may directly {base}.",
"ownership": f"P1 owns {ownership}.",
}
changed = original + f"\n{claims[form]}\n"
elif mutation == "config-ordering":
changed = original.replace(
"tht config check -c <path>", "tht -c <path> config check", 1
@@ -593,6 +619,38 @@ PY
fi
}
expect_evidence_claim_accepted() {
local label="$1" claim="$2"
local fixture_root="$negative_root/evidence-safe-${label// /-}"
local fixture_output="$fixture_root/output"
mkdir -p \
"$fixture_root/deploy/workspaces" \
"$fixture_root/docs/contracts" \
"$fixture_root/docs/install/examples"
cp "$root/deploy/workspaces/example.yaml" "$fixture_root/deploy/workspaces/example.yaml"
cp "$root/deploy/workspaces/psd.yaml.example" "$fixture_root/deploy/workspaces/psd.yaml.example"
cp "$root/docs/contracts/workspace-evidence-v3.md" \
"$fixture_root/docs/contracts/workspace-evidence-v3.md"
cp "$root/docs/install/local-workspace-registry.md" \
"$fixture_root/docs/install/local-workspace-registry.md"
cp "$root/docs/install/server-workspace-registry.md" \
"$fixture_root/docs/install/server-workspace-registry.md"
cp "$root/docs/install/examples/workspace-bindings.env.example" \
"$fixture_root/docs/install/examples/workspace-bindings.env.example"
printf '\n%s\n' "$claim" >>"$fixture_root/docs/contracts/workspace-evidence-v3.md"
set +e
verify_workspace_evidence_contract "$fixture_root" >"$fixture_output" 2>&1
local status=$?
set -e
if [[ $status -ne 0 ]]; then
echo "safe Evidence fixture rejected: $label" >&2
cat "$fixture_output" >&2
negative_failures=$((negative_failures + 1))
fi
}
expect_guide_rejected() {
local label="$1" validator="$2" source_guide="$3" relative_path="$4"
local mutation="$5" expected_error="$6"
@@ -1001,36 +1059,51 @@ expect_evidence_fixture_rejected \
expect_evidence_fixture_rejected \
"P1 Evidence scope inversion" docs/contracts/workspace-evidence-v3.md p1-scope-inversion \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 acquisition claim" docs/contracts/workspace-evidence-v3.md p1-append-acquisition \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 materialization claim" docs/contracts/workspace-evidence-v3.md p1-append-materialization \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 extraction claim" docs/contracts/workspace-evidence-v3.md p1-append-extraction \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 preprocessing claim" docs/contracts/workspace-evidence-v3.md p1-append-preprocessing \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 embeddings claim" docs/contracts/workspace-evidence-v3.md p1-append-embeddings \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 Qdrant writes claim" docs/contracts/workspace-evidence-v3.md p1-append-qdrant-writes \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 indexing claim" docs/contracts/workspace-evidence-v3.md p1-append-indexing \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 ACTIVE publication claim" docs/contracts/workspace-evidence-v3.md p1-append-active \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 retention claim" docs/contracts/workspace-evidence-v3.md p1-append-retention \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 garbage collection claim" docs/contracts/workspace-evidence-v3.md p1-append-gc \
"P1 scope violation"
p1_operations=(
acquisition materialization extraction preprocessing embeddings
qdrant-writes indexing active retention gc
)
p1_positive_forms=(
base third-person can may must will should
adverb-before-modal adverb-after-modal ownership
)
for operation in "${p1_operations[@]}"; do
for form in "${p1_positive_forms[@]}"; do
expect_evidence_fixture_rejected \
"appended P1 ${operation} ${form} claim" \
docs/contracts/workspace-evidence-v3.md "p1-append-${operation}-${form}" \
"P1 scope violation"
done
done
p1_safe_bases=(
"acquire Evidence" "materialize Evidence" "extract Evidence" "preprocess Evidence"
"create embeddings" "write embeddings to Qdrant" "index Evidence" 'publish `ACTIVE`'
"retain Evidence" "garbage-collect Evidence"
)
p1_safe_third_person=(
"acquires Evidence" "materializes Evidence" "extracts Evidence" "preprocesses Evidence"
"creates embeddings" "writes embeddings to Qdrant" "indexes Evidence" 'publishes `ACTIVE`'
"retains Evidence" "garbage-collects Evidence"
)
p1_safe_ownership=(
"Evidence acquisition" "Evidence materialization" "Evidence extraction"
"Evidence preprocessing" "Evidence embeddings" "Evidence Qdrant writes"
"Evidence indexing" 'Evidence `ACTIVE` publication' "Evidence retention" "Evidence GC"
)
for index in "${!p1_operations[@]}"; do
operation="${p1_operations[$index]}"
base="${p1_safe_bases[$index]}"
third_person="${p1_safe_third_person[$index]}"
ownership="${p1_safe_ownership[$index]}"
expect_evidence_claim_accepted "negative P1 ${operation} cannot" "P1 cannot ${base}."
expect_evidence_claim_accepted "negative P1 ${operation} must not" "P1 must not ${base}."
expect_evidence_claim_accepted "negative P1 ${operation} does not" "P1 does not ${base}."
expect_evidence_claim_accepted "negative P1 ${operation} never" "P1 never ${third_person}."
expect_evidence_claim_accepted \
"later plan ${operation}" "A later plan may assign ${ownership} to P1."
expect_evidence_claim_accepted "P2 ${operation}" "P2 may directly ${base}."
expect_evidence_claim_accepted "P6 ${operation}" "P6 ${third_person}."
done
expect_evidence_fixture_rejected \
"config check option reordered" docs/contracts/workspace-evidence-v3.md config-ordering \
"exact config-check ordering missing"
+28 -14
View File
@@ -406,21 +406,35 @@ if not all(token in p1 for token in ("lexical URI", "Git tree", "same commit", "
if not all(token in p6 for token in ("commit-addressed materialization", "realpath", "recursive containment", "nested-symlink", "race")):
raise SystemExit("missing P6 materialization ownership")
no_scope = "P1 performs no acquisition, extraction, preprocessing/indexing, embeddings, Qdrant writes, `ACTIVE` publication, retention, or GC."
p1_adverbs = r"(?:\s+(?:also|then|now|directly|itself))*"
p1_base_operation = r"""(?:
acquire|materialize|extract|preprocess|index|retain|
(?:create|generate)\s+embeddings?|
write\s+(?:embeddings?\s+)?to\s+Qdrant|
publish\s+`?ACTIVE\b`?|
garbage[- ]collect|
(?:run|perform)\s+(?:retention|GC|garbage[ -]collection)
)"""
p1_third_person_operation = r"""(?:
acquires|materializes|extracts|preprocesses|indexes|retains|
(?:creates|generates)\s+embeddings?|
writes\s+(?:embeddings?\s+)?to\s+Qdrant|
publishes\s+`?ACTIVE\b`?|
garbage[- ]collects|
(?:runs|performs)\s+(?:retention|GC|garbage[ -]collection)
)"""
p1_ownership = r"""(?:
(?:owns|handles|performs)|is\s+responsible\s+for
)\s+(?:Evidence\s+)?(?:
acquisition|materialization|extraction|preprocessing|embeddings?|
Qdrant\s+writes?|indexing|`?ACTIVE`?\s+publication|retention|GC|
garbage[ -]collection
)"""
positive_p1_operation = re.compile(
r"""\bP1\b(?:\s+(?:also|then|now|directly|itself))*\s+(?:
(?:will\s+|must\s+|may\s+|can\s+)?(?:
acquires?|materializes?|extracts?|preprocesses?|indexes?|retains?|
(?:creates?|generates?)\s+embeddings?|
writes?\s+(?:embeddings?\s+)?to\s+Qdrant|
publishes?\s+`?ACTIVE\b`?|
garbage[- ]collects?|
(?:runs?|performs?)\s+(?:retention|GC|garbage[ -]collection)
)|
(?:owns?|handles?|performs?|is\s+responsible\s+for)\s+(?:Evidence\s+)?(?:
acquisition|materialization|extraction|preprocessing|embeddings?|
Qdrant\s+writes?|indexing|`?ACTIVE`?\s+publication|retention|GC|
garbage[ -]collection
)
rf"""\bP1\b{p1_adverbs}\s+(?:
(?:(?:can|may|must|will|should|does){p1_adverbs}\s+){p1_base_operation}|
{p1_third_person_operation}|
{p1_ownership}
)\b""",
re.IGNORECASE | re.VERBOSE,
)