Files
rune/tests/test_contextual_construction.py
T
Nick 15408841ac feat(context): request-modal handling + 17R conversational self-hosting proof
17R live finding: 'can you fix that?' is an imperative in interrogative
clothing — the terminal-? rule misrouted polite requests to DISCUSSION
and the first proof run proceeded on a non-load-bearing intent. Fixed:
request modals (can/could/would you, please) + construction verb =
ACTION with USER_APPROVED; information openers (how/what/why/should)
outrank modals ('how would you build X?' stays method discussion).
Held-outs added for both.

17R PROOF (load-bearing intent, end-to-end): conversational request →
USER_APPROVED ConstructionIntent → classify_mode EXISTING_PROJECT_CHANGE
→ contract 17r2-task-persist-load (approved→building) → async
verification under the real event bridge (resumed via event, verdict
success) → independent authority re-read PASS → verifying→closed with
evidence checkpoints. The fixed defect: task_persist.load() did not
exist — the TUI startup loader's AttributeError was silently swallowed
(dead startup task loading, 17F audit finding).

Suite 1917 green.
2026-09-15 19:38:31 +01:00

296 lines
12 KiB
Python

"""test_contextual_construction.py — T17 §17J-§17Q: conversational
interpretation held-outs.
The matrix cases are TESTS, not trigger vocabulary: every authorization
and referent case below uses fresh paraphrases not present in the
implementation. The intent layer resolves MEANING; Scaffolding's
classify_mode (the ONE classifier) decides mode. §17O boundary holds
throughout.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import construction_intent as cint # noqa: E402
from rune import construction_mode as cm # noqa: E402
def ctx(**kw) -> cint.ConversationContext:
return cint.ConversationContext(**kw)
def propose(c, summary) -> cint.Proposal:
return c.add_proposal(summary)
# ── 17Q case 1: explicit demand, new standalone program ────────────────
def test_q1_explicit_demand_new_program():
c = ctx()
intent = cint.resolve_construction_intent(
"Build me a program that tracks my climbing sessions", c)
assert intent.action_intent == "ACTION"
assert intent.approval_state == "USER_APPROVED"
# Scaffolding (the ONE classifier) decides the mode
mode = cm.classify_mode(intent.objective, project_exists=False)
assert mode.mode == "PROJECT_BUILD" and mode.bootstrap_required
# ── 17Q case 2: "how would you build X?" is discussion only ────────────
def test_q2_how_question_never_authorizes():
c = ctx()
intent = cint.resolve_construction_intent(
"How would you build a session tracker for climbing?", c)
assert intent.action_intent == "DISCUSSION"
assert intent.approval_state == "DISCUSSION"
mode = cm.classify_mode(intent.objective)
assert mode.mode != "PROJECT_BUILD" or intent.approval_state != \
"USER_APPROVED" # no construction intent, no mutation
# ── 17Q case 3: propose → later authorize (§17L lifecycle) ─────────────
def test_q3_proposal_then_authorization():
c = ctx()
i1 = cint.resolve_construction_intent(
"Do you think the wait/resume machinery could be simpler?", c)
assert i1.approval_state == "DISCUSSION"
# Rune proposes (recorded via the context, not mutation)
propose(c, "Consolidate the four wait/verify paths into one bridge "
"adapter with per-kind verdict functions")
i2 = cint.resolve_construction_intent(
"Alright, let's go ahead with that.", c)
assert i2.approval_state == "USER_APPROVED"
assert i2.resolved_referent == c.live_proposals()[0].proposal_id
assert "Consolidate" in i2.objective
# ── 17Q case 4: unseen indirect authorization of a prior proposal ──────
def test_q4_unseen_indirect_authorization():
c = ctx()
propose(c, "Add a drift-aware resume gate to the programme runner")
i = cint.resolve_construction_intent(
"Yeah that's the one, make it happen.", c)
assert i.approval_state == "USER_APPROVED"
assert i.resolved_referent is not None
# ── 17Q case 5: ordinal option selection among multiple proposals ──────
def test_q5_ordinal_referent():
c = ctx()
p1 = propose(c, "Option A: poll-based retry sweep")
p2 = propose(c, "Option B: event-driven resume with watchdog")
i = cint.resolve_construction_intent(
"Go with the second approach.", c)
assert i.approval_state == "USER_APPROVED"
assert i.resolved_referent == p2.proposal_id
# ── 17Q case 6: ambiguous "do that" → clarification, never a guess ─────
def test_q6_ambiguous_referent_asks():
c = ctx()
p1 = propose(c, "Rewrite the transport layer")
p2 = propose(c, "Rewrite the router escalation")
# both proposed same turn → recency NOT decisive
i = cint.resolve_construction_intent("Do that.", c)
assert i.ambiguity == "REFERENT"
assert i.approval_state != "USER_APPROVED"
assert i.probe_questions # asks, doesn't guess
# ── 17Q case 7: explicit imperative "build that", resolved referent ────
def test_q7_imperative_with_resolved_referent():
c = ctx()
propose(c, "Bounded CLI for the ledger")
i = cint.resolve_construction_intent("Build that.", c)
assert i.action_intent == "ACTION"
assert i.approval_state == "USER_APPROVED"
assert i.resolved_referent == c.live_proposals()[0].proposal_id
# ── 17Q case 8: topic changed → stale proposal not selected ────────────
def test_q8_stale_context_resistance():
c = ctx()
propose(c, "Refactor the old exporter")
c.record_turn("user", "actually, never mind that — let's talk about "
"the TUI fonts instead")
c.record_turn("assistant", "sure, fonts it is")
c.next_turn()
i = cint.resolve_construction_intent("Build that.", c)
# "that" now plausibly refers to the font topic, not the stale
# proposal — the layer must NOT silently build the exporter
assert i.approval_state != "USER_APPROVED" or \
"exporter" not in i.objective
# ── 17Q case 9: research discussion with construction language ─────────
def test_q9_research_not_mutation():
c = ctx()
i = cint.resolve_construction_intent(
"Research how other tools implement retry budgets — what would "
"building something like that involve?", c)
assert i.action_intent == "DISCUSSION"
# ── 17Q case 10: non-construction meaning of "build" ───────────────────
def test_q10_non_construction_build():
c = ctx()
i = cint.resolve_construction_intent(
"Build a list of the pros and cons.", c)
# imperative "build" + non-constructible object → Scaffolding's
# verb-object check refuses construction semantics
mode = cm.classify_mode(i.objective)
assert mode.mode == "NOT_CONSTRUCTION"
# ── 17Q cases 11-14: mode proportionality through the ONE classifier ───
def test_q11_existing_project_change():
mode = cm.classify_mode("fix the pagination bug in the rune TUI",
target_project="rune", project_exists=True)
assert mode.mode == "EXISTING_PROJECT_CHANGE"
def test_q12_small_local_change():
mode = cm.classify_mode("fix the typo in the README",
target_project="rune", project_exists=True)
assert mode.mode == "LOCAL_CHANGE"
def test_q13_large_architectural_migration():
mode = cm.classify_mode(
"migrate the whole provider layer to a new adapter protocol",
target_project="rune", project_exists=True)
assert mode.mode == "EXISTING_PROJECT_CHANGE"
# depth is Scaffolding's decision; the intent layer's consequence
# signal is HIGH (checked at the intent layer level below)
c = ctx(active_project="rune")
i = cint.resolve_construction_intent(
"migrate the whole provider layer to a new adapter protocol", c)
assert i.consequence_level == "HIGH"
def test_q14_new_standalone():
mode = cm.classify_mode("a small CLI that renames photos by EXIF date",
project_exists=False)
assert mode.mode == "PROJECT_BUILD"
# ── 17Q cases 15-16: command / natural-language / contextual
# equivalence — all three produce the same intent semantics ──────────
def test_q15_command_and_language_same_backend():
# /build X (command form) and "build X" (natural language) must both
# end at classify_mode with the same inputs
cmd_objective = "a password generator with configurable entropy"
nl_objective = "build me a password generator with configurable entropy"
m_cmd = cm.classify_mode(cmd_objective, project_exists=False)
i_nl = cint.resolve_construction_intent(nl_objective, ctx())
m_nl = cm.classify_mode(i_nl.objective, project_exists=False)
assert m_cmd.mode == m_nl.mode == "PROJECT_BUILD"
assert m_cmd.bootstrap_required == m_nl.bootstrap_required
def test_q16_contextual_and_explicit_equivalent_semantics():
c = ctx(active_project="rune")
propose(c, "Add a --dry-run flag to the deploy script")
i_ctx = cint.resolve_construction_intent("yes, go ahead with that", c)
i_exp = cint.resolve_construction_intent(
"Add a --dry-run flag to the deploy script",
ctx(active_project="rune"))
# contextual route resolves through the referent; explicit route
# through the imperative — SAME approval semantics
assert i_ctx.approval_state == i_exp.approval_state == "USER_APPROVED"
assert i_ctx.action_intent == i_exp.action_intent == "ACTION"
# ── 17Q case 17-18: provider ownership + approval not bypassed ─────────
def test_q17_provider_ownership_preserved():
# the intent layer emits no provider field at all — provider
# selection cannot pass through it (17O boundary, structural)
c = ctx()
i = cint.resolve_construction_intent("build a fixture runner", c)
assert not hasattr(i, "provider") and "provider" not in i.to_dict()
def test_q18_approval_not_bypassed_by_context():
# a proposal is NOT authorization: approval_state stays PROPOSAL
# until a user turn authorizes
c = ctx()
propose(c, "Rewrite the auth subsystem")
i = cint.resolve_construction_intent(
"sounds plausible, tell me more about the migration steps", c)
assert i.approval_state != "USER_APPROVED"
# ── 17Q case 19: unknown target → clarification ────────────────────────
def test_q19_unknown_target_asks():
c = ctx() # no active project
i = cint.resolve_construction_intent("Implement the export feature.", c)
assert i.ambiguity == "TARGET"
assert i.approval_state != "USER_APPROVED"
assert i.probe_questions
# ── 17Q case 20: superseded proposal not accidentally built ────────────
def test_q20_superseded_proposal_ignored():
c = ctx()
p_old = propose(c, "Use polling for CI waits")
c.supersede_proposal(p_old.proposal_id)
p_new = propose(c, "Use event-driven waits with watchdogs")
i = cint.resolve_construction_intent("go ahead with that", c)
assert i.resolved_referent == p_new.proposal_id
# ── 17N: structured intent shape ────────────────────────────────────────
def test_17n_intent_shape_complete():
c = ctx(active_project="rune")
i = cint.resolve_construction_intent("fix the flaky lease test", c)
d = i.to_dict()
for f in ("intent_id", "objective", "requested_effect",
"resolved_target", "resolved_referent", "origin_context_id",
"action_intent", "approval_state", "confidence", "ambiguity",
"consequence_level", "relevant_constraints",
"classification_evidence"):
assert f in d, f"missing intent field {f}"
assert d["resolved_target"] == "rune"
# ── §17L: approval-state lifecycle legality ─────────────────────────────
def test_17l_discussion_never_mutates():
c = ctx(active_project="rune")
for phrase in ("what do you think about the deploy script?",
"should we improve the wait semantics?",
"how would you build the exporter?"):
i = cint.resolve_construction_intent(phrase, c)
assert i.approval_state == "DISCUSSION"
assert i.action_intent == "DISCUSSION"
# ── 17R finding: polite request form is action intent ──────────────────
def test_17r_polite_request_authorizes():
"""'Can you fix that?' is an imperative in interrogative clothing —
found live during the 17R self-hosting proof (the terminal-? rule
misrouted it to DISCUSSION and the flow would have proceeded on a
non-load-bearing intent)."""
c = ctx(active_project="rune")
i = cint.resolve_construction_intent(
"The TUI's startup task loading is silently broken — can you "
"fix that?", c)
assert i.approval_state == "USER_APPROVED"
assert i.action_intent == "ACTION"
assert i.resolved_referent == "this-turn-topic"
def test_17r_information_questions_stay_discussion():
c = ctx(active_project="rune")
for phrase in ("Should we fix the startup task loading?",
"Could we improve the TUI?",
"Would it be worth fixing the loader?"):
i = cint.resolve_construction_intent(phrase, c)
assert i.approval_state == "DISCUSSION", phrase
assert i.action_intent == "DISCUSSION", phrase