Skip to content

Design: mcpx write policy — one gate for writes (ask/allow/deny, argument rules, unknown-outcome ledger) #2126

Closed
3 of 3 issues completed

Description

@dezren39

Design (repo design architect, pass 12, 2026-10-29): one policy evaluator in a new internal/policy package, evaluated inside resolveAndGuard after it gains the call's arguments, in a fixed order — hidden-tool check (existing) → unknown-write ledger check → argument rules → write mode → confirmDestructive floor. The three origin issues below are three separately-filed slices of one design; they share one seam, one write classification, and one evaluator (#1986's own body: "Design the two together… the evaluator is one function so the two can never disagree"). This design adopts them as its phases instead of filing parallel implementation issues.

Design doc: files/designs/code-mcpx-write-policy.md in the keep-the-four-repos-healthy goal workspace (problem, current-state audit against origin/main 73ba3a96, proposal, alternatives, risks, open questions).

Why now / grounding

Decisions recorded in the design doc

  1. Classification shared and annotation-derived (write iff readOnlyHint is not true); annotation dishonesty stays owned by mcpx: smaller conformance gaps -- user identity, annotation trust, rate limits, cursors, URI encoding, prompt validation, restart, invalidation #1957 — argument rules are the annotation-independent control.
  2. Modes are settings (policy.write, daemon scope, ask default per mcpx: a write policy -- ask, allow, deny -- with the exact call shown when asking #1979's acceptance; per-server writePolicy override); rules are per-server config (argumentRules, kept over a folded policy block — mcpx: argument rules -- allow a call by its arguments, per tool and per argument (JSON Schema; depends on #254) #1986 open question 1 resolved); the evaluator is the shared part.
  3. ask renders the exact call (tool + canonical arguments) through the existing broker confirmation; elicit.confirmDestructive is subsumed as a floor (destructive asks even under allow — decision 0002's "no level skips a consent gate").
  4. Rule refusals render natively per surface (daemon 400 HiddenTool-style naming argument path/value/constraint/rule origin; MCP pass-through isError: true result) — mcpx: argument rules -- allow a call by its arguments, per tool and per argument (JSON Schema; depends on #254) #1986 open question 3 resolved by the hidden-tool precedent. Rules only tighten (allOf across pool/server/layered files); bad schema or non-RE2 pattern is a load-time config error; dead rules warn at load and on catalog change.
  5. Unknown-outcome ledger is a versioned state-dir file (server, tool, args SHA-256 — never the arguments — time, surface), written atomically, persisted across restarts, never auto-cleared; "ambiguous" is conservative (post-dispatch marker only once request bytes were written).
  6. The policy is an operator consent gate, not the autonomy dial (decision 0002, pkgs/mcpx/docs/decisions/0002-autonomy-dial.md: confirmDestructive classed "not the dial"); neither clamps the other, and Phase 1 documents the interaction.

Phased breakdown (each independently landable)

Not in this design (open questions in the doc)

Schema advertising of rules (allOf in mcpx types / tools/list); pinning/set (#1933's slice); redaction of secret-shaped values in ask renderings; per-principal policy (waits on #1968/#1972); ledger visibility in mcpx status.

Related: #1984 (tracker that lists #1979/#1977), #1481 (validation — dependency, not a slice), #1957 (annotation trust), #1634 (landed; its client recovery sits below this design's recording point).

{"kind":"design","by":"repo-design-architect-code","pass":12,"date":"2026-10-29","design_doc":"files/designs/code-mcpx-write-policy.md","phases":{"1":1979,"2":1986,"3":1977},"origins":{"1979":"dezren39/nix#340","1986":"dezren39/nix#355","1977":"dezren39/nix#339"}}

Activity

  1. added
    priority/normalOrdinary priority (the default)
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    epicMeta-issue grouping multiple items (org Project Type=Epic)
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    on Oct 29, 2026
  2. developing-today commented on Oct 29, 2026

    @developing-today
    Contributor
  3. added
    kind/designA design proposal (design-architect): multi-step, phased, decision-bearing
    on Oct 29, 2026
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 3, 2026
  5. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Triage — 2026-11-03 (repo-health queue, worker repo-worker-code)

    Verdict: the design is fully delivered; nothing in this epic remains to design or build. Closing with evidence.

    All three phases named in this design landed on main, verified at ffad69add:

    Phase Issue Merged Evidence on current main
    1 — the gate (policy.write, classification, args plumbed into resolveAndGuard, deny, destructive floor) #1979 PR #2829 (bd48deb1e) pkgs/mcpx/internal/policy/policy.go, pkgs/mcpx/internal/daemon/consumer.go, pkgs/mcpx/internal/settings/effect.go; write_policy_test.go
    2 — argument rules (argumentRules, schemaval compilation, refusals, dead-rule warnings) #1986 PR #2855 (cffd524fd) pkgs/mcpx/internal/config/argumentrules.go, pkgs/mcpx/internal/daemon/consumer.go, pkgs/mcpx/internal/daemon/argument_rules_test.go
    3 — unknown-outcome ledger (mcpx writes clear, /v1/writes/{server}/clear, restart persistence, guard step) #1977 PR #4721 (a41cba35d) pkgs/mcpx/internal/daemon/write_ledger.go, pkgs/mcpx/internal/api/ops_writes.go, pkgs/mcpx/internal/daemon/registry.go (ledgerLookupFor), pkgs/mcpx/docs/parity.md

    The evaluation order this design fixed (hidden-tool → ledger → argument rules → mode → confirmDestructive floor) is the one the landed code implements; the ledger's own error text names mcpx writes clear <server>.

    What this close does not do: it does not close the sub-issues. #1977 is still open (status/needs-triage, queue/queued) even though its implementation merged in #4721; its closure is that issue's own queue task, and #1975 has the same shape. Tracker #1984 already records both as "implemented and merged but not yet closed" with the same SHAs, so no bookkeeping is lost here.

    Out-of-scope items this design listed (schema advertising of rules, pinning/set, secret redaction in ask renderings, per-principal policy, ledger visibility in mcpx status) remain separately tracked and are not part of this epic.

    {"kind":"triage","by":"repo-worker-code","lane":"9aca43b4-5439-4939-9f71-323f5d32521b","date":"2026-11-03","verdict":"delivered","phases":{"1979":"PR #2829 bd48deb1e","1986":"PR #2855 cffd524fd","1977":"PR #4721 a41cba35d"},"verified_at":"ffad69add","action":"close design epic; sub-issue closure stays with each child's own queue task"}
    
  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
  7. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: triaged (coverage pass)
    Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 40 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
    Found via: coverage pass 2026-11-03

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/lLarge: multi-day or open-ended; expect staged check-insepicMeta-issue grouping multiple items (org Project Type=Epic)kind/designA design proposal (design-architect): multi-step, phased, decision-bearingpriority/normalOrdinary priority (the default)queue/queuedHas a queued task in the Supabase agent queuesource/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions