diff --git a/.github/CONVENTIONS.md b/.github/CONVENTIONS.md
index 89bd522..20c57b0 100644
--- a/.github/CONVENTIONS.md
+++ b/.github/CONVENTIONS.md
@@ -337,3 +337,53 @@ counting nothing meaningful. The endpoint had been rewritten between two reading
The same applies to base branches: check whether the PR you are stacking on has merged before
branching from `master`, or you will silently revert it.
+
+---
+
+## 8. Reading and the check each catch what the other cannot. Neither is sufficient
+
+Bought twice in one session, 2026-08-09, on the same claim.
+
+**Reading found what the check could not see.** `check-shared-copy.mjs` matched its `claimPatterns`
+with `indexOf` — case-sensitive literals. The heading "No call back to us" on `index.html` sat
+outside any tag and the check stayed green, because the pattern was written lower-case. A literal
+list also cannot spell one claim two ways, and this site spells it at least five: *call back to us*,
+*callback to Observer*, *network call to Observer*, *neither our permission nor our uptime*, *no
+runtime dependency on OP*. Each variant shares no wording with the others. Four assertions were
+invisible to the enumeration whose entire job is to know where the claim lives — on three pages it
+already listed. Only reading the pages found them.
+
+**The check found what reading missed.** After widening the matcher for the fourth variant, it
+immediately reported two more instances — one of them on a line of `integrate/index.html` that had
+been read end to end minutes earlier. The other was on a page nobody had opened.
+
+**So:** do not treat a passing check as coverage, and do not treat having read the page as
+enumeration. A page is covered when a human has read it in a reader's order *and* every claim it
+makes is inside a mechanism that will notice the next one. Convert what you find by reading into a
+pattern **before** you finish the page, not after — the widened matcher is what caught the instance
+the reading had just walked past.
+
+Two corollaries, both bought the same way:
+
+- **Put your own new prose inside the matcher.** A first fix in this session passed its base case for
+ the wrong reason: the replacement wording tripped no pattern at all, so the control could not see
+ the sentence just written to fix the claim. The wording you write to correct a claim is a new
+ instance of that claim.
+- **A sweep that found three variants is not evidence there was no fourth.** Keep widening from the
+ site's own examples. Treat the pattern list as permanently incomplete.
+
+## 9. Confirm a state by fetching the thing, never by reading a write's response
+
+`app.agenticterminal.io` is behind a site-level Netlify password. The Netlify API **echoes
+`password: false` back even on a successful write**, so a caller who confirms the lock by reading the
+response it got is reading its own request back, not the state.
+
+The general form: **a write's response is a claim about the request, not a measurement of the
+result.** Where the state is externally observable, observe it — fetch the site, resolve the DID,
+query the row. This is the same shape as [§7](#7-re-measure-immediately-before-writing-a-correction),
+one layer down: re-measuring is useless if the instrument is the thing that just wrote.
+
+Also true of a covered/uncovered list. On 2026-08-09 the audit handover's two lists held 33 pages and
+`find . -name '*.html'` returned 38 — five pages on neither list, one of them edited by the branch
+under review. **Derive the population with a command and diff it against the list.** A partition that
+does not cover its domain fails by omission, so nothing ever looks wrong.
diff --git a/integrate/index.html b/integrate/index.html
index cfba537..db04581 100644
--- a/integrate/index.html
+++ b/integrate/index.html
@@ -206,15 +206,25 @@
Real on-chain proof
Verified x402 Payment - Base Mainnet
- TX:
0xd94a6d8bfa9c1634...b39b94
+ TX:
0xd94a6d8bfa9c1634...b39b94 · Base mainnet block 45319014
Agent: did:web:observerprotocol.org:agents:d13cdfceaa8f895afe56dc902179d279
- Counterparty: did:web:hyperbolic.xyz
+ Counterparty: did:web:hyperbolic.xyz —
this DID no longer resolves
Amount: 0.10 USDC
Rail: x402 (USDC on Base)
On-chain verified: true
+
+ The counterparty DID is recorded as it stood at the time of the payment, and it no longer
+ resolves: hyperbolic.xyz redirects to hyperbolic.ai, and
+ /.well-known/did.json returns 404 there. Checked 9 August 2026. The
+ settlement is unaffected and still checkable by anyone — it is on Base mainnet at
+ block 45319014, and the amount, asset and recipient are in the transaction, not in the DID. But a
+ did:web is only as durable as the domain serving it, and a page about independent
+ verification is the wrong place to leave that unsaid.
+
+
X402PaymentCredential issued
diff --git a/ows/index.html b/ows/index.html
index 033682c..9da428c 100644
--- a/ows/index.html
+++ b/ows/index.html
@@ -299,7 +299,13 @@
OWS Quickstart
Install OP SDK
-
Add the Observer Protocol SDK to your project.
+
Add the Observer Protocol SDK to your project.
Note that this
+ package is deprecated. @observer-protocol/sdk was last released in
+ April 2026, is unmaintained, and carries a deprecation notice on the registry itself. It
+ still installs and the steps below still run. For
verifying a credential use
+
@observer-protocol/policy-engine; the register-and-attest flow in steps 3 to 5
+ has no replacement package yet, which is why this page still names the old one. See
+
the SDK page for what replaces which part.
npm install @observer-protocol/sdk
diff --git a/scripts/check-shared-copy.mjs b/scripts/check-shared-copy.mjs
index 52c7253..9910272 100644
--- a/scripts/check-shared-copy.mjs
+++ b/scripts/check-shared-copy.mjs
@@ -129,6 +129,33 @@ for (const [key, entry] of Object.entries(spec)) {
}
}
+// REQUIRES DISCLOSURE. Not a forbidden stem: naming the package is allowed, naming it
+// silently is not. A page may legitimately instruct a deprecated package when nothing
+// replaces the flow — what it may not do is let a reader adopt it without knowing.
+const requires = spec['$requiresDisclosure'] ?? {};
+for (const [concept, rule] of Object.entries(requires)) {
+ const stems = rule.stems ?? [];
+ const musts = (rule.mustAlsoContain ?? []).map((x) => x.toLowerCase());
+ if (stems.length === 0 || musts.length === 0) continue;
+
+ for (const file of allHtml()) {
+ const src = readFileSync(file, 'utf8');
+ const rel = file.slice(root.length + 1);
+ const low = src.toLowerCase();
+ const hit = stems.find((stem) => low.includes(stem.toLowerCase()));
+ if (!hit) continue;
+ const missing = musts.filter((m) => !low.includes(m));
+ if (missing.length === 0) continue;
+ const line = src.slice(0, low.indexOf(hit.toLowerCase())).split('\n').length;
+ failures.push(
+ `${rel}:${line}: names "${hit}" but the page never says ${missing.map((m) => `"${m}"`).join(' or ')}.\n` +
+ ` Concept: ${concept}. Instructing a deprecated package is allowed where nothing\n` +
+ ` replaces the flow. Instructing it silently is not — a reader adopts it without\n` +
+ ` knowing. Add the disclosure to this page, or stop naming the package.`
+ );
+ }
+}
+
// FORBIDDEN STEMS. A concept ruled off the site cannot be policed by searching for
// the phrasing it had when it was ruled off — that is how reputation survived three
// clearances. Forbid the stem; allowlist each surviving occurrence with a reason.
diff --git a/scripts/shared-copy.json b/scripts/shared-copy.json
index 09577f3..b7c08e0 100644
--- a/scripts/shared-copy.json
+++ b/scripts/shared-copy.json
@@ -58,6 +58,20 @@
"claimPatternsAreRegex": "Case-insensitive regexes, matched by check-shared-copy.mjs. Written from the site's own examples rather than from one remembered sentence: the claim is spelled 'call back to us' on index.html and 'callback to Observer' on docs.html, and a literal lower-case list saw neither the capitalised heading nor the Observer variant. Four assertions were invisible to this block on 2026-08-09 for exactly that reason. When you add a home for the claim, add its WORDING here, not just its file.",
"claimPatternNote": "Any occurrence of these in an HTML file MUST sit inside an element tagged data-shared-copy=\"offline-scope\". That is what makes a tenth instance a build failure rather than a discovery. Add the tag, or add the file to mustAppearIn if it is a new home for the claim. Some tagged instances REFER to the claim rather than asserting it (docs.html's resolution-hosting rationale is one). They are tagged anyway: the enumeration's job is to know everywhere the phrase appears, and a human scoping the claim decides per instance."
},
+ "$requiresDisclosure": {
+ "deprecated-sdk": {
+ "why": "A page may name a deprecated package — there is no replacement for the register/attest flows, so instructing it is sometimes correct. What is never correct is instructing it SILENTLY. On 2026-08-09 four pages disclosed the deprecation and four did not, and the four that did not included the integration guide end to end, under the heading 'Ship chargeback prevention today'. This is not a $forbidden stem: the package name is allowed, the omission is not. Verified at the registry, not from our own copy: npm carries the deprecation notice on the package, and PyPI's newest observer-protocol is 0.2.0 from 2026-04-30.",
+ "stems": [
+ "@observer-protocol/sdk",
+ "@observerprotocol/sdk",
+ "pip install observer-protocol"
+ ],
+ "mustAlsoContain": [
+ "deprecat"
+ ],
+ "note": "mustAlsoContain is matched case-insensitively anywhere in the same FILE, not the same element: a page-level notice covers every mention on that page, which is how the four disclosing pages already do it. Stem, not phrase — 'deprecat' catches deprecated/deprecation/deprecates, because the last three vocabulary misses on this site were all a matcher that knew one spelling of its subject."
+ }
+ },
"$forbidden": {
"reputation-model": {
"why": "Reputation was ruled off this site entirely. It was cleared three times and was wrong three times, because each search was shaped like the previous fix: the phrase, then the subject, then the subject as previously written. 'trust scoring' does not contain 'trust score' \u2014 the stem diverges at scor|e versus scor|ing. This forbids the STEM, so a gerund, a bare noun, or a number with a label cannot reappear unnoticed.",