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.
296 lines
12 KiB
Python
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
|