Skip to content

Describe the surface in model-readable metadata, don't direct the model - #167

Merged
sea-snake merged 78 commits into
mainfrom
claude/icp-marketplace-compliance-vpawel-10-simplify-metadata
Aug 31, 2026
Merged

Describe the surface in model-readable metadata, don't direct the model#167
sea-snake merged 78 commits into
mainfrom
claude/icp-marketplace-compliance-vpawel-10-simplify-metadata

Conversation

@aterga

@aterga aterga commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

The server instructions and the served tool descriptions told a model how to work: START WITH open_app, ALWAYS call this FIRST, NEVER guess a domain, "web search the official URL", a numbered seven-step flow across tools, and a routing chain to run before answering anything. Both directories read these fields, and both prohibit a description that steers the model rather than describing the tool — so every surface now describes what it is, what it returns, what it rejects, and what it requires.

What a caller needs is kept, and after review the emphasis moved: both directories expect a description to say when a tool applies and what is unsafe to pass, so the rule this PR enforces is not "no guidance" but "none of the five manipulations the directories actually prohibit" — unrelated behavioral instructions, overly broad triggering, preference over or interference with other tools, calls to unrelated external software, and hidden or obfuscated instructions.

The second half of the PR became an accuracy pass. Review turned up a recurring defect — metadata claiming more than the code does — and every instance found is fixed and listed below.

Stacked on #163 (base is that branch, so the diff shows this change alone; GitHub retargets to main when #163 merges). It rewrites the same regions #163 touched — the update-call description and the instructions' policy paragraph — so branching off main would have lost that work.

Related issues

None — follow-up in the directory-submission series.

