Orchestrata Ultimate › Plans › prolog-ast-reasoning-ladder
Prolog + AST deterministic-reasoning ladder
Source: plans/prolog-ast-reasoning-ladder.md at 8a2cb42ad · last changed 2026-08-13 · first committed 2026-08-02 · 169 lines.
Prose pending.
Plan text
Prolog + AST deterministic-reasoning ladder
Origin: user directive 2026-08-02 — "what more can prolog do, tasks that take a lot of time and tokens but are deterministic and we can build right now?" Then, on tests/coverage: consolidate the suite so branches run once/as-necessary, not redundantly.
Principle
Work that is expensive but deterministic — whole-program reachability, impact, dead code, cycles, requirement↔code↔test tracing, coverage consolidation — should be computed once by a logic engine over code facts and queried forever, instead of approximated per-question by an LLM reading files (slow, token-heavy, never exhaustive). The AST supplies ground-truth facts; Prolog (SWI 10.0.2 + janus, tabling, CLP(FD), s(CASP)) supplies exact, complete closures.
Substrate (already on disk — extend, don't rebuild)
src/orchestrata_ultimate/tools/codebase_db.pl+CodebaseTools(codebase_tools.py): STRUCTURAL facts (calls_resolved/3,module,class,function,imports,inherits_resolved/2) + tabledaffected/2(reverse dep closure),mod_depends/2,mod_cycle. Reindexes fromplans/ast-ultimate.jsonin ~2.6s for 823 modules.plans/ast-ultimate.json: byte-exact snapshot; carries per-symboltraces/requirements/decorators(~1828 distinct REQ ids) and start/end lines + body_hash.scripts/parse_spec_requirements.py: emitsspec_requirement(Spec,Req,Desc)facts.scripts/attribute_traces_runtime.py+reports/rtm-evidence.json(5284 graded reqs): runtime execution-evidence attribution (test —executed(|run)→ def), needs.coveragecollected with--cov-context=test.scripts/schedule_tests.py: CLP(FD) test scheduler (reuse as the set-cover solver).factory/evidence/mutation.py: mutation engine (the drop-safety criterion for T1c).scripts/affected_tests.py: per-change test selection (the fast-tier gate).
Metric doctrine (governs T1b/T1c — do not violate)
- Coverage-hit ≠ verification. A branch executed N× but asserted 0× is false confidence, not coverage. The real metric is fault detection: mutate the branch — does a test fail?
- Redundant execution ≠ redundant verification. A unit test proves logic; a high-layer test proves the code is wired in and reached with real inputs. Built-but-unwired is this project's dominant defect class — the overlap is the safety net. Tier the suite, never delete for coverage overlap.
- AST is the denominator.
branches/call-graphdefine the complete set of paths that should be covered, so gap reports are honest ("what's missing"), not just overlap reports.
The ladder
T0 — wire the structural KB for the orchestrator (building)
scripts/code_intel.py CLI + freshness over CodebaseTools: impact/cycles/unused/
callers/callees/dep-path/module-api/hotspots, auto-reindex on staleness, qname prefix
normalization. Facts already exist. Immediate token win on every structural question.
T1 — traceability break-finders (no test evidence) (building, scope-corrected)
src/orchestrata_ultimate/tools/traceability_db.pl (consults codebase_db) +
scripts/trace_intel.py. Facts: traces/2, spec_requirement/3, entrypoint/2.
Rules (tabled forward reaches/2 over calls_resolved):
feature_narrative(Entry, ReachedModules)— auto-generated use-case narrative.orphan_feature(Q)— traces to a req, reachable from no entrypoint (built-but-unwired).untraced_req(R)— requirement no code implements.unspecced_code(Q)— reachable from an entrypoint, traces to no req.specless_usecase(S)— spec whose traced code no entrypoint reaches. Entrypoints in MVP: cli mains /__main__/*server.py/runtime_main. Dynamic dispatch (registry→handler) is Phase-2 (until then the dead-code surface is inflated — stated honestly, not deleted).
T1b — false_confidence via three witnesses (next; needs coverage regen)
test_reaches(Test, SrcQName) evidenced by ANY of:
- coverage — per-test
|runcontexts (regenerate.coveragewith--cov-context=test, ~8 min deterministic; current DB has no contexts) → map lines to symbols via AST line ranges. - tracing —
attribute_traces_runtime.pyexecution attribution. - AST —
reaches/2(call-graph) refined bybranches; the reference oracle.false_confidence(Test,Req) :- test_traces(Test,Req), \+ (traces(Q,Req), test_reaches(Test,Q)).missing(Req,Path)= AST-required paths − (coverage ∪ trace) actuals.
T1c — suite consolidation / minimization (user-approved; rides T1b matrix)
Coverage matrix = test × branch × assertion. Outputs:
- Minimal fast tier — greedy/CLP(FD) set-cover over branches (reuse
schedule_tests.py). Runs on each change alongside every directly-asserting test. - Pyramid inversions — branches covered ONLY by high-layer tests → add a unit test.
- Redundancy vs false confidence — executed-but-unasserted (T1b) split from asserted-elsewhere. Drop-safety proven by mutation (removing a test must not lower mutation-kill count). Full suite stays nightly; consolidation only re-tiers the fast loop.
T2 — contract / security proofs (D1–D7)
Reachability constraints: violates_audit(Path) = gateway-handler→return path missing
audit.write; ungoverned_decision(Q) = decision reachable without the Prolog gate.
Proves the audit-loss / circuit-breaker defects or yields counterexample paths. Needs
entrypoint + must-pass-through facts (builds on T1 entrypoints).
T3 — clone / consolidation (module-count reduction)
body_clone(Q1,Q2) on body_hash (exact dup functions); structural_twin(C1,C2)
(identical method-signature sets). Feeds the duplicate audit/postman/temporal/graph
consolidation + DP2.
Real findings already surfaced (live, this session)
- 8 exact import cycles (cxp.proto↔pb2_grpc, harness.evaluators fan, semantic_graph↔ adapters↔repository).
- 5213 unused-symbol candidates — a SURFACE, not a kill-list; inflated by dynamically dispatched volk/tool/gateway entrypoints. Needs T1 entrypoint + dynamic-dispatch facts before any deletion.
CodebaseTools._run_subprocesshits a broken system swipl json module (json.so: undefined symbol PL_release_stream); janus path works → latent fallback bug..coveragehas no per-test contexts → runtime execution witness unavailable without a--cov-context=testre-run (T1b prerequisite).
Capability integration (post-T1-verify) — make the tools the DEFAULT path
User directive 2026-08-02: after T1 lands+verifies, update skills / tool-calls / workflow / instructions to exploit the progress. Goal: deterministic queries replace file-reading approximations, for the orchestrator AND every subagent, with drift caught at commit. Ordered by leverage:
.claude/agents/{auditor,builder,reviewer,refactorer,wirer}.md— deterministic-first clause: querycode_intel(impact/callers/cycles/unused/dep-path) +trace_intel(orphans/untraced/narrative) +source <qname>BEFORE reading files for structure/ impact/traceability. Highest leverage — propagates to every swarm fan-out..claude/skills/ast-toolkit/SKILL.md— add code_intel + trace_intel + a decision table (serialize_codebase_ast→symbols/refs/source/refactor; code_intel→graph closures; trace_intel→req↔code↔test) + the KB freshness/reindex model.CLAUDE.mdKey Rules +gen_level0.py→LEVEL0.md — deterministic-first rule + list the new CLIs in the L1 pointers (regenerate LEVEL0 via the generator)..claude/skills/test-loop/SKILL.md— fold in false_confidence (T1b) + unused + the T1c minimal-covering-set..pre-commit-config.yaml— advisory gates (warn-first, like doc-lint): trace_intel orphan_feature/untraced_req drift + a code_intel cycles ratchet (baseline 8).- orchestrata project memory — "deterministic-first tools exist; default to them". Decisions deferred to integration time (need T1's real counts): warn-only vs blocking for #5; whether trace_intel folds into code_intel or stays a separate CLI.
Ownership / sequencing
Opus designs + evaluates + owns the spec-consolidation ANALYSIS (interpreting the finder outputs into a reorganized, use-case-narrated spec set — the core issue: features with no usage scenario). Builders implement each tier to a settled brief. Order: T0 ∥ T1 → verify/commit → T1b (with coverage regen) → T1c → T2/T3.
T1-P2 — runtime call graph closes the dynamic-dispatch gap + derived RTM (DONE 2026-08-02)
T1 flagged that static reaches/2 over calls_resolved misses registry→handler /
DI / CXP dispatch, inflating the dead-code surface. That gap is now closed with a
RUNTIME call graph, and the payoff extended into per-spec attribution.
- Capture (
core/tracing/runtime_callgraph.py): asys.monitoring(PEP 669) CALL pytest plugin, dormant unlessORCH_RCG_DIRset. Each call site fires once (returns DISABLE) → full-suite capture at ~0 overhead (30,275 tests traced in 13.8 min vs 14.7 untraced). First-party↔first-party edges only; qnames match the AST convention. - Merge (
scripts/build_runtime_callgraph.py): unions the per-worker edge files (8,011 distinct, 7,984 AST-resolved), writesplans/runtime-callgraph.json+reports/runtime-callgraph.md, and augments each function/method inplans/ast-ultimate.jsonwith aruntime_callsfield.reaugmentre-applies that field from the committed json after any AST regen (serialize drops it). Commits 6852f84a + 94dee865. - Query (
codebase_db.pl):runtime_call/2∪calls_resolved=merged_edge/2(static wins thevia:label), BFStool_trace_path/4→code_intel trace-path FROM TOtraces end-to-end THROUGH dispatch edges static analysis cannot see (proved:session.cli.lifecycle.end → GitOpsMixin._commit_and_push_changes, 3 hops static→runtime→runtime; static-only finds nothing). - Merged traceability (
traceability_db.pl, additive — static finders byte-identical):merged_reaches/2+ merged twins of the finders.report --mergedquantifies the lift: orphans 3563→3416, specless usecases 47→36, unspecced (attribution surface) 16→243. Corpus-wide, traced-code reachability from entrypoints 10%→74% once runtime edges + the data-driven entry surface (functions with no first-party caller) are used; the residual unreachable is correctly ABSTRACT (Protocols/interfaces/models). - Derived RTM (
scripts/derive_rtm.py, Python — the exactly-one-spec discriminator is corpus-global and exceeds SWI table space): per spec, closure = merged-reach from its @traces anchors; ATTRIBUTED = untraced code in exactly one spec's closure; ALIGNED = + same module as an anchor. Yields 857 high-confidence proposed @traces across 47 specs (SPEC-006: 65, all genuine session code — module-alignment kills the shared-util false positives). The ~22 anchorless specs are cold-start gaps (no traced code to seed).
Open follow-on: expand entrypoint/2 to the real entry surface (volk handlers, registered
tools, CXP/gRPC handlers) so the static finders' orphan/specless counts match the
attribution engine's 74% reachability — the diagnosis (agents.volks 194 orphans, tools
100+, protocols 97 correctly-abstract) is in this session's analysis.