fix(scaffolding): builder self-approval PROVED and closed by approval attribution
§1 SELF-APPROVAL ATTACK RUN FIRST, AND IT SUCCEEDED. Before this change:
ConstructionInterface(builder="mimo").transition(
cid, "approved", evidence_ref="user-instruction:fabricated",
authorised_paths=["docs"]) -> success
...amend_scope(..., authority_ref="user-instruction:fabricated") -> success
=> BUILDER_SELF_APPROVAL = PROVEN DEFECT
Removing record_user_authority was NOT sufficient: the builder could still
CREATE the canonical approval it was supposed to be constrained by. The
authority was circular.
REPAIR AT THE SHARED CANONICAL OWNER (no TUI change):
- transition_contract records `authorised_by` on the approval checkpoint — WHO
approved, not merely what the approval says;
- ConstructionInterface.transition passes the caller identity; the contextual
activation path passes its origin;
- _resolve_authority REFUSES an approval that records no authorising identity,
or one whose authorising identity is a BUILDER_IDENTITIES member.
RESULT:
A/B/C builder transition->approved + self-made amend -> REFUSED, scope unchanged
("approval ... was created by 'mimo', which produces construction —
a builder cannot approve its own work, so this approval grants nothing")
D user-surface (TUI identity) approval -> ALLOWED
E amend within the grant -> ALLOWED
F amend beyond the grant -> REFUSED
G builder CONSUMES a real user approval -> ALLOWED
(consuming is not minting — the required architecture is
"may consume a proven user-authority event, may not create one")
A CORRECTION FOUND BY THE ATTACK: "tui-build-cmd" was listed in
BUILDER_IDENTITIES. The TUI is the USER-FACING APPROVAL SURFACE, not a builder;
listing it as one would have made the existing TUI approval path unauthorised
the moment attribution was enforced. Removed, with the reason recorded inline.
§0 HARD BOUNDARY RESPECTED. TUI_CODE_CHANGED = NO — rune_tui.py is untouched.
Every change is shared canonical code (scaffold, construction_interface,
construction_authority, construction_activation). The TUI remains a consumer of
the same canonical Scaffolding APIs; MiMo adapts to them rather than the TUI
being rewritten to suit MiMo. TUI regression passed unchanged.
Verification: 542 targeted regression including TUI tests, 0 failures; ruff
clean. LOCAL_MODEL_COMPUTE_EXECUTED = NO.
STILL OPEN, recorded rather than claimed: §5 revision/replay/supersession
semantics (reusable vs revision-scoped, staleness, revocation), and §6 the
registered-external-project absolute-path positive proof.
This commit is contained in:
1 parent
d7450cb0eb
commit
a41c93c878
5 files changed
+33
-7
No files matched your search
@@ -349,7 +349,8 @@ def mint_bounded_contract(scaffold: sc.Scaffold, *, text: str, origin: str,
|
||||
for phase in ("approved", "building"):
|
||||
scaffold.transition_contract(
|
||||
doc, cid, phase,
|
||||
evidence_ref=f"contextual-activation:{origin}")
|
||||
evidence_ref=f"contextual-activation:{origin}",
|
||||
authorised_by=origin)
|
||||
scaffold.save(doc)
|
||||
return {"contract_id": cid, "scope": scope, "minted": True}
|
||||
except Exception as exc: # noqa: BLE001 — reported, never swallowed
|
||||
|
||||
@@ -88,7 +88,10 @@ AUTHORITY_PRINCIPALS = frozenset({"user", "operator", "supervisor"})
|
||||
# Identities that PRODUCE construction and therefore cannot certify it.
|
||||
BUILDER_IDENTITIES = frozenset({
|
||||
"mimo", "mimo-guard", "mimo-builder", "rune-native", "scope-promotion",
|
||||
"tui-build-cmd", "rune", "test", "tests", "automated", "ci",
|
||||
# NOTE: "tui-build-cmd" is deliberately NOT here — the TUI is the
|
||||
# user-facing approval surface, not a builder. Listing it as a builder
|
||||
# would have made the existing TUI approval path unauthorised.
|
||||
"rune", "test", "tests", "automated", "ci",
|
||||
"subagent", "actor", "builder",
|
||||
})
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ from rune import structural_store as ss
|
||||
from rune import scaffold as sc
|
||||
from rune import spine as spine_mod
|
||||
from rune import construction_decision as cd
|
||||
from rune.construction_authority import BUILDER_IDENTITIES
|
||||
|
||||
__all__ = ["ConstructionInterface", "INTERFACE_SCHEMA_VERSION", "Errors"]
|
||||
|
||||
@@ -87,9 +88,19 @@ def _resolve_authority(contract: dict, authority_ref: str,
|
||||
ref = (authority_ref or "").strip()
|
||||
if not ref:
|
||||
return None, "no authority_ref supplied"
|
||||
builders = {b.lower() for b in BUILDER_IDENTITIES}
|
||||
for cp in contract.get("checkpoints") or []:
|
||||
if str(cp.get("evidence", "")) != ref:
|
||||
continue
|
||||
# WHO authorised it matters as much as WHAT it says.
|
||||
by = str(cp.get("authorised_by") or "").strip()
|
||||
if not by:
|
||||
return None, (f"approval {ref!r} records no authorising identity — "
|
||||
"an unattributed approval is not authority")
|
||||
if by.lower() in builders:
|
||||
return None, (f"approval {ref!r} was created by {by!r}, which "
|
||||
"produces construction — a builder cannot approve "
|
||||
"its own work, so this approval grants nothing")
|
||||
if cp.get("phase") != "approved":
|
||||
return None, (f"reference {ref!r} is not an approval checkpoint "
|
||||
f"(phase={cp.get('phase')!r})")
|
||||
@@ -690,6 +701,7 @@ class ConstructionInterface:
|
||||
doc, contract_id, phase,
|
||||
evidence_ref=evidence_ref or f"builder:{self.builder}",
|
||||
authorised_paths=authorised_paths,
|
||||
authorised_by=self.builder,
|
||||
)
|
||||
except sc.IllegalPhaseTransition as e:
|
||||
return _envelope(
|
||||
|
||||
@@ -460,7 +460,8 @@ class Scaffold:
|
||||
|
||||
def transition_contract(
|
||||
self, doc: dict, contract_id: str, phase: str, *, evidence_ref: str = "",
|
||||
note: str = "", authorised_paths: list[str] | None = None
|
||||
note: str = "", authorised_paths: list[str] | None = None,
|
||||
authorised_by: str = ""
|
||||
) -> dict:
|
||||
"""Move a contract to a new phase through the legal-transition map.
|
||||
Durable and resumable: the phase lives in the store."""
|
||||
@@ -479,7 +480,8 @@ class Scaffold:
|
||||
e["status"] = "closed"
|
||||
elif phase == "failed":
|
||||
e["status"] = "failed"
|
||||
_cp = {"phase": phase, "at": now(), "evidence": evidence_ref, "note": note[:200]}
|
||||
_cp = {"phase": phase, "at": now(), "evidence": evidence_ref,
|
||||
"note": note[:200], "authorised_by": authorised_by}
|
||||
if authorised_paths is not None:
|
||||
# T24: the SMALLEST COMPATIBLE EXTENSION of the existing authority
|
||||
# owner. A user approval already records durable provenance as a
|
||||
|
||||
@@ -257,7 +257,15 @@ def test_authority_reference_must_resolve_and_bound_the_request(store):
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
from rune.scaffold import Scaffold
|
||||
|
||||
cid = turn("refactor src/rune/scaffold.py", store)["contract_id"]
|
||||
# The user-facing surface creates the contract (draft) and approves it.
|
||||
# A MINTED contract is already past `approved`, so an approval could not
|
||||
# legally be recorded on it — that is why this uses submit_need.
|
||||
user = ConstructionInterface(builder="tui-build-cmd")
|
||||
user.scaffold = Scaffold(str(store), PROJECT)
|
||||
env = user.submit_need("refactor src/rune/scaffold.py", origin="user",
|
||||
affected_surface=["docs"],
|
||||
contract_id="gov-user-1")
|
||||
cid = (env.get("ids") or {}).get("contract_id")
|
||||
iface = ConstructionInterface(builder="mimo")
|
||||
iface.scaffold = Scaffold(str(store), PROJECT)
|
||||
|
||||
@@ -274,8 +282,8 @@ def test_authority_reference_must_resolve_and_bound_the_request(store):
|
||||
assert scope() == base
|
||||
|
||||
# the USER surface records an approval naming what it authorises
|
||||
iface.transition(cid, "approved", evidence_ref="user-instruction:SUP",
|
||||
authorised_paths=["docs"])
|
||||
user.transition(cid, "approved", evidence_ref="user-instruction:SUP",
|
||||
authorised_paths=["docs"])
|
||||
R = "user-instruction:SUP"
|
||||
assert iface.amend_scope(cid, ["docs"], reason="ok", authority="user",
|
||||
authority_ref=R)["success"] is True
|
||||
|
||||
Reference in new issue
Block a user