Changes

  • Server instructions: 10005 → ~3600 characters. What remains is a description: where textual Candid applies (and where it does not), what the two tool families act on, what a derivation origin is and which reads it gates, the two Internet Identity access levels, how canister values are stored, the financial-transactions policy (stated as policy — the base branch removed the copy of compliance.rs's method families and canister scopes, and this branch takes that rewrite verbatim), and where building and deploying happen. Gone: the START WITH entry-point rule, the routing chain, the numbered "Typical flow", the locale-conversion directives, "web search or ask the user", and the caps imperatives.
  • All 11 served descriptions rewritten as capability statements. canister_query loses its "start with open_app first" preamble; open_app loses its ROUTING block; resolve_app and discover_app_canisters lose the "NEVER guess / web search" rules. open_app carries, per review, the one constraint the runtime cannot enforce: pass the user's app name unchanged, pass only a URL the user supplied or one from a verified official source, and do not construct a domain from the name — because the IC-evidence check shows a domain is served from the Internet Computer, not that it is the app the user meant.
  • Argument and reply schemas follow the same rule (calls.rs, discover.rs, identities.rs): imperatives become plain statements, and "or ask the user for it" becomes what actually provides the value.
  • Accuracy fixes, all found in review and each verified against the handler first. The IC-evidence refusal was asserted unconditionally in six places when it fires only where the derivation origin must be assumed — a declared origin is never probed. discover_app_canisters claimed a refusal it does not perform (it returns an empty result). get_canister_api_doc promised a structured result "in every case", in its description and again in its root schema. retry: true was presented as a diagnosis, though an unreadable interface and a rejecting or trapping method both set it. api_doc_available was presented as "a guide is returned" when it reports a declaration — corrected on get_canister_candid, open_app, the DiscoveredCanister schema, and the runtime notes. ResolveAppOutput.alternative_origins was attributed to the application origin, though it is read from the derivation origin. canister_query promised a did-you-mean repair on any empty OQL result, when it needs an unknown start and a schema read that returns entities. get_canister_oql_schema's whole schema documented an anonymous path the handler rejects. Three tools did not disclose their session prerequisite. The identity-mismatch note claimed a wrong derivation origin when a different account or Internet Identity produces the same difference. And the access levels govern only calls signed with the session's delegation, not every update call — which was also wrong in both submission documents' reviewer procedures, where a reviewer following the step would not have seen the promised behavior.
  • New test model_readable_metadata_respects_marketplace_policy scans the server instructions, all 11 descriptions, and every argument and reply schema for those five prohibited categories. It checks each schema's decoded strings rather than its JSON serialization — escaping turns an invisible character into printable ASCII, which would split a banned phrase and pass the character check — matches phrases on collapsed whitespace, and allows only printable ASCII plus the three punctuation marks the metadata actually uses, so no invisible character can ride along. A second test, open_app_metadata_forbids_a_constructed_domain, pins the safeguard above on both surfaces a model reads before calling.
  • Submission docs state the real rule (descriptions explain when a tool applies; the five prohibited manipulations) rather than "must not manipulate how the model works", record the guessed-identifier posture, and drop a stale line claiming the instructions "prime the assistant".

Out of scope: the 13 deferred IcProtocolTools descriptions are not served and are unchanged. Two behavior changes review raised are deliberately not here, since they are not metadata: classifying retryable agent errors, and extending the IC-evidence probe to declared derivation origins.

Testing

  • cargo build --locked --all-targets
  • cargo test --workspace — 146 core + 52 / 10 / 9 / 20 / 1, all green
  • cargo clippy --workspace --all-targets — unchanged 10-warning baseline (this repo is not rustfmt-clean; no repo-wide cargo fmt, matching prior PRs)
  • npm test --prefix monitoring/mcp-status (dashboard unchanged)

Checklist

  • I have read the Contributing guidelines.
  • Docs (README / comments) updated for any user-visible change.
  • No secrets, credentials, or internal-only information are included.

Generated by Claude Code


Generated by Claude Code

claude added 11 commits August 28, 2026 09:26
The name registry exists to route agents to well-known apps; routing
them to a staking/funds frontend sits badly with the server's
not-a-financial-tool posture, so the NNS entry is removed: no name
resolution for "nns", no repair guidance toward it, and the unknown-app
note no longer enumerates it. The KNOWN_DERIVATION_ORIGINS mappings
stay: a user who brings an NNS URL themselves still derives the correct
per-app identity for reads instead of a silently wrong (empty-looking)
one, and update calls to the NNS canisters are refused by the
finance-canister guard added alongside this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Adds a third group to the financial-transactions guard: a curated,
labeled disallow list of canisters whose purpose is holding, staking,
exchanging, or moving value. On these, canister_update_call refuses
EVERY update method; each entry carries what the service is and how it
is financial, and the refusal interpolates both labels verbatim.

The list (49 entries, every id verified against the IC dashboard's
canister registry and the services' own published sources on
2026-08-28):

- The network's own funds infrastructure: NNS Governance
  (manage_neuron disburses neuron-held ICP), both NNS dapp frontends,
  the Genesis Token canister, the ICP ledger, the cycles ledger.
- Chain-key minters (they move REAL assets on other chains): ckBTC,
  ckETH/ckERC20, ckDOGE.
- All 14 mainnet chain-key token ledgers.
- Five prominent SNS governance canisters (OpenChat, Gold DAO,
  WaterNeuron, KongSwap, Dragginz) — the same disburse pattern.
- Wallets: the Oisy backend, the Chain Fusion Signer (signs and
  broadcasts Bitcoin/Ethereum transactions), Oisy Trade, the Oisy
  rewards canister, and the wallet frontend.
- Exchanges: ICPSwap (SwapFactory, PasscodeManager, farm and
  staking-pool factories, frontend), KongSwap (backend, treasury
  extension, frontend), Sonic (swap, LBP registry, vesting, plus the
  XTC and WICP DIP-20 ledgers whose legacy transfer names the ICRC
  group deliberately does not match), ICDex/ICLighthouse (router,
  DexAggregator, DAO trader, trading-mining, frontend), and ICPEx
  (router, token-creation service, frontend).

Method-level refusals keep precedence, so the tailored messages
survive (a creation spend still points at the icp CLI; an ICRC hit
still names the standard method); the canister blanket catches
everything else on listed canisters. The CMC stays deliberately
unlisted (its notify_* calls are the documented recovery path), as do
read-only indexes/archives and the exchange-rate canister. The module
docs note the honest limit: exchanges create per-pair pool canisters
dynamically, so the list covers each service's central canisters and
the ICRC method group plus the stated policy cover the rest.

Tests: every entry refuses arbitrary update names with both labels and
the protective policy wording; the list is well-formed (valid, unique
principals, non-empty labels); method-level precedence is pinned. The
server instructions, README tool table, both directory docs, and the
landing page now state the canister-level refusal alongside the method
refusals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Per maintainer review on #163: the service no longer exists (its site
and repository are gone), so its backend, treasury extension, frontend,
and SNS governance come off the list — a dead service does not meet the
list's prominence bar. The list stands at 45 entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Per maintainer review on #163: the submission docs read as if a gap had
been closed at some point, rather than describing a product that simply
does not support financial transactions. The financial sections of both
directory docs now state the posture present-tense only — the
"(resolved in code)" heading, the shipped-mitigations lead with its PR
citations, the "execution paths that once moved funds are removed from
the binary" sentences, the "per maintainer review" attributions, and
the "Status: resolved in code" gap-closure paragraph are gone. The
mcp-review email logistics stay (no reply needed; answer with the
stated posture if one arrives), and the production-gap blocker keeps
its operational fact (deploy a release from current main before
submitting) without dwelling on what the old build serves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Answering review on #163 ("why not list all SNS governance
canisters?"): enumerating them can't stay current — 54 SNS DAOs exist
today (a third of them aborted swaps, some renamed or folded) and new
ones launch by NNS proposal. Their value-moving surface is one
standardized method, so the guard now refuses `manage_neuron` on EVERY
canister, the way it refuses the ICRC transfer/approval names: that
covers each existing SNS DAO's governance, every future one, and the
NNS's, with no list to go stale. The four per-DAO governance entries
come off the canister list (the NNS Governance entry stays — the
network's own governance keeps its whole update surface refused), and
the standardized-methods group is renamed and re-documented to say it
carries both the ICRC token surface and the governance interface.

Also per review: the Chain Fusion Signer label had an accidental run of
spaces (interpolated verbatim into refusals) — collapsed; and the gate
function's doc now describes all three refusal scopes instead of the
stale "both groups" wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Per review on #163. WaterNeuron (liquid staking): the water_neuron
protocol canister (ICP deposits, nICP minting, unstaking withdrawals),
the nICP ledger, and the frontend — ids pinned by the project's own
canister_ids.json and the dashboard registry; the nICP index/archive
(read-only) and the icrc_validator (proposal validator) stay off, as do
the two SNS-registered dapp canisters with no documented role. MULTI/DEX
(exchange): its backend, bridge, and frontend, self-declared by the
app's own /.well-known/ic-app.json manifest (the frontend id also
matches the live x-ic-canister-id header and the known-app registry).
The list stands at 47 entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Review on #163: the guard refuses manage_neuron everywhere, but the
server instructions, README tool row, both directory docs, and the
landing page still enumerated only the ICRC methods, the ledger-scoped
methods, and the canister list — understating the guard and leaving SNS
neuron coverage unexplained. All four surfaces now name the governance
method alongside the ICRC names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
…names

Three review points on the finance-canister disallow list, plus one
risk-reduction change:

