Skip to content

Unified agentic spec inference for P: frozen candidate schema, robust validation backbone, ranking (PLAN.md Phases 0-4) - #977

Open
ankushdesai wants to merge 4 commits into
masterfrom
claude/quizzical-mclaren-c279df
Open

Unified agentic spec inference for P: frozen candidate schema, robust validation backbone, ranking (PLAN.md Phases 0-4)#977
ankushdesai wants to merge 4 commits into
masterfrom
claude/quizzical-mclaren-c279df

Conversation

@ankushdesai

Copy link
Copy Markdown
Member

What

Implements the machine-independent core of the unified P specification-inference plan (Src/PInfer/Scripts/PLAN.md): a single validate→prune→rank→judge backbone with pluggable proposers, where LLM proposers derive candidate spec monitors from design intent and PChecker is the sound oracle. Supersedes the scripts in #974 (same lineage, hardened).

The plan is v2 — it incorporates a 44-finding multi-agent review of the v1 design; §13 of PLAN.md maps every finding to its resolution. The three silent-failure classes that review surfaced are fixed here:

Review finding Fix in this PR
Vacuity depended on the proposer emitting a canary — vacuous specs reported as verified Auto-canary (make_canary): backbone derives a <Name>_canary probe from any candidate by replacing its asserts in place; canary-less monitors report UNKNOWN-VACUITY, never a bare pass
Monitor-vs-SUT attribution by substring match — a monitor-induced deadlock was silently dropped Baseline-differential attribution: bounded-check the unmodified project once; a failure is SUT-owned iff its signature pre-exists (regression-tested on the deadlock case)
Hallucinated event/field names survived to model-checking Static name gate: parses project event/type decls; rejects undeclared events, unobserved handlers, phantom payload fields — before any compile cycle, with a repair-ready diagnostic

Structure (Src/PInfer/Scripts/)

  • invariant_core.py — the hub: frozen Candidate/Formula schema (structured guards/relations/SC/quorum fields), canonical dedup key (cluster-and-keep, nothing silently dropped), honest verdict vocabulary (HOLDS-BOUNDED vs reserved HOLDS-PROVEN, VACUOUS, UNKNOWN-VACUITY, INCONCLUSIVE), judge contract with development_action, ranking kernel (compute_score, ported from experimental/pinfer's ranking_with_llm.py minus Strands/Bedrock coupling) — all engine-agnostic; wrappers own the LLM provider.
  • Proposers: propose_templated.js (grammar-scaffold enumeration) + propose_intent.js (new: design-intent lenses) emitting one schema; JS↔Python parity is unit-tested.
  • Pipeline CLIs: merge_candidates.py (hybrid merge + per-provenance stats), prep_candidates.py (P fixups + auto-canaries), check_candidates.py (gate → baseline → wire → bounded-check → classify), diag_compile.py/repair_workflow.js/verify_repairs.py (compile-repair loop), rank_candidates.py (verdict-gated ranking).

Testing

  • 42 unit tests (test_invariant_core.py), no toolchain needed: schema round-trip, dedup, full mocked verdict-classification matrix, attribution (incl. the deadlock regression), gate, canary derivation (incl. ;-inside-format-string), ranking kernel + gating, JS↔Python parity.
  • rank_candidates.py/merge_candidates.py smoke-tested end-to-end on fixtures.
  • Not yet run: live p compile/p check acceptance on FD/TPC (+ fault-injected variant, N≥3 seeds, §8 metrics) — needs a box with .NET 8 + p install + an LLM runtime; that is the milestone's remaining exit criterion and is called out in PLAN.md §12.

Reviewer notes

  • PInfer's C# enumerator and Z3 remain optional plug-in tiers (PLAN.md §3); nothing here depends on experimental/pinfer building.
  • The verdict vocabulary is deliberately conservative: nothing is labeled verified/sound on bounded checking alone.
  • Experimental tooling; no compiler/runtime code touched.

🤖 Generated with Claude Code

ankushdesai and others added 4 commits July 1, 2026 22:11
…ndidate schema + verdict vocabulary

Phase 0: bring the agentic invariant-mining scripts from pinfer-peasyai-design
onto this branch (invariant_core.py, proposers, prep/diag/check/repair/verify).

Phase 1 (per Src/PInfer/Scripts/PLAN.md v2):
- Candidate/Formula dataclasses with structured formula record (quantifiers,
  guards, relations, sc/config_event for quorum, uses_index) + provenance_run
- dedup_key/dedup_candidates: canonical property identity, cluster-and-keep
- Frozen verdict vocabulary: HOLDS-BOUNDED (not "HOLDS"), UNKNOWN-VACUITY for
  canary-less passes, HOLDS-PROVEN reserved for PVerifier
- JUDGE_OUTPUT_SCHEMA {verdict, confidence, cex_grounding, development_action}
- build_env deduped into invariant_core (diag_compile/verify_repairs import it)
- propose_templated.js CAND_SCHEMA extended to mirror the Python contract
- test_invariant_core.py: 8 -> 22 tests (schema round-trip, dedup, judge
  contract, JS<->Python parity, full mocked verdict-classification matrix)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ser + hybrid merge

Phase 2a (PLAN.md): propose_intent.js — intent-lens proposer Workflow. Derives
candidates from design intent (docs + source comments) through the four
INTENT_LENSES (agreement/liveness/consistency/validity), complementing
propose_templated.js's grammar-scaffold enumeration. Emits the shared
CAND_SCHEMA with provenance:"intent" and structured formula records; the
validity lens carries the canary convention. Python INTENT_LENSES stays the
source of truth (TestProposerParity guards drift).

Phase 2b, deterministic half: merge_candidates.py — hybrid merge CLI. Unions
candidate sets from any number of proposers (workflow-result or plain-list
shape), dedups by invariant_core.dedup_key, clusters-and-keeps representatives
(nothing silently dropped), and emits per-provenance in/out stats for the
PLAN §8 reduction-ratio metric. The PInfer enumerative adapter plugs in as
just another input file.

Tests: 22 -> 26 (proposer parity, cross-proposer dedup with cluster/stat
assertions, plain-list input).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aseline-diff attribution

The three silent-failure fixes from the PLAN.md multi-agent review:

- Auto-canary (M4, §6.1): make_canary derives a <Name>_canary vacuity probe
  from any candidate by replacing its asserts with `assert false` in place
  (string-literal-aware scanner, guards preserved). PREP appends canaries for
  candidates lacking one; validate_candidates does the same. Vacuity detection
  no longer depends on proposer cooperation; assert-less monitors stay
  UNKNOWN-VACUITY rather than being overclaimed.

- Static name gate (M9, §6.2): static_gate parses the project's event/type
  declarations and rejects candidates observing undeclared events, handling
  unobserved events, or accessing phantom payload fields — before any compile
  cycle, with a REPAIR-ready diagnostic (verdict COMPILE-ERR, detail
  "static-gate: ..."). Mirrors PeasyAI's EventDeclarationValidator /
  PayloadFieldValidator semantics without adding a dependency.

- Baseline-differential attribution (M5, §6.3): _baseline_failures bounded-
  checks the unmodified project once; _attribute_owner marks a candidate
  failure SUT-owned iff its signature pre-exists there. Replaces the brittle
  '_candidates.p'/'liveness'/'hot state' substring heuristic — a monitor-
  induced deadlock now correctly attributes to the monitor (regression test).

check_candidates.py grows --no-auto-canary/--no-gate/--no-baseline.
Tests: 26 -> 37 (canary derivation incl. `;`-in-format-string, attribution
matrix incl. the deadlock regression, gate pass/flag/integration).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oring kernel)

Port the scoring kernel from experimental/pinfer's ranking_with_llm.py into the
shared core, per PLAN.md Phase 4 / review finding M12:

- compute_score: sqrt(gen*crit)*0.8 + dist*0.2 + vis*0.0 (identical formula,
  RANK_WEIGHTS), plus the 4-metric rubric (generalization / criticality /
  distinguishability / visibility) as engine-agnostic prompt strings and a
  RANK_OUTPUT_SCHEMA structured-output contract.
- Dropped couplings: Strands agent layer, Bedrock model id, `constants`
  module, pruned_invariants.txt input — the ranker consumes Candidate/Verdict.
- Verdict gating: only HOLDS-BOUNDED/HOLDS-PROVEN candidates are rankable;
  VACUOUS/FAILS/unvalidated land in an explicit unranked list with reasons
  (perfect LLM scores cannot rescue a gated candidate).
- rank_candidates.py CLI: --emit-prompt (deterministic prompt build for the
  LLM-owning wrapper) and --apply (combine returned scores + gate + report),
  completing the wrapper-owns-the-provider seam used by propose/judge.

Tests: 37 -> 42 (kernel formula/validation/custom-weights, gating+sort,
prompt/schema shape). CLI smoke-tested in both modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 2, 2026 06:43
@ankushdesai
ankushdesai changed the base branch from master to pinfer-agentic-invariants July 2, 2026 06:45
@ankushdesai
ankushdesai changed the base branch from pinfer-agentic-invariants to master July 2, 2026 06:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a unified, engine-agnostic “validate → prune/dedup → rank → judge” backbone for agentic P specification inference (per PLAN.md Phases 0–4), with a frozen candidate/formula schema, deterministic validation improvements (auto-canary vacuity handling, baseline-differential attribution, static name gate), plus two pluggable LLM proposers and supporting CLIs/tests.

