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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants