Skip to content

The console's read-only claim is derived from scope, which #563 established does not mean 'does not mutate' #577

Description

@serge-ivo

The console tells an auditor "nothing can change anything" from a field that does not mean that

store/console/src/lib/toolPolicy.ts:106:

export function writesOwnData(policy: readonly ToolPolicyEntry[]): boolean {
	return listedTools(policy).some((t) => t.scope === "write" && !t.connector);
}

and :77:

export function mayWrite(t: ToolPolicyEntry): boolean {
	return t.scope === "write" || t.writeConsent === "per_call";
}

writesOwnData decides which sentence toolScopeSummary renders above the tool switches —
including the read-only claim that :116's own comment calls "the one #525 made dangerous."

#563 established what scope actually is: the write-consent trigger, not a mutation label.
FIRST_PARTY_TOOLS omit it and instance-tool-policy.ts defaults it to "read", so nine
mutating tools — start_work, stop_work, end_coding_session, set_behaviour,
set_stats_card, run_pipeline, record_feedback, create_ticket, dedupe_upsert — report
scope: "read" with no connector. writesOwnData cannot see any of them.

Why nobody has seen it

BASE always contains write_memory, which is scope:"write" with no connector, so the
predicate is true for every agent and the wrong sentence never renders. The claim is correct
today because of an invariant that exists for unrelated reasons and is declared nowhere as
load-bearing. Remove write_memory from BASE, or ship an agent that declares its tools without
it, and a Coder holding start_work and run_pipeline is described to its owner as unable to
change anything.

What changed that makes this fixable

#563 shipped a mutates field on the listing precisely because scope could not answer this
question — 104 rows, 52 mutates:true, measured in production. writesOwnData is asking the
mutates question and reading the scope field.

mayWrite is a separate call and should be looked at, not assumed: its own comment argues
scope plus per_call is right for the connector-grant decision, because http_request is
scope:"read" yet the caller names the method. That reasoning may survive mutates intact.
Decide it explicitly rather than changing both because they sit together.

Acceptance criteria

  1. writesOwnData derives from mutates, or a recorded reason why scope is correct there.
  2. mayWrite is decided separately and the decision stated — its existing comment is a real
    argument and must be answered, not overwritten.
  3. A test pins the case that is currently masked: a policy containing a mutates:true,
    scope:"read", connector-less tool and no write_memory renders the "can change things"
    sentence. It must fail on today's code.
  4. The BASE-contains-write_memory dependency is either removed or declared where it is
    relied on, so the next person does not rediscover it as a bug.

Verified vs inferred

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions