M3.3: the trust dial (the agent merges approved PRs)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m29s
CI / e2e (pull_request) Successful in 11m23s

A project's trust.merge is operator (the default) or approved (L3, D5,
#65). Set to approved, the harness, never the model, merges a PR in the
PR watch once an approving review from a gitea.operators account is on
its current commit, its checks pass (none at all counts; pending
waits) and it's mergeable; the item then goes on as after any merge.
Each reason to hold off (failed checks, conflicts, a refused merge) is
said once per commit.

Gitea enforces it: protection requires approvals from an operators-only
whitelist and dismisses stale ones. At start, for each approved
project, the agent checks by behaviour that its own unapproved merge is
refused, with a throwaway branch and PR made through the API (never the
workspace); if it isn't refused, the operator merges until a restart.

- gitea: approvals, combined check status, a PR's head commit and
  mergeability, API-made branches and their deletion.
- Test and bring-up repositories get the protection.
- Tests: the decision (unit), the hard wall on a real Gitea
  (test_trust_live), an e2e test where the operator only approves, and
  a walk property, merges_only_approved, checked against a mutant that
  ignores which commit was approved.

Closes #65

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-08 13:48:16 +00:00
1 parent 21cbf1682c
commit ab469b60bb
17 files changed
+733 -11

No files matched your search

+6 -3
View File
@@ -726,9 +726,12 @@ Installing on any other host is dangerous.
- **The PR may have moved on meanwhile.** After pushing a fix, the agent checks the
PR: if it merged before the fix landed, the fix goes out as a new PR; if it was
closed, the agent says so and asks.
- **Merging follows the trust dial** (L3). Today the operator merges. If the dial
lets the agent merge its own PRs once checks pass, branch protection must require
an approval the agent's account can't give, so Gitea enforces the rule.
- **Merging follows the trust dial** (L3), set per project (`trust.merge`). By
default the operator merges. Set to `approved`, the agent merges a PR once an
operator has approved its current commit and its checks pass; branch
protection requires that approval from an operators-only whitelist and
dismisses stale approvals, so Gitea enforces the rule, and the agent checks at
start that its own unapproved merge is refused.
### D6. Action classes
+20 -3
View File
@@ -25,7 +25,7 @@ Only `nisse-dev` has real code; `nisse-core` and `nisse-ops` are placeholders.
|---|---|---|
| **L1** Work model | mostly done | Work items with an active pointer (M1.1); issue intake (M2.1); queue, parking and blocked items (M2.2) on the new loop; no grace period yet |
| **L2** Agent loop | mostly done | The loop (Phase 2), the only one since the switch-over (M2.8): driver runs, waits, `stop`, `abandon`, question runs |
| **L3** Authority | partial | Go-aheads bound to the restatement's revision, on the issue and its conversation (M2.1); action classes and the fast path, with `allow` bound to one dangerous action (M3.1); no trust dial yet (M3.3) |
| **L3** Authority | partial | Go-aheads bound to the restatement's revision, on the issue and its conversation (M2.1); action classes and the fast path, with `allow` bound to one dangerous action (M3.1); the trust dial, `trust.merge` (M3.3) |
| **L4** Classifier | mostly done | On the classifier role (the local model), for what the fast path doesn't know; fails closed; overrides export as an evaluation set (M3.2). Promoting its verdicts into rules is by hand |
| **L5** Conversation | partial | Matrix transport, mentions, lenient commands; on the new loop, a thread per item, named and ambiguous commands, plain-language go-aheads (M2.3); little explanation |
| **L6** Context | partial | Context fitting, per-role views and orchestrator facts (M1.2); no scope or agent memory |
@@ -355,8 +355,25 @@ fail-closed verdicts, overrides as an evaluation set.
- Deferred: promoting verdicts the classifier keeps agreeing on into rules (by
hand, from exported cases); the trust dial (M3.3).
**M3.3 The trust dial.** *Needs discussion.* Per project (D8); agent merging behind
protection it can't satisfy, verified by a test (D4, D5, #65).
**M3.3 The trust dial.** *Needs discussion.* **Done** (this PR). Per project (D8);
agent merging behind protection it can't satisfy, verified by a test (D4, D5, #65).
- `trust.merge` per project: `operator` (the default) or `approved`: the harness
(never the model) merges a PR in the PR watch once an approving review from a
`gitea.operators` account is on the PR's current commit, its checks pass (no
checks at all counts as passing; pending waits) and it's mergeable; then the
item goes on as after any merge. Each reason to hold off is said once.
- Protection, verified at start per `approved` project: a throwaway branch and
PR made through the API (never the workspace), whose merge must be refused;
if it isn't, the operator merges until a restart. Real Gitea is tested too
(`test_trust_live.py`): refused unapproved, merged once approved, refused
again after a commit lands on the approved PR.
- Test and bring-up repositories get the protection: approvals from an
operators-only whitelist, stale approvals dismissed, the agent on the merge
whitelist.
- Decided: two levels only (no merging on checks alone, D5); verification by
behaviour, since a collaborator can't read protection and must never set it.
Deferred: a project-setup script for the operator (protection, labels) and a
label check, #130.
### Phase 4 — The environment