Changes:

  • Adds invariant_core.py defining the frozen Candidate/Formula schema, dedup key + clustering, static gate, auto-canary derivation, bounded-check verdicting, and ranking kernel + prompt builders.
  • Adds/updates pipeline tooling: proposers (propose_templated.js, propose_intent.js), candidate prep/merge/check/diagnose/repair/verify, and rank application.
  • Adds a comprehensive unit test suite (test_invariant_core.py) and supporting docs (README_agentic_invariants.md, updated PLAN.md).

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
Src/PInfer/Scripts/invariant_core.py Core schema + validation/gating/attribution + ranking prompt/kernel utilities.
Src/PInfer/Scripts/test_invariant_core.py Unit tests for schema, dedup, canary, gate, attribution, ranking, proposer parity.
Src/PInfer/Scripts/propose_templated.js Template-scaffold proposer emitting schema-shaped candidates.
Src/PInfer/Scripts/propose_intent.js Intent-lens proposer emitting schema-shaped candidates.
Src/PInfer/Scripts/prep_candidates.py Deterministic candidate cleanup + auto-canary emission into .p files.
Src/PInfer/Scripts/merge_candidates.py Hybrid merge + structured dedup (cluster-and-keep + stats).
Src/PInfer/Scripts/check_candidates.py CLI wrapper over validate_candidates.
Src/PInfer/Scripts/diag_compile.py Per-candidate isolated compile diagnostics for repair.
Src/PInfer/Scripts/repair_workflow.js LLM workflow to repair non-compiling candidates given compiler errors.
Src/PInfer/Scripts/verify_repairs.py Recompile repaired candidates to measure repair success.
Src/PInfer/Scripts/rank_candidates.py Deterministic rank prompt emission + score application/gating/reporting.
Src/PInfer/Scripts/README_agentic_invariants.md User-facing documentation for propose→check→judge and repair loop.
Src/PInfer/Scripts/PLAN.md Updated plan/spec describing architecture, stages, acceptance, and findings map.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


Return the corrected full \`spec ${f.name} ... { ... }\` source (keep the same spec name and the same intent/property) plus a one-line summary of what you changed. If a property fundamentally cannot be expressed as a P monitor (e.g. it needs the ternary only for a value the property doesn't really need), simplify minimally while preserving the property's meaning.`,
{ label: `repair:${f.name}`, phase: 'Repair', schema: SCHEMA, agentType: 'Explore' }
).then(r => ({ benchmark: f.benchmark, name: f.name, changes: r && r.changes, fixedSource: r && r.fixedSource }))
Comment on lines +309 to +324
handlers = list(_HANDLER_RE.finditer(block))
for i, hm in enumerate(handlers):
ev, param = hm.group(1), hm.group(2)
if ev not in declared:
errs.append(f"handles undeclared event '{ev}'")
continue
if observed and ev not in observed:
errs.append(f"handles '{ev}' which is not in the observes list")
fields = declared[ev]
if fields is None:
continue # payload type unresolvable; skip field checks
body_end = handlers[i + 1].start() if i + 1 < len(handlers) else len(block)
for fa in re.finditer(rf"\b{re.escape(param)}\.(\w+)", block[hm.end():body_end]):
if fa.group(1) not in fields:
errs.append(f"'{param}.{fa.group(1)}' — event '{ev}' payload has no field "
f"'{fa.group(1)}' (fields: {sorted(fields)})")
sys.path.insert(0, str(Path(__file__).resolve().parent))
from invariant_core import make_canary # noqa: E402

HANDLER_RE = re.compile(r'(on\s+\w+\s+do\s*\([^)]*\)\s*|on\s+\w+\s+goto\s+\w+\s+with\s*\([^)]*\)\s*|entry\s*(?:\([^)]*\))?\s*|exit\s*)\{')
Comment on lines +347 to +349
out = subprocess.run(["p", "compile"], cwd=project, env=env,
capture_output=True, text=True).stdout
return "Compilation succeeded" in out
for i in range(len(spans) - 1):
name, block = spans[i][1], src[spans[i][0]:spans[i + 1][0]].strip()
diag.write_text(block)
out = subprocess.run(["p", "compile"], cwd=d, env=env, capture_output=True, text=True).stdout
d = r["dir"]
diag = Path(d) / "PSpec" / "_diag.p"
diag.write_text(r["fixedSource"])
out = subprocess.run(["p", "compile"], cwd=d, env=env, capture_output=True, text=True).stdout
Comment on lines +54 to +55
args = ap.parse_args()

Comment on lines +4 to +5
monitors into a P project, compile, bounded model-check, and classify each as
HOLDS / FAILS(+counterexample) / VACUOUS / INCONCLUSIVE / COMPILE-ERR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants