This file is the WHY half of
CONTRACT.md(§0–§6, plus amendment trade-offs and the non-normative notes fromCLAUDE.md).CONTRACT.mdcarries the normative rules (MUST / NEVER / exact text, ids and schemas); the explanatory prose, precedent notes and audit history that used to sit beside them live here, verbatim, under the same section numbers. Split out on 2026-08-18: nothing was summarized, reworded, or dropped, and no rule changed meaning.Reading order:
CONTRACT.md§0–§6 is the required read for a build. This file is optional background, useful when you need to know why a rule is worded the way it is before proposing a change to it.
When bandwidth allows, a proper implementation (translated metadata layer for ES/FR/PT with AR/中文 stubs) is fully specced in ../I18N-SPEC.md (Option B). That spec is the source of truth for any future re-implementation.
This is a held state — the toggles were cosmetic and do no harm.
Pattern unified with sister suite Apex Logics; swept via standardize_mcp_toggle.py.
Typing -- where an em-dash was removed is itself a machine-generated tell: it reads as a CLI flag or as un-typeset draft text, and it is banned for the same reason the em-dash is.
Measured debt at the time the two 2026-08-02 rules were written (prose-visible only, using the gate's own strip logic — <script>/<style>/<pre>/<code>/comments/badges removed): -- in 453 hits across 204 files; entity-encoded em-dashes in 2 211 hits across 686 files. Both are legacy debt and both get a baseline bucket on introduction, on the same ratchet as the em-dash count (a baselined file may carry at most its recorded count; any file absent from the baseline must be clean). The entity figure is the more serious finding: those em-dashes were never counted by any gate, so they are debt the suite did not know it had, not debt it chose to defer.
The failure this section exists to prevent is a page that computes a value, exports it in the artifact, and never shows it to the person reading the screen. The reader then cannot answer questions the artifact can already answer. The measured basis for each rule is stated inline below, so the rule and its evidence stay together.
- Why this clause is here rather than assumed. Every node page today mints
generated_atinsideexportArtifact(), so the field records when the reader clicked export, not when the numbers were computed.art-525-nway-balance-closure-check.htmlis the one page that renders the value, and it callsnew Date()twice independently (:629for display,:688for export), so its two timestamps are from two different moments. Rendering without this clause would propagate that divergence across the estate. - Scope of the change this implies. Moving the capture point changes the artifact's
generated_atvalue for a run exported later than it was computed. It changes no field, no type, no schema outcome, and no hash:execution_hashis taken over{policy_parameters, output_payload}only (chaingraph/kernels/_hash.mjs), andscripts/check-page-determinism.mjsexplicitly holds that a clock value reaching the envelope or the DOM is outside the preimage and is not a determinism defect.
Collapsing did_not_run into "fail", or a review state into "pass", tells the reader something untrue.
A two-state computation renders correctly under this rule with two affordances; nothing here obliges a third.
Several distinct shapes are live across the estate, and the pointer's shape is hash-bearing: it sits inside output_payload, so re-shaping it moves execution_hash and stales the node's proof. This section therefore takes each page's shape as given.
§1.5.2 is a standing rule, not a sweep. The survey behind this section examined 526 kernels and found 50 fields carrying three or more states; every page flagged as omitting one was read by hand, and none was a presentation discard. The two cases that looked closest (art-44 / art-46, and art-174's coverage_band against the page's overall_coverage) are page-versus-kernel divergence, which belongs to the divergence programme.
This check exists because Wave-2 chains once referenced invented slugs (e.g. 53-stablecoin-compliance-checker vs the real 53-cbdc-architecture-comparator), silently 404ing on the live server.
This settles a scope question §2.2 left open by titling itself "Per-Tool"; it introduces no new field and no new artifact class (8 node-only art-* manifests already exist).
Whether to normalise is a separate open question.
A gate is the right eventual mechanism, but only once the backfill below is closed — a gate that is red on the day it lands enforces nothing.
A schema that has drifted from the emission is worse than none — it is a false claim an agent will act on.
- Existing nodes (open debt, owned elsewhere): measured 2026-08-02 across 526 live nodes with an
mcp_name, 21 have a manifest file and 6 declare anoutput_schema— so 505 nodes have no manifest and 520 declare no output shape. That is a large, explicitly acknowledged conformance debt, tracked and closed separately: it does not block any push today, and it does not make those nodes retroactively invalid. Do not restate those figures as current — re-derive them bytool_idagainstchaingraph.jsonbefore quoting.
Real AP2 (see ap2-protocol.org) defines IntentMandate / CartMandate / PaymentMandate for agent-mediated payment flows — a different problem domain.
Adopted for human-readable audit + machine-agent ingestion. execution_hash added as optional audit metadata.
Prevents client-side bloat & enforces deterministic guarantees.
An earlier duplicate file state was resolved; both are live and valid.
(Added 2026-06-11 after a structural JS edit silently deleted live code in dozens of tools — syntax errors are invisible until a user hits them.)
(2026-08-21, CONTRACT-DEADCMD-FIX-1: §6.2 and §2.5 carried five dead npm run/npm install-shaped instructions — the site repo has no package.json at all, so npm run lint:manifests, npm run test:ap2-exports, npm run test:ui-ap2-placement, and two npm run validate:chains variants were unrunnable as written. Replaced with the real zero-dependency invocations — node scripts/check-manifest-schema.mjs, python scripts/verify_repo.py (its Check 3 already covers both the AP2 export-button consistency and placement checks — there was never a separate DOM-placement test), and node scripts/validate-chains.mjs from mcp-apps-poc/. New gate scripts/check-ssot-no-npm.mjs, wired into preflight.mjs, greps CONTRACT.md/CLAUDE.md/AGENTS.md/CONTRIBUTING.md for the three dead-command shapes so the SSOT cannot regress into this state silently again.)
- Inbound external links to removed guide URLs break (accepted — A3.3). The MCP
composer_urlfields are repointed to chain pages. - Two export schemas coexist transitionally: §4 (ChainGraph) and §3.1 Policy Mandate (un-promoted catalog tools). The crosswalk (A3.5) keeps them reconcilable; the long-term direction is §4 only.
- The catalog tool count drops as tools are promoted/retired — update the "counts drift" verification and any hardcoded totals.
- This amendment supersedes the optional-
execution_hashlanguage in §3.1 for ChainGraph artifacts, deprecates architecture #4 in §5.3, and updates the §1.2 disclosure rules to the/chaingraph/surface.
Measured baseline that motivated this clause: 104 of 155 tools citing a regulation carried no reliance hedge of any kind, only 12 carried a class="disclaimer*" div, no estate-wide terms page existed, and this document contained the word "advice" zero times. This amendment fixes the gap going forward; it does not retrofit the 104 already-shipped tools — that retrofit is a separate, larger, future work unit and is explicitly out of scope here.
A 2026-10-17 inventory of every earlier WebMCP audit found no normative definition anywhere: the contract and the standard were both silent, and each audit had measured a different thing (registration present, listed in the directory, executes, agrees across doors) under the same word. Counts from those audits could not be compared, and none could be counted toward a goal. A15 fixes the word before anything is counted against it.
Pages and chains get separate definitions because their evidence differs. A page registers one tool and its execution is already vm-tested by the wrapper and direct gates, so three of its four parts are gated today. A chain registers the generic chain tools on its composer page, and no gate runs any of them: the only gated chain test at the time of writing is region drift. Stating both definitions with the gated or advisory mark on each part keeps the gap visible instead of letting a green generator check read as chain conformance.
Two known holes are stated in the text rather than left to discovery. First, the generator's --check reds a region that drifted but cannot see a composer_url page that carries no region at all, so chain part 1 is gated for drift and advisory for presence. Second, the browser chain pages at the time of writing hash a chain definition artifact (the steps and their handoffs), not an executed composite, so they cannot satisfy chain part 4 as built; the SPEC §21.2 reopening defines the browser composite that part 4 compares, without changing any shipped hash.
The program target is worded as "chains censused", not "chains conformant", because three of the four chain parts have no instrument yet. A census against a written definition is honest today; a conformance claim is not.
<link rel="canonical">uses absolute URLs intentionally — this is correct SEO practice, not a routing violation.- index.html is 5 500+ lines — high truncation risk in any AI context window. Edit surgically with grep + line numbers rather than reading the whole file.
TOOLPAGE-FILE-IMPORT-1 (2026-09-05) — the web-app manifest is not client storage, and share_target text routing is not egress
manifest.webmanifest is a static site descriptor the BROWSER reads (install metadata, icon, start URL); it is not a storage mechanism — nothing is written to the device on its behalf, and the estate still ships no service worker, so no offline cache, no background work, and no storage origin exists. The share_target member declares that when a user (or a local automation) shares plain TEXT into the installed app, the browser hands that text to /start.html?share=… as a normal local navigation — no network request leaves the machine beyond the navigation to the user's own already-loaded origin, which is the same zero-egress surface as typing in the search box by hand. start.html uses ?share= ONLY to prefill the local tool-search routing box and never as tool input, preserving the deep-link contract's fragment-only rule for parameters (TOOLPAGE-DEEPLINK-1). Likewise the node-page file-import reader (drag-drop / file picker) keeps file bytes in memory only: no storage API is written, no upload occurs, and the sole accepted doorway is the same in-page validation and prefill path the fragment deep link uses.