- The cycles-minting canister's update surface is refused. Its four
  user-callable methods (notify_top_up, notify_create_canister,
  notify_mint_cycles, create_canister) each complete a funding
  operation: the ICP debit happened earlier, but the call is what
  finishes the flow, which makes it a concrete financial path rather
  than a theoretical one. Their refusal points at the icp CLI, which is
  also how an interrupted mint is recovered — so the recovery path is
  preserved, just not through the connector. The review named the two
  notify_* completions; the other two are the same interface and the
  same class of call, so all four are listed together.

- canister_update_call's description discloses the refusal. Both
  directories require a description to match actual behavior, and
  OpenAI's guidelines say a side effect must never be implicit, so the
  description now carries one factual sentence: financial operations and
  update calls to known financial-service canisters are not supported
  and return an error. The full policy — refused method families and the
  reason — stays in the server-level instructions rather than being
  restated per tool. The test that prohibited the word "financial" in
  that description is replaced by one that pins the disclosure.

- No refusal names a venue any more. Refusal messages, the server
  instructions, the README, the landing page, and the submission doc all
  now say the operation belongs outside the connector, in a trusted
  interface the user controls, instead of naming a wallet. Metadata that
  answered a refused financial operation with a specific transactional
  service would read as a redirect from one such route to another.
  Canister creation and funding keep the icp CLI pointer: that is the
  user's own tooling, and this connector already says that work happens
  there.

- The OpenAI submission's policy section is corrected. It claimed that
  OpenAI's restriction was usefully narrower than Anthropic's; it is
  not — the app guidelines prohibit execution of money transfers, crypto
  transfers, and investment trades outright. The section is rewritten
  around the same non-financial posture the Anthropic submission states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
The server instructions and the 11 served tool descriptions told a model
how to work — "START WITH open_app", "ALWAYS call this FIRST", "NEVER
guess a domain", "web search the official URL", a numbered seven-step
flow across tools, and a routing chain to run before answering. Both
directories read these fields: OpenAI scans server instructions and
prohibits model-readable fields that steer how the model works, and
descriptions that tell Claude how to behave are a rejection trigger.

Every surface now states what it is and how it behaves. The instructions
go from 10005 to 3521 characters and cover the value encoding, what the
two tool families act on, what a derivation origin is and which reads it
gates, the two Internet Identity access levels, how canister values are
stored, the financial-transactions policy, and where building and
deploying happen. Each description says what its tool does, what it
returns, and what it rejects. Behavior that a caller needs stays, stated
as fact rather than instruction: an anonymous OQL read is rejected, a
Candid data query on an OQL canister is rejected, a URL with no Internet
Computer evidence is refused. Argument schemas follow the same rule.

A unit test pins the property across the instructions and all 11
descriptions, so the phrasings that crept in before cannot come back.
The submission docs record the posture against the guidelines each
directory publishes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
…ransfer

The shared refusal template classified every matched method as "a
financial transaction". That is right for icrc1_transfer, but
manage_neuron is one entry point for the whole neuron surface — voting,
following, and dissolve-delay changes go through it alongside
disbursing, splitting, and spawning — so a user who tried to vote was
told their call was a transfer.

The template now states what the method is and lets the policy sentence
that follows say what is not supported, and manage_neuron's label says
the method is refused as a whole rather than per command, because its
surface includes the operations that move staked tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Keeps this stacked branch current with its base (#163), so the metadata
diff shows only its own changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

aterga commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI is red on aa71657, and it is not this PR's failure.

The failing check is CI / test, on one live-network test:

discover::tests::search_finds_ckusdc_and_lookup_identifies_it
lookup: "dashboard request failed: error sending request for url
(https://ic-api.internetcomputer.org/api/v3/canisters/xevnm-gaaaa-aaaar-qafnq-cai)"

The IC dashboard API is down — that URL returns HTTP 524 and then times out on three consecutive direct attempts from here. This PR changes tool descriptions, the server instructions, and argument-schema documentation; it changes no request path. The same test failed at the same time on #164, an unrelated diff, and this branch's own previous head (30850a0) passed CI at 10:56Z, before the outage.

Nothing to port and nothing to weaken: the test asserts against a live service, and relaxing it to survive an outage would cost the assertion its value. The re-run waits for the dashboard; a check-in is scheduled and will report back here only if the result is anything other than green.

For the record, the head also carries a merge of this PR's base branch (#163's c90baaf), so the diff shown here stays limited to the metadata change.


Generated by Claude Code

@aterga
aterga requested a balanced review from Copilot August 28, 2026 13:43
@aterga
aterga marked this pull request as ready for review August 28, 2026 13:44
@aterga
aterga requested a review from a team as a code owner August 28, 2026 13:44
Comment thread crates/imcp2-core/src/tools.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Reframes MCP metadata as factual capability descriptions rather than model directives.

Changes:

  • Rewrites server, tool, and argument descriptions.
  • Adds a regression test for directive wording.
  • Updates directory-submission documentation.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/openai-directory-submission.md Documents OpenAI metadata compliance.
docs/anthropic-directory-submission.md Documents Anthropic metadata posture.
crates/imcp2-core/src/tools.rs Rewrites served metadata and adds testing.
crates/imcp2-core/src/identities.rs Revises resolver argument metadata.
crates/imcp2-core/src/discover.rs Revises discovery argument metadata.
crates/imcp2-core/src/calls.rs Revises call argument metadata.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/openai-directory-submission.md Outdated
Comment thread crates/imcp2-core/src/identities.rs Outdated
Comment thread crates/imcp2-core/src/discover.rs Outdated
Comment thread crates/imcp2-core/src/tools.rs Outdated
Comment thread crates/imcp2-core/src/tools.rs
claude added 10 commits August 28, 2026 13:50
Two review decisions from the PR author and sea-snake.

icp_create_canister and icp_top_up_canister are gone. Both printed icp
CLI steps for operations that spend the user's ICP or cycles, and both
already sat on the unserved protocol router, so no client sees a change
— but the crate no longer carries a funding-shaped tool at all, which is
what the submission docs already claim. Their instruction builders,
argument and output types, and the ICP decimal helpers that existed only
to validate the amounts they echoed go with them; the two tool-surface
counts move from 26/15 to 24/13.

canister_update_call's description loses the financial sentence, per
sea-snake: a policy paragraph inside that description reads as a hint
that the tool is usable for financial transactions, which is the one
thing it must not suggest. The test that required the sentence is
replaced by the stronger property it displaced — NO tool description
carries financial language, checked across the whole served surface,
alongside the venue-naming ban. The policy stays where it belongs, in
the server-wide instructions.

Creating and funding canisters remains refused at the guard: the cycles
ledger's creation spends and the cycles-minting canister's whole update
surface, each pointing at the user's own icp CLI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Resolves two conflicts in tools.rs against this branch's metadata work:

- canister_update_call keeps this branch's factual description, minus
  the financial sentence #163 has now removed from every description.
- The two instructions-only tool tests go with the tools they covered;
  this branch's model_readable_metadata_states_capabilities_not_directives
  test stays, and now runs over the 11-tool served surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
…closes

Two review points from sea-snake: the submission docs and the README
described the finance-canister rule as covering "a curated list" and then
enumerated the categories, which invites the reading that some services
are outside it. They now say the guard refuses every update call on the
financial-service canisters it carries, with no hedge.

The same passages still claimed canister_update_call's description
discloses the financial refusal, which stopped being true when that
sentence came out. They now state the actual posture: the policy is in
the server-level instructions — the field the directories scan — and
deliberately in no tool description, held there by a test across the
served surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
…rion

The SERVER_INSTRUCTIONS doc comment still described the design that was
just reversed: it said canister_update_call's description carries a
one-sentence disclosure. It now says the policy is server-wide and in no
tool description, and names the test that holds that.

KNOWN_APPS says what it is and why its contents are safe: name
resolution is a read convenience — the guard refuses every update call to
all three apps' canisters, so an entry routes to reads, not to a
transaction. NNS came out at the author's request, which was a choice
about which names the server advertises rather than a safety criterion,
and its derivation origins stay so reads still resolve to the right
identity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
The directives test read tool descriptions and the server instructions
but not the schemas — and a directive in an argument or reply field's doc
comment reaches the model exactly like one in a description. Review found
the hole and a live violation through it: OpenAppOutput's
alternative_origins said "never infer". The scan now covers every served
tool's input and output schema, that field states the inverse relation as
a fact instead, and a guard asserts the scan still sees field
documentation so the new coverage cannot go vacuous.

Three descriptions claimed things the server has no way to observe or
enforce:

- open_app and resolve_app said a domain "derived from an app's name" is
  not resolvable. The server cannot know how a caller arrived at a URL;
  what it actually does is refuse an unknown bare name, and refuse an
  assumed origin that shows no Internet-Computer evidence. They now say
  that.
- discover_app_canisters promised every canister id found, while results
  are bounded at 50 (20 for unlabelled bundle literals) with the dropped
  count reported in `omitted`. The description now states the bound and
  the reporting.

The OpenAI submission doc's descriptions-match row loses a garbled
possessive and the claim that canister_update_call's description names
its financial refusal, which is no longer the design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
…tale claims

The comment above the gate in canister_update_call still described a
method-only guard, which makes the call look narrower than it is. It now
names all three scopes: the standardized value-moving names (the ICRC
surface plus manage_neuron) on every canister, the system ledgers' and
cycles-minting canister's own methods on those canisters, and every
update method on a listed financial-service canister.

The compliance module doc still said the policy is disclosed in
canister_update_call's description. It now says the opposite, with the
reason.

The OpenAI submission's descriptions-match bullet claimed every
description says what it rejects and then said the financial rejection is
absent from them — a contradiction a reviewer would catch. It now
distinguishes the two: descriptions state the inputs each tool rejects,
and the financial policy is a server-wide statement made once, where the
scan reads it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
One conflict, in SERVER_INSTRUCTIONS: this branch's factual instruction
text is kept, and its doc comment takes #163's correction — the policy is
stated server-wide and in no tool description, held there by a test that
now also covers the schemas.

The instruction text itself loses the same hedge sea-snake asked to
remove from the docs: it said "a curated list of known financial-service
canisters", which invites the reading that some services are outside the
list. It now says the financial-service canisters the guard carries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Both directory documents explained why the financial policy is kept out
of tool descriptions. A reviewer does not need the deliberation, and
spelling it out invites the reading it exists to prevent — that the tool
might otherwise be usable for financial transactions. They now state the
posture: the policy is in the server-level instructions, covering the
whole surface at once, held there by a test.

The same rationale stays in the code comments (tools.rs, compliance.rs),
where it is the reason the test exists and the thing that stops the
sentence being added back a third time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
The comment justifying KNOWN_APPS said every update call to those apps'
canisters is refused. That is the overclaim this PR keeps removing
elsewhere: the list covers each service's central canisters, and
compliance.rs says in its own scope notes that a static list cannot cover
an exchange's dynamically created pool canisters.

What is true, and what it says now: name resolution writes nothing, so an
entry is not itself a route to a transaction; a later update call is a
separate request and goes through the guard under exactly the scope that
module documents, including its note on what the list cannot cover.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
…ster-disallow' into claude/icp-marketplace-compliance-vpawel-10-simplify-metadata

@aterga aterga left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on three points:

  1. P1 — Restore the no-fabricated-domain safeguard.
    In [OpenAppArgs](https://github.com/dfinity/imcp2/blob/6c50d6a1219078c97cd64b3f13f0466dbc97fe99/crates/imcp2-core/src/discover.rs#L1827-L1837), add:

    If the user supplied only an app name, pass that name unchanged. Only pass a URL supplied by the user or obtained from a verified official source; do not construct a domain from the name.

    Add the same constraint to open_app’s description and a regression assertion. The runtime check proves that a domain uses ICP, not that an ICP-hosted lookalike belongs to the intended app. OpenAI specifically says required identifiers must not depend on model guessing. [OpenAI tool guidance](https://developers.openai.com/plugins/plan/tools)

  2. P1 — Replace the blanket “no directives” test with a narrower marketplace-policy test.
    [model_readable_metadata_states_capabilities_not_directives](https://github.com/dfinity/imcp2/blob/6c50d6a1219078c97cd64b3f13f0466dbc97fe99/crates/imcp2-core/src/tools.rs#L2501-L2569) prohibits legitimate invocation and safety guidance. Both marketplaces expressly expect descriptions to explain when tools apply. [OpenAI](https://developers.openai.com/plugins/plan/tools), [Anthropic](https://claude.com/docs/connectors/building/review-criteria)

    Change it to reject only:

    • Unrelated behavioral instructions.
    • Overly broad triggering.
    • Preference over or interference with other plugins/tools.
    • Calls to unrelated external software.
    • Hidden or obfuscated instructions.

    Preserve tool-local prerequisites, selection criteria, and safety constraints. Update both submission documents accordingly; “must not manipulate how the model works” overstates OpenAI’s actual rule.

  3. P2 — Correct the identity-mismatch diagnosis.
    The claims in [get_app_principal](https://github.com/dfinity/imcp2/blob/6c50d6a1219078c97cd64b3f13f0466dbc97fe99/crates/imcp2-core/src/tools.rs#L908-L910) and [list_app_accounts](https://github.com/dfinity/imcp2/blob/6c50d6a1219078c97cd64b3f13f0466dbc97fe99/crates/imcp2-core/src/tools.rs#L964-L966) incorrectly say a browser mismatch means the derivation origin is wrong.

    Replace with:

    A difference from the browser may indicate a different derivation origin, selected account, or Internet Identity; compare those inputs before retrying.

claude added 2 commits August 31, 2026 13:35
Review: the shared CLI redirect is also used for `notify_mint_cycles`, whose
operation mints cycles into a cycles-ledger account rather than creating or
funding a canister, so "Recommend that the user creates and funds canisters
themselves with the icp CLI" gave that caller the wrong next step.

Verified: `notify_mint_cycles` is in `CLI_REDIRECT_METHODS`, and that
constant's own doc comment already said an interrupted mint is recovered in
the CLI too — the intent was there, the redirect text just did not say it.
It now covers all three operations, and the doc comment records why the
wording is deliberately broader than canisters.

The refusal still points only at the user's own terminal, names no connector
tool, and keeps the install pointer the test asserts.

Workspace green (52 + 10 + 10 + 146 + 21 + 1), clippy at the 10-warning
baseline.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Review found the name-based-detection rule broken again at the places the
last pass did not reach — including the note a caller actually receives.
`has_oql` reports only that the interface declares `schema` and `execute`;
neither data behaviour nor authorization follows from two method names.

- The served data-access note said "the canister(s) flagged [oql] hold this
  app's data, gated by the CALLER's principal". This one was not flagged in
  review — a sweep for the same phrasing found it — and it is the worst
  instance, being text the caller reads. It now states the READ PATH, which
  is this server's own behaviour: those canisters are read through the OQL
  tools rather than a Candid data query, that path requires the origin, and
  the flag reports the declaration, not what the canister stores.
- `DiscoveredCanister.oql` closed with "this is a caller-gated data backend",
  two lines below the correction that made the rest of the field accurate.
- `open_app`'s description said a flagged canister "holds the app's data,
  gated by the caller's principal".
- `DiscoveredCanister.api_doc_available` still collapsed the two outcomes the
  adjacent `oql` field now separates: only a failed FETCH gives null, while a
  fetched-but-unparsable interface gives false.
- `ApiDocOutput`'s root doc, `expected`, and `retry` all treated "no
  compatible method detected" as proof of absence. `api_doc_method` returns
  None for an interface that cannot be parsed or exceeds the parser's limits,
  and that path sets `expected: true` — so true does not mean the interface
  read fine, and false on `retry` means the reading will not change, not that
  the canister declares nothing.
- The README row and two internal comments carried the same claim.

Also merges the base branch's cycles-minting redirect fix.

Workspace green (52 + 10 + 10 + 149 + 21 + 1), clippy at the 10-warning
baseline.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 31, 2026 13:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Review: `discover_app_canisters`'s description promised that a domain with
no Internet-Computer evidence yields an empty `canisters` list, but the
handler returns a plain error when discovery itself fails —
`Err(e) => Ok(err(app_url_error_with_guidance(...)))` — so an unreachable
domain (DNS, TLS, timeout) never reaches the empty-list path.

The promise is now conditioned on discovery succeeding, and says outright
that an empty list means no findings rather than a failed lookup, so a
caller cannot read one as the other. The argument schema already said
"reachable"; the tool description now agrees with it.

Workspace green (52 + 10 + 10 + 149 + 21 + 1), clippy at the 10-warning
baseline.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 31, 2026 13:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

docs/anthropic-directory-submission.md:368

  • canister_update_call cannot perform lifecycle management calls as claimed here. Its raw update path does not set an effective canister ID (calls.rs:1522-1532), while management-canister lifecycle calls explicitly require the target via with_effective_canister_id (management.rs:324-328). Remove this parenthetical so reviewers are not told to expect an unsupported capability.
>    connector, with the icp CLI. (The generic `canister_update_call` can
>    still reach management methods on a canister your principal controls.)

Comment thread crates/imcp2-core/src/tools.rs Outdated
Comment thread docs/anthropic-directory-submission.md Outdated
claude added 3 commits August 31, 2026 13:54
main gained #165, which drops the landing site in favour of
internetcomputer.org/icp-mcp and redirects the old paths. Two conflicts:

- `src/assets/index.html` (modify/delete): accepted the deletion. This
  branch had edited that page's two "refused by design" strings, replacing
  "use a wallet you control, like oisy.com" with "use a trusted interface
  you control, outside the connector" — the no-named-venue rule review
  asked for. That file is gone, so the edit has no home here; the page is
  maintained in dfinity/internetcomputer-org and the change belongs there.
- The Anthropic checklist: took main's privacy-policy line, which names the
  page's new single home, and kept this branch's financial-acknowledgment
  line. main's phrasing ("question resolved in code") is the past-gap
  framing review asked to remove — the posture is stated present-tense, not
  as a gap we closed.

The move also falsified a claim in both submission documents: they said the
README, the landing page, and the server instructions all state that
financial transactions are not supported. The README does (its opening
"Not for financial operations" section) and the instructions do, but the
landing page is no longer in this repository, and the page committed in
the other one carries no policy text at all. Both documents now claim only
the two surfaces this repository controls, and say where the third lives
and that adding the posture there is a separate change.

Workspace green (52 + 7 + 10 + 146 + 21 + 1), clippy at the 10-warning
baseline.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Review caught an inaccuracy I introduced two commits ago. Fixing the
CLI-only claim, I wrote that the generic `canister_update_call` can still
reach the management canister's lifecycle methods for a controller
principal. That is wrong, and the repository says so in its own words.

`raw_call` issues `agent.update(&canister, method)` with no
`with_effective_canister_id`, so an ingress call to `aaaaa-aa` carries
`aaaaa-aa` as its effective canister id. `management.rs`'s `mgmt_call`
sets `.with_effective_canister_id(target)` explicitly, and its doc comment
states why: "the boundary node requires this for lifecycle methods". So
those calls are rejected before they reach a replica; the update-call path
is not a route to canister management.

All three sites that carried my claim now say that, with the mechanism, so
a reviewer can check it rather than take it on trust: the Anthropic posture
bullet, its reviewer step 4, and the OpenAI posture bullet.

Worth recording what this cost: the original text said canister management
is done by the user with the icp CLI, review called that an overclaim, and
I widened it on a premise I had not verified against the agent call path.
The claim is now no wider than the mechanism behind it.

Workspace green (52 + 7 + 10 + 146 + 21 + 1), clippy at the 10-warning
baseline; documentation-only change.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Review: the served instructions and the Anthropic reviewer procedure both
said a call with no derivation origin "runs as the anonymous principal, and
the canister decides whether to accept it". Verified against the handler:
`canister_update_call` parses the canister id and then calls
`compliance::disallowed_update_method` immediately — before `resolve_did`,
before any identity resolution, before any network I/O. So for a refused
method the SERVER decides, with or without an origin, and the canister
never sees the call.

Both texts now say the Internet Identity access levels do not govern such a
call, that this server's own checks still do and the financial guard runs
before any identity or network work, and that past those the canister
decides. Three deciders, in the order they actually apply.

Also merges the base branch: main's removal of the landing site, the two
submission documents no longer claiming the landing page states the policy,
and the correction that the update-call path cannot reach
management-canister lifecycle methods.

Workspace green (52 + 7 + 10 + 149 + 21 + 1), clippy at the 10-warning
baseline.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 31, 2026 13:57
@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown

Answering the suppressed finding from the latest review, which has no thread of its own — and it was the most valuable one in the round, because it caught an error I introduced myself.

canister_update_call cannot perform lifecycle management calls as claimed here. Its raw update path does not set an effective canister ID (calls.rs:1522-1532), while management-canister lifecycle calls explicitly require the target via with_effective_canister_id (management.rs:324-328).

Verified and correct. raw_call issues agent.update(&canister, method) with no with_effective_canister_id, so an ingress call to aaaaa-aa carries aaaaa-aa as its effective canister id, while management.rs's mgmt_call sets .with_effective_canister_id(target) — and that function's own doc comment says why: "the boundary node requires this for lifecycle methods". Those calls are rejected before a replica sees them.

How it got there is worth stating plainly. Two rounds ago review flagged "creating, funding, and managing canisters is done by the user with the icp CLI" as an overclaim, because the generic update-call tool restricts nothing about the target canister. I checked the guard, saw no restriction on aaaaa-aa, and widened the text to say management lifecycle methods stay reachable — without checking the agent call path, where the actual block lives. So I replaced one inaccuracy with another, in a compliance document, while fixing inaccuracies.

Fixed on the base branch in 5363586 and merged here in 83a53e8. All three sites that carried my claim — the Anthropic posture bullet, its reviewer step 4, and the OpenAI posture bullet — now state that the update-call path is not a route to canister management, and give the mechanism (the effective canister id it does not set) so a reviewer can check it rather than take it on trust.


Generated by Claude Code

sea-snake
sea-snake previously approved these changes Aug 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Suppressed comments (3)

crates/imcp2-core/src/tools.rs:1987

  • “Per-app data is gated by the calling principal” is broader than the implementation and conflicts with the new DiscoveredCanister documentation, which correctly says method names establish neither storage nor canister-side gating. Only this connector's OQL paths unconditionally require a delegated principal; Candid method reads may remain anonymous. Attribute the gate to the OQL tool path rather than to all per-app data.
    An app's derivation origin is the exact origin Internet Identity derives the user's principal from. It is not necessarily the app's visible URL, and an alternative-origins entry does not identify it; open_app and resolve_app resolve it, and the identity-bearing tools take the origin itself rather than a URL. There is no on-chain name-to-URL directory: open_app matches a name against a built-in registry of well-known apps, and where the derivation origin would have to be assumed from the URL itself, open_app and resolve_app refuse an origin with no evidence of being an Internet Computer app, while discover_app_canisters returns an empty result for such a domain. Per-app data is gated by the calling principal, so OQL reads require a derivation origin and reject an anonymous read. Account delegations are short-lived and derived on demand from this connection's standing Internet Identity credential, which is obtained at connect time and lasts for the chosen session duration (up to 30 days). Internet Identity's consent screen offers two access levels, and they govern the calls signed with that session's account delegation — the ones that carry a derivation origin: on a \"Questions only\" session those reads work and those update calls are rejected by the network, while \"Actions & questions\" permits both. A call made with no derivation origin is not signed with the delegation at all; it runs as the anonymous principal, so those access levels do not govern it. This server's own checks still do — the financial-transactions guard runs before any identity or network work, so a call it refuses is refused whether or not an origin was passed — and past that the canister decides whether to accept it.\n\n\

crates/imcp2-core/src/tools.rs:595

  • This repeats the unsupported claim that per-app data itself is principal-gated. The implementation only establishes that this connector rejects anonymous OQL requests; the canister may expose public data, and the same tool allows anonymous Candid reads. Describe the enforced OQL prerequisite without inferring the canister's storage or authorization behavior from the schema/execute names.
        description = "Read from an Internet Computer canister with either a Candid `query` method or an OQL query — exactly one of the two. `method` is a query function from the canister's Candid interface, invoked with textual-Candid `args`; `oql` is an OQL query as a JSON object string, run against the canister's `execute` method as plain JSON with no Candid escaping. Canisters that expose OQL (get_canister_candid reports `oql: true`) reject a Candid `method` data query and are read through `oql`; a plain query canister such as a ledger takes `method`. The `oql` path requires `derivation_origin` — per-app data is gated by the caller's principal, and an anonymous OQL read is rejected — and returns `columns` and `rows` (a markdown table) with `has_more` for paging; on an empty result it re-reads the schema for this principal and, when that read returns entities and the query's `start` is not one of them, returns `valid_entities` plus a did-you-mean repair; a `start` that does exist, an empty schema, or a schema read that fails leave both out. The `method` path may be anonymous, or take `derivation_origin` and `account` to read as the user's account, and returns the decoded reply in textual Candid. Reading as the user's account — the whole `oql` path, and the `method` path when given a `derivation_origin` — requires an authenticated session; an anonymous `method` read does not. `derivation_origin` is resolved by open_app or resolve_app rather than being a raw URL, and the OQL entity and field names come from get_canister_oql_schema. State changes go through canister_update_call.",

crates/imcp2-core/src/tools.rs:248

  • This also attributes the authentication requirement to the schema/canister, although the code only proves that this connector rejects a missing origin before making any call (tools.rs:268-274). Keep the prerequisite, but state it as connector behavior so the metadata does not infer canister-side gating from the OQL method names.
        description = "Fetch the OQL schema catalogue of a canister that exposes the OQL surface (get_canister_candid reports `oql: true`): its entities, their primary keys, fields, and edges, as JSON, plus a ready-to-run canister_query example per entity (each preserving this call's identity). Entity names are the schema's own, often plural and different from the Candid types and methods (e.g. `bookings`, not `Booking`/`getBookings`). `derivation_origin` is required: the schema is gated by the caller's principal, so a read with no origin is rejected with guidance rather than returning an empty entity list. Since every read here is made as the user's app account, it requires an authenticated session. The origin is the app's canonical Internet Identity derivation origin, which open_app and resolve_app resolve; the reply echoes `derived_for_origin` and `acted_as_principal`.",

Comment thread README.md Outdated
Comment thread crates/imcp2-core/src/tools.rs Outdated
Comment thread docs/openai-directory-submission.md Outdated
Comment thread crates/imcp2-core/src/calls.rs Outdated
Base automatically changed from claude/icp-marketplace-compliance-vpawel-7-finance-canister-disallow to main August 31, 2026 14:04
@aterga
aterga dismissed sea-snake’s stale review August 31, 2026 14:04

The merge-base changed after approval.

…nisters

#163 was squash-merged as 2279f91, so its commits are not ancestors of main
and this stacked branch went un-mergeable against its new base. Merged main
in rather than rewriting history. Five conflicts, all this branch's own
rewrites against the pre-merge text of the same lines, resolved to this
branch: `discover_app_canisters`'s description, SERVER_INSTRUCTIONS and its
doc comment, the model-readable-metadata policy test (absent on the other
side), the metadata paragraph in the Anthropic document, and its reviewer
step 3.

Review then found the same defect at seven sites, and it is one this branch
created: the corrected `DiscoveredCanister` docs say method names establish
neither what a canister stores nor how it authorizes callers, while other
surfaces still asserted "per-app data is gated by the caller's principal" as
fact. What is actually enforced is narrower and entirely ours: this server
rejects an OQL read with no derivation origin. A canister may serve public
data, and a Candid `method` read may be anonymous.

Reattributed to the connector, with that distinction stated: the server
instructions, `icp_oql_guide`'s served text, `canister_query`'s and
`get_canister_oql_schema`'s descriptions, `OqlSchemaArgs.derivation_origin`,
`CanisterQueryOutput.is_anonymous`, and both README passages. Three
implementation comments keep the old shorthand deliberately — they are
neither served nor public, so they are notes, not metadata.

Two further findings, both real:

- The declared-origin path is NOT uniformly fail-soft, and the exception is
  security-relevant. `decide_declared_origin` returns Err — refusing
  resolution outright — for a CROSS-origin declaration the declared origin
  does not authorize in its /.well-known/ii-alternative-origins, with
  "Refusing to derive an identity here rather than use a wrong one". My
  earlier wording lumped that in with the fetch failures that fall back to
  the assumed origin, so a caller could have expected an IC-evidence
  fallback where resolution actually fails. `open_app`'s and `resolve_app`'s
  descriptions and the OpenAI readiness row now name it as a hard refusal,
  so a declaration cannot read as a way around the identity checks.
- The README's `get_canister_api_doc` row still promised a structured result
  "in every case" after the description and output schema had been
  corrected: an unusable canister id is a plain error, and `expected: true`
  is not proof of absence.

This push resets the approval on this PR. That is the cost of the seven
metadata corrections plus a base branch that had to be merged anyway — the
merge alone would have reset it.

Workspace green (52 + 7 + 10 + 149 + 21 + 1), clippy at the 10-warning
baseline.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 31, 2026 14:09
@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown

Answering the three suppressed findings from the latest review, which have no threads of their own. All three were right, and they name the same defect this branch created.

The corrected DiscoveredCanister docs now say that two method names establish neither what a canister stores nor how it authorizes callers — while SERVER_INSTRUCTIONS, canister_query's description and get_canister_oql_schema's description still asserted "per-app data is gated by the caller's principal" as fact. So the PR contradicted itself, and a directory reviewer comparing the two would have seen it.

What is actually enforced is narrower and entirely ours: this server rejects an OQL read with no derivation origin. A canister may serve public data, and a Candid method read may be anonymous — as the same tool's description already says.

Fixed in 2ba5e93 at all seven sites, each now attributing the rule to the connector: the server instructions, icp_oql_guide's served text, canister_query's and get_canister_oql_schema's descriptions, OqlSchemaArgs.derivation_origin, CanisterQueryOutput.is_anonymous, and both README passages. Three implementation comments (tools.rs:474, tools.rs:713, calls.rs:915, plus discover.rs:114) keep the shorthand on purpose: they are neither served to a model nor published, so they are notes about the code, not claims in metadata. Say so if you'd rather they matched.

On the approval. @sea-snake, this push resets your approval, and I want to be straight about the trade. The base branch was squash-merged, so this PR had to merge main regardless and the reset was already unavoidable; the seven corrections rode along at no extra cost rather than waiting for a second reset. Workspace green (52 + 7 + 10 + 149 + 21 + 1), clippy at the 10-warning baseline, and the PR is mergeable against main again.


Generated by Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-core/src/tools.rs
@sea-snake
sea-snake merged commit f596c65 into main Aug 31, 2026
13 checks passed
aterga pushed a commit that referenced this pull request Aug 31, 2026
#167 rewrote the served metadata to describe the surface rather than direct
the model, and added a test that scans the instructions, all 11 descriptions
and every argument/reply schema for the manipulations the directories
prohibit. This branch's metadata was written in the old register, so the
resolution is main's text plus this branch's facts restated in the new one:

- SERVER_INSTRUCTIONS: main's rewrite, with the write gate as a descriptive
  paragraph. The old "WRITES GO ONLY TO APPS THAT OPTED IN" block told the
  model what to do when refused ("do NOT keep retrying", "tell the user");
  the replacement states what the gate is, that reads are not gated that way,
  and who can change it. The refusal texts still carry the actionable detail
  — they are tool results, not metadata.
- canister_update_call: main's description plus the gate, `app_url`, and the
  `declared_by`/`declared_at` echo, as statements.
- calls.rs `app_url`: same, in the schema doc ("Omitted, `derivation_origin`
  is used as that origin instead" rather than "omit it only when…").
- resolve_app: main's description with the declared source corrected to
  /.well-known/ii-derivation-origin, which this branch made take precedence
  over the legacy field.
- discover_app_canisters + the README row: main's text with the ai-connect
  provenance replaced by the two manifest documents, per review.
- open_app: main's description and reply text, noting that `app_url` is the
  origin the write gate reads the manifest from.

Two of this branch's own assertions pinned the old wording (READABLE, "WRITES
GO ONLY…"); they now pin the same facts in the new text.

Build and the full suite pass on the merge: 259 tests, 0 failures, including
main's new metadata scan over the text added here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JarhapgjytMcVSCW7d8sqB
aterga pushed a commit that referenced this pull request Aug 31, 2026
Resolves the conflicts #163's follow-ups (#167) and the landing-site move
(#165) created against the write gate:

- compliance.rs: takes main's four-group guard wholesale (the CMC's
  funding-completion methods are listed there now, so the branch's "deliberately
  NOT listed" note went away) and re-applies only the Layer-2 framing plus the
  note that Layer 1, not this list, bounds the un-enumerable remainder.
- tools.rs: the gate is now described in #167's factual register — no ordering
  rules, no imperatives — on canister_update_call, resolve_app,
  discover_app_canisters and in SERVER_INSTRUCTIONS, which keeps the surface
  inside the marketplace-policy scan. resolve_app's "declared" source and
  discovery's app-declared sources are corrected to ii-derivation-origin and
  ic-architecture.
- The Developer Terms page follows every other human-facing page to
  dfinity/internetcomputer-org: its source text lives in
  docs/icp-mcp-developer-terms-draft.md (the privacy policy's convention),
  DEVELOPER_TERMS_URL points at internetcomputer.org/icp-mcp/developer-terms/,
  and this origin redirects /developer-terms there. The anti-drift test moves
  with it: the source text must name the revision the gate enforces, and the
  URL refusals carry must be the page this origin redirects to.
- Refusal-text assertions follow main's guard, which no longer names a venue.

cargo test --locked --workspace --all-targets: 263 tests green; clippy
unchanged at the 6-warning pre-existing baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SzGAjX3k9utczAg2Jw8yQX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants