953 lines
32 KiB
TypeScript
953 lines
32 KiB
TypeScript
#!/usr/bin/env tsx
|
|
/**
|
|
* wiring-completeness-smoke — the "claim ⇒ wired" enforcer.
|
|
*
|
|
* Standing rule (Ken, recurring): every claim made in
|
|
* MORPHIT-BRAG-LIST.md and apps/web/src/lib/i18n/locales/en.json's
|
|
* FAQ MUST be verifiable in code or honestly disclosed as backlog.
|
|
* "Push notifications work" is a claim; if the code has no
|
|
* pushManager.subscribe() anywhere, that claim violates the rule.
|
|
*
|
|
* Past pattern: claims drift ahead of implementation. Design docs
|
|
* announce a feature, FAQs describe it in present tense, code never
|
|
* catches up. The discipline survived inspection in some checkpoints
|
|
* and broke in others; without mechanical enforcement, drift wins.
|
|
*
|
|
* This smoke is the mechanical enforcer. For each substantive
|
|
* feature claim, we record a CHECK: a phrase from the brag list / FAQ
|
|
* and the code-anchor pattern that must exist for the claim to be
|
|
* honest. CI fails the build when a claim is on the surface but the
|
|
* anchor is missing.
|
|
*
|
|
* Adding to the registry: when shipping a new claim, append a CHECK
|
|
* row. When intentionally deferring a feature, mark it `deferred`
|
|
* with a one-line rationale — the smoke skips deferred rows but
|
|
* REPORTS them, so Ken sees the deferral list every triple-pulse.
|
|
*/
|
|
|
|
import { readFileSync, existsSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { execSync } from 'node:child_process';
|
|
|
|
const REPO = join(import.meta.dirname, '..', '..', '..');
|
|
|
|
interface Check {
|
|
/** Short id used in failure messages */
|
|
readonly id: string;
|
|
/** Where the public-facing claim lives */
|
|
readonly claim_source: 'brag_list' | 'faq';
|
|
/** Substring that, if present in the source, triggers this check.
|
|
* Match is case-insensitive and substring-based. */
|
|
readonly claim_phrase: string;
|
|
/** What code anchor must exist for the claim to be honest. */
|
|
readonly anchor: AnchorSpec;
|
|
/** If `deferred`, the claim is honestly disclosed as backlog and
|
|
* the smoke reports but does not fail on it. */
|
|
readonly status: 'live' | 'deferred';
|
|
/** Reason — required for `deferred` rows. */
|
|
readonly reason?: string;
|
|
}
|
|
|
|
type AnchorSpec =
|
|
| { kind: 'grep'; pattern: string; paths: readonly string[] }
|
|
| { kind: 'file_exists'; path: string }
|
|
| { kind: 'any_of'; specs: readonly AnchorSpec[] };
|
|
|
|
const CHECKS: readonly Check[] = [
|
|
// ─── Notifications system ─────────────────────────────────────
|
|
{
|
|
id: 'notifications-ambient-channel',
|
|
claim_source: 'faq',
|
|
claim_phrase: 'favicon',
|
|
anchor: { kind: 'file_exists', path: 'apps/web/src/lib/notifications/ambient.ts' },
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'notifications-os-native',
|
|
claim_source: 'faq',
|
|
claim_phrase: 'Notification API',
|
|
anchor: { kind: 'file_exists', path: 'apps/web/src/lib/notifications/native.ts' },
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'notifications-audio-cue',
|
|
claim_source: 'faq',
|
|
claim_phrase: 'two-tone chime',
|
|
anchor: { kind: 'file_exists', path: 'apps/web/src/lib/notifications/audio.ts' },
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'notifications-vibrate-cue',
|
|
claim_source: 'faq',
|
|
claim_phrase: 'navigator.vibrate',
|
|
anchor: { kind: 'file_exists', path: 'apps/web/src/lib/notifications/vibrate.ts' },
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'notifications-push-web-push',
|
|
claim_source: 'faq',
|
|
claim_phrase: 'Push notifications let Morphit alert you',
|
|
anchor: {
|
|
kind: 'any_of',
|
|
specs: [
|
|
{
|
|
kind: 'grep',
|
|
pattern: 'pushManager\\.subscribe',
|
|
paths: ['apps/web/src']
|
|
},
|
|
{
|
|
kind: 'grep',
|
|
pattern: "addEventListener\\('push'",
|
|
paths: ['apps/web/src/service-worker.ts']
|
|
}
|
|
]
|
|
},
|
|
status: 'live'
|
|
},
|
|
|
|
// ─── Chat inbox ─────────────────────────────────────────────
|
|
{
|
|
id: 'chat-inbox-folders',
|
|
claim_source: 'faq',
|
|
claim_phrase: 'Archived',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'tab_archived',
|
|
paths: ['apps/web/src/routes/[lang]/chat']
|
|
},
|
|
status: 'live'
|
|
},
|
|
|
|
// ─── Operator alerts ──────────────────────────────────────────
|
|
{
|
|
id: 'operator-matrix-bot',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Operator alerts to a private Matrix DM',
|
|
anchor: { kind: 'file_exists', path: 'apps/matrix-bot/src/main.ts' },
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'operator-resource-monitor',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Resource alerts',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'proc/meminfo',
|
|
paths: ['ops/scripts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
|
|
// ─── Federation / decentralization ────────────────────────────
|
|
{
|
|
id: 'federation-rss-orderbook',
|
|
claim_source: 'faq',
|
|
claim_phrase: '/rss/orderbook.xml',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'rss/orderbook',
|
|
paths: ['apps/indexer/src']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'killswitch',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'kill-switch',
|
|
anchor: { kind: 'file_exists', path: 'apps/relay/src/policy/killSwitch.ts' },
|
|
status: 'live'
|
|
},
|
|
|
|
// ─── Mediakit ──────────────────────────────────────────────
|
|
{
|
|
id: 'mediakit-zip',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: '/morphit-mediakit.zip',
|
|
anchor: { kind: 'file_exists', path: 'apps/web/static/morphit-mediakit.zip' },
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'mediakit-build-script',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'regenerated and re-committed every time its source files change',
|
|
anchor: { kind: 'file_exists', path: 'scripts/build-mediakit.sh' },
|
|
status: 'live'
|
|
},
|
|
|
|
// ─── Upgrade tooling ────────────────────────────────────────
|
|
{
|
|
id: 'upgrade-ops-cli',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'morphit-ops upgrade',
|
|
anchor: { kind: 'file_exists', path: 'apps/ops-cli/src/commands/upgrade.ts' },
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'release-signers',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'release-signers',
|
|
anchor: { kind: 'file_exists', path: '.forgejo/release-signers/README.md' },
|
|
status: 'live'
|
|
},
|
|
|
|
// ─── Crypto / privacy ──────────────────────────────────────────
|
|
{
|
|
id: 'chat-e2ee-x25519-chacha',
|
|
claim_source: 'faq',
|
|
claim_phrase: 'X25519 key agreement',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'X25519|x25519|crypto_box',
|
|
paths: ['apps/web/src']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'monero-view-key-env-only',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'XMR',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'MORPHIT_INDEXER_XMR_FEE_VIEWKEY',
|
|
paths: ['apps/indexer/src']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
// cp16 — brag list #65 (sig-verify on push subscribe).
|
|
// The claim cites three components: (a) a canonical
|
|
// message format, (b) the cross-check smoke that defends
|
|
// the contract, (c) the rejection-reason coverage.
|
|
id: 'push-subscribe-sig-verify',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Push subscriptions are proof-of-ownership protected',
|
|
anchor: {
|
|
kind: 'any_of',
|
|
specs: [
|
|
{
|
|
kind: 'file_exists',
|
|
path: 'apps/relay/src/policy/pushSubscribeSig.ts'
|
|
},
|
|
{
|
|
kind: 'file_exists',
|
|
path: 'apps/relay/scripts/canonical-message-cross-check-smoke.ts'
|
|
}
|
|
]
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
// cp16 walkthrough surfaced this — operator following
|
|
// the env-example file MUST see VAPID placeholders so
|
|
// they know push notifications need setup. Without
|
|
// this, an operator who skips RUN-A-MORPHIT-NODE.md
|
|
// would ship push-disabled silently.
|
|
id: 'vapid-env-documented-in-example',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Push subscriptions are proof-of-ownership protected',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'MORPHIT_RELAY_VAPID_PUBLIC_KEY',
|
|
paths: ['ops/env/relay.env.example']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
// cp17 — featured-slot auction refinements (bid history).
|
|
// Phase A claim: users see their own recent bids with
|
|
// visibility status above the bid form.
|
|
id: 'featured-bid-history-endpoint',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Bidders see their own recent bids inline',
|
|
anchor: {
|
|
kind: 'any_of',
|
|
specs: [
|
|
{
|
|
kind: 'file_exists',
|
|
path: 'apps/indexer/src/api/featuredBids.ts'
|
|
},
|
|
{
|
|
kind: 'file_exists',
|
|
path: 'apps/web/src/lib/components/FeaturedBidHistory.svelte'
|
|
}
|
|
]
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
// cp17 — outbid push notifications.
|
|
id: 'featured-bid-outbid-push',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'displaced bidder gets a push notification',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'outbid_notify_failed',
|
|
paths: ['apps/indexer/src/indexer/handlers/featureBid.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
// cp18 — anti-snipe extension. Soft-close auction rule:
|
|
// late bids extend the deadline so snipers can be
|
|
// countered. Capped at MAX_EXTENSIONS to bound
|
|
// auction-drag.
|
|
id: 'featured-bid-anti-snipe',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'minimum-hours floors prevent micro-bid sniping',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'anti_snipe_extended',
|
|
paths: ['apps/indexer/src/indexer/handlers/featureBid.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp26 transparent-chain privacy framework ────────────────────
|
|
// 5 CHECK rows for the 5 brag entries cp26 added (29 updated +
|
|
// new 30/31/32/33/34). Each pins the canonical code anchor
|
|
// that proves the claim. Per cp26 DD-8: the standing rule
|
|
// (every brag-list claim must be wire-verifiable) was applied
|
|
// to cp26's new claims as a follow-up audit step.
|
|
{
|
|
id: 'cp26-amount-jitter-generalized',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Amount-jitter on every transparent chain',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'export function jitterUtxoAmount',
|
|
paths: ['apps/web/src/lib/chat/payload.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'cp26-address-reuse-detection',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Client-side address-reuse warning',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'export function findPriorShare',
|
|
paths: ['apps/web/src/lib/privacy/addressHistory.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'cp26-payjoin-bip78',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'PayJoin (BIP-78) support for BTC',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'payjoin_endpoint',
|
|
paths: ['apps/web/src/lib/chat/payload.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
id: 'cp26-privacy-guide-pages',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Per-asset privacy guide pages',
|
|
anchor: {
|
|
kind: 'file_exists',
|
|
path: 'apps/web/src/routes/[lang]/privacy/[asset]/+page.svelte'
|
|
},
|
|
status: 'live'
|
|
},
|
|
{
|
|
// "No wallet recommendations" is a POLICY claim, not a
|
|
// feature claim. The anchor here is the privacy-guide
|
|
// content asserting the policy verbatim, since policy is
|
|
// enforced through content discipline rather than code
|
|
// gates. This is the canonical pattern for cp26-style
|
|
// policy claims.
|
|
id: 'cp26-no-wallet-recommendation-policy',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'No wallet recommendations',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'no_wallet_recommendation',
|
|
paths: ['apps/web/src/lib/i18n/locales/en.json']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp27 DASH P2P ────────────────────────────────────────────────
|
|
// New brag entry #279 claims DASH is wired as a 4th Category-B
|
|
// trade-only asset. Anchor on the canonical registry entry —
|
|
// if DASH ever loses its registry slot the brag claim drifts
|
|
// into vaporware and this CHECK row fires.
|
|
{
|
|
id: 'cp27-dash-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dash (DASH) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'DASH'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp30 USDC P2P ────────────────────────────────────────────────
|
|
// New brag entry #280 claims USDC is wired as the 5th Category-B
|
|
// trade-only asset (parallel to USDT — multi-network). Anchor
|
|
// on the canonical registry entry — if USDC ever loses its
|
|
// registry slot the brag claim drifts into vaporware and this
|
|
// CHECK row fires. Brag #29 (amount-jitter) is also extended
|
|
// in cp30 to claim stablecoin coverage; this CHECK row implicitly
|
|
// covers that since #29 only makes sense if USDC is registered.
|
|
{
|
|
id: 'cp30-usdc-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'USD Coin (USDC) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'USDC'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp30-DD-11 USDT per-network explorer override actually works
|
|
// Anchor on the indexer-side body construction that DD-11 added.
|
|
// Before cp30-DD, the indexer-client declared this field but the
|
|
// indexer body never populated it — frontend defensive-fallback
|
|
// hid the bug. Sentinel pins the new body-construction line.
|
|
{
|
|
id: 'cp30-dd-11-usdt-per-network-override-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'USDT (Tether) peer-to-peer across four networks',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'frontendUsdtErc20ChatLinkUrl',
|
|
paths: ['apps/indexer/src/api/instance.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp30-DD-10 USDC per-network explorer override actually works
|
|
// Same pattern as DD-11 above; anchor on the body-construction.
|
|
{
|
|
id: 'cp30-dd-10-usdc-per-network-override-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'USD Coin (USDC) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'frontendUsdcErc20ChatLinkUrl',
|
|
paths: ['apps/indexer/src/api/instance.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp31 DAI P2P ─────────────────────────────────────────────────
|
|
// cp31 brag entry claims DAI is wired as the 6th Category-B
|
|
// trade-only asset (parallel to USDT and USDC — multi-network).
|
|
// Anchor on the canonical registry entry; if DAI ever loses its
|
|
// registry slot the brag claim drifts into vaporware and this
|
|
// CHECK row fires.
|
|
{
|
|
id: 'cp31-dai-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dai (DAI) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'DAI'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp31 DAI per-network explorer override actually works
|
|
// Same pattern as cp30-DD-10/11 — anchor on indexer-side body
|
|
// construction. Catches the same "interface declared, body
|
|
// missing" class of bug DD-10 closed for USDC at cp30-DD.
|
|
{
|
|
id: 'cp31-dai-per-network-override-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dai (DAI) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'frontendDaiErc20ChatLinkUrl',
|
|
paths: ['apps/indexer/src/api/instance.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp31 DAI distinct privacy-warning class (not lumped with
|
|
// USDT/USDC). Brag claims DAI gets the more-nuanced
|
|
// `dai_partly_centralized` warning rather than the
|
|
// freeze-power-implying `*_centralized` class. Anchor on the
|
|
// ─── cp33 DOGE P2P ────────────────────────────────────────────────
|
|
// New brag entry #282 claims DOGE is wired as a 7th Category-B
|
|
// trade-only asset. Anchor on the canonical registry entry —
|
|
// if DOGE ever loses its registry slot the brag claim drifts
|
|
// into vaporware and this CHECK row fires.
|
|
{
|
|
id: 'cp33-doge-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dogecoin (DOGE) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'DOGE'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp33 DOGE payment-rail wired (cp32 LL #36) ───────────────────
|
|
// Cp32 LL #36: every tradable asset must also be wired as a
|
|
// payment rail. Cp31 missed this for DAI (closed in cp32
|
|
// CODE-1); cp33 ships DOGE with both axes same-turn. Anchor:
|
|
// payments/registry.ts must contain pay_doge.
|
|
{
|
|
id: 'cp33-doge-payment-rail-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dogecoin (DOGE) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "key: 'pay_doge'",
|
|
paths: ['apps/web/src/lib/payments/registry.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp33 DOGE explorer URL bundled default ───────────────────────
|
|
// blockchair.com/dogecoin chosen from Ken's 9-explorer survey.
|
|
// Anchor on the constant; a renamed/removed constant means the
|
|
// frontend has lost its fallback default and operators see a
|
|
// broken explorer link when no override is configured.
|
|
{
|
|
id: 'cp33-doge-explorer-bundled-default',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dogecoin (DOGE) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'BUNDLED_DOGE_CHAT_LINK_URL',
|
|
paths: ['apps/web/src/lib/explorer/urlsCore.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp39 ZEC P2P trading wired ───────────────────────────────────
|
|
// Zcash addition (cp39 — Part 122). Eleventh tradable asset.
|
|
// Brag list (entry #283) advertises ZEC peer-to-peer trading.
|
|
// Anchor: canonical registry must contain a ticker entry for ZEC.
|
|
// If ZEC ever loses its registry slot the brag claim drifts
|
|
// into vaporware and this CHECK row fires.
|
|
{
|
|
id: 'cp39-zec-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Zcash (ZEC) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'ZEC'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp39 ZEC payment-rail wired (cp32 LL #36) ────────────────────
|
|
// Cp32 LL #36: every tradable asset must also be wired as a
|
|
// payment rail. Cp39 ships ZEC with both axes same-turn per
|
|
// the pattern established in cp33 for DOGE. Anchor:
|
|
// payments/registry.ts must contain pay_zec.
|
|
{
|
|
id: 'cp39-zec-payment-rail-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Zcash (ZEC) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "key: 'pay_zec'",
|
|
paths: ['apps/web/src/lib/payments/registry.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp39 ZEC explorer URL bundled default ────────────────────────
|
|
// mainnet.zcashexplorer.app chosen from Ken's 7-explorer survey
|
|
// as the community-run, project-aligned default. Anchor on the
|
|
// constant; a renamed/removed constant means the frontend has
|
|
// lost its fallback default and operators see a broken explorer
|
|
// link when no override is configured.
|
|
{
|
|
id: 'cp39-zec-explorer-bundled-default',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Zcash (ZEC) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'BUNDLED_ZEC_CHAT_LINK_URL',
|
|
paths: ['apps/web/src/lib/explorer/urlsCore.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp41 ARRR P2P trading wired ─────────────────────────────────
|
|
// Pirate Chain addition (cp41 — Part 122). Twelfth tradable asset.
|
|
// Brag list (new entry) advertises ARRR peer-to-peer trading.
|
|
// Anchor: canonical registry must contain a ticker entry for ARRR.
|
|
// If ARRR ever loses its registry slot the brag claim drifts
|
|
// into vaporware and this CHECK row fires.
|
|
{
|
|
id: 'cp41-arrr-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Pirate Chain (ARRR) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'ARRR'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp41 ARRR payment-rail wired (cp32 LL #36) ──────────────────
|
|
// Cp32 LL #36: every tradable asset must also be wired as a
|
|
// payment rail. Cp41 ships ARRR with both axes same-turn per
|
|
// the pattern established for DOGE at cp33 and ZEC at cp39.
|
|
// Anchor: payments/registry.ts must contain pay_arrr.
|
|
{
|
|
id: 'cp41-arrr-payment-rail-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Pirate Chain (ARRR) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "key: 'pay_arrr'",
|
|
paths: ['apps/web/src/lib/payments/registry.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp41 ARRR explorer URL bundled default ──────────────────────
|
|
// explorer.piratechain.com chosen from Ken's 3-explorer survey
|
|
// as the official project pointer, project-aligned, free of
|
|
// third-party tracking. Anchor on the constant; a renamed/
|
|
// removed constant means the frontend has lost its fallback
|
|
// default and operators see a broken explorer link when no
|
|
// override is configured.
|
|
{
|
|
id: 'cp41-arrr-explorer-bundled-default',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Pirate Chain (ARRR) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'BUNDLED_ARRR_CHAT_LINK_URL',
|
|
paths: ['apps/web/src/lib/explorer/urlsCore.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp43 DCR P2P trading wired ──────────────────────────────────
|
|
// Decred addition (cp43 — Part 122). Thirteenth tradable asset.
|
|
// Brag list (new entry #285) advertises DCR peer-to-peer trading.
|
|
// Anchor: canonical registry must contain a ticker entry for DCR.
|
|
{
|
|
id: 'cp43-dcr-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Decred (DCR) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'DCR'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp43 DCR payment-rail wired (cp32 LL #36) ───────────────────
|
|
// Cp32 LL #36: every tradable asset must also be wired as a
|
|
// payment rail. Cp43 ships DCR with both axes same-turn per the
|
|
// pattern established for DOGE/cp33, ZEC/cp39, and ARRR/cp41.
|
|
{
|
|
id: 'cp43-dcr-payment-rail-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Decred (DCR) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "key: 'pay_dcr'",
|
|
paths: ['apps/web/src/lib/payments/registry.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp43 DCR explorer URL bundled default ───────────────────────
|
|
// dcrdata.decred.org chosen from Ken's 4-explorer survey as the
|
|
// official project explorer. Anchor on the constant; a renamed
|
|
// or removed constant means the frontend has lost its fallback
|
|
// default.
|
|
{
|
|
id: 'cp43-dcr-explorer-bundled-default',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Decred (DCR) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'BUNDLED_DCR_CHAT_LINK_URL',
|
|
paths: ['apps/web/src/lib/explorer/urlsCore.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp45 SOL P2P trading wired ──────────────────────────────────
|
|
// Solana addition (cp45 — Part 122). Fourteenth tradable asset.
|
|
// Brag entry #286 advertises SOL peer-to-peer trading. Anchor on
|
|
// canonical registry SOL ticker entry.
|
|
{
|
|
id: 'cp45-sol-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Solana (SOL) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'SOL'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp45 SOL payment-rail wired (cp32 LL #36) ──────────────────
|
|
// Every tradable asset MUST also be wired as a payment rail. Cp45
|
|
// ships SOL with both axes same-turn per the pattern established
|
|
// for DOGE/cp33, ZEC/cp39, ARRR/cp41, DCR/cp43.
|
|
{
|
|
id: 'cp45-sol-payment-rail-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Solana (SOL) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "key: 'pay_sol'",
|
|
paths: ['apps/web/src/lib/payments/registry.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp45 SOL explorer URL bundled default ──────────────────────
|
|
// explorer.solana.com chosen from Ken's 5-explorer survey as the
|
|
// official project explorer. Anchor on the constant; renamed or
|
|
// removed means the frontend has lost its fallback default.
|
|
{
|
|
id: 'cp45-sol-explorer-bundled-default',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Solana (SOL) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'BUNDLED_SOL_CHAT_LINK_URL',
|
|
paths: ['apps/web/src/lib/explorer/urlsCore.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp47 ETH P2P trading wired ──────────────────────────────────
|
|
// Ethereum addition (cp47 — Part 122). Fifteenth tradable asset.
|
|
// Brag entry #287 advertises ETH peer-to-peer trading.
|
|
{
|
|
id: 'cp47-eth-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Ethereum (ETH) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'ETH'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp47 ETH payment-rail wired (cp32 LL #36) ──────────────────
|
|
{
|
|
id: 'cp47-eth-payment-rail-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Ethereum (ETH) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "key: 'pay_eth'",
|
|
paths: ['apps/web/src/lib/payments/registry.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp47 ETH explorer URL bundled default ──────────────────────
|
|
{
|
|
id: 'cp47-eth-explorer-bundled-default',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Ethereum (ETH) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'BUNDLED_ETH_CHAT_LINK_URL',
|
|
paths: ['apps/web/src/lib/explorer/urlsCore.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp49 XRP P2P trading wired ──────────────────────────────────
|
|
// Ripple addition (cp49 — Part 122). Sixteenth tradable asset.
|
|
// Brag entry #288 advertises XRP peer-to-peer trading.
|
|
{
|
|
id: 'cp49-xrp-p2p',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Ripple (XRP) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "ticker: 'XRP'",
|
|
paths: ['packages/asset-registry/src/index.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp49 XRP payment-rail wired (cp32 LL #36) ──────────────────
|
|
{
|
|
id: 'cp49-xrp-payment-rail-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Ripple (XRP) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "key: 'pay_xrp'",
|
|
paths: ['apps/web/src/lib/payments/registry.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp49 XRP explorer URL bundled default ──────────────────────
|
|
{
|
|
id: 'cp49-xrp-explorer-bundled-default',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Ripple (XRP) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'BUNDLED_XRP_CHAT_LINK_URL',
|
|
paths: ['apps/web/src/lib/explorer/urlsCore.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp34 I-1 closure — DAI post-page wired ───────────────────────
|
|
// Cp31 added DAI to the canonical registry + chat surfaces but
|
|
// MISSED the post page's DaiNetworkPicker mount + daiNetwork
|
|
// state. DAI orders silently failed cp31→cp34 because the
|
|
// indexer requires asset_network for DAI and the form never
|
|
// supplied one. This CHECK pins that DaiNetworkPicker is
|
|
// actually mounted in the post page; removing it again would
|
|
// reproduce the I-1 bug.
|
|
{
|
|
id: 'cp34-i1-dai-post-page-wired',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dai (DAI) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: '<DaiNetworkPicker',
|
|
paths: ['apps/web/src/routes/[lang]/post/+page.svelte']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp34 I-3 closure — orderbook DAI network chip rendered ───────
|
|
// Cp30 (USDC) + cp31 (DAI) added the canonical registry entry +
|
|
// per-network address shapes but the orderbook page never
|
|
// rendered a network chip for non-USDT multi-network rows. A
|
|
// USDC-ERC-20 order looked identical to USDC-Solana on the list;
|
|
// a DAI-ERC-20 looked identical to DAI-Arbitrum. CP34 closure
|
|
// adds the chips with matching i18n keys. This CHECK pins that
|
|
// the DAI chip is in place.
|
|
{
|
|
id: 'cp34-i3-orderbook-dai-chip-rendered',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dai (DAI) peer-to-peer',
|
|
// cp442 — the per-row ternary (`daiRowNetwork`) was extracted into the
|
|
// shared `networkChipFor` helper so the featured cards get the chip too.
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: "isDaiNetwork",
|
|
paths: ['apps/web/src/lib/orders/networkChip.ts']
|
|
},
|
|
status: 'live'
|
|
},
|
|
// ─── cp34 H-1 closure — cheat-sheet DAI + DOGE rows rendered ──────
|
|
// Cp31 (DAI) + cp33 (DOGE) added per-asset i18n strings under
|
|
// cheat_sheet.section_assets but the cheat-sheet page never
|
|
// rendered <dd> for those new assets. Strings existed in 10
|
|
// locales but were orphaned. CP34 closure adds both rows.
|
|
{
|
|
id: 'cp34-h1-cheat-sheet-doge-rendered',
|
|
claim_source: 'brag_list',
|
|
claim_phrase: 'Dogecoin (DOGE) peer-to-peer',
|
|
anchor: {
|
|
kind: 'grep',
|
|
pattern: 'cheat_sheet.section_assets.doge',
|
|
paths: ['apps/web/src/routes/[lang]/cheat-sheet/+page.svelte']
|
|
},
|
|
status: 'live'
|
|
}
|
|
];
|
|
|
|
// ─── Verifier ──────────────────────────────────────────────────────
|
|
function bragText(): string {
|
|
return readFileSync(join(REPO, 'MORPHIT-BRAG-LIST.md'), 'utf-8');
|
|
}
|
|
|
|
function faqText(): string {
|
|
const json = readFileSync(
|
|
join(REPO, 'apps/web/src/lib/i18n/locales/en.json'),
|
|
'utf-8'
|
|
);
|
|
const d = JSON.parse(json);
|
|
let out = '';
|
|
for (const v of Object.values(d.faq?.entries ?? {})) {
|
|
if (typeof v === 'object' && v !== null) {
|
|
const e = v as { q?: string; a?: string };
|
|
out += (e.q ?? '') + '\n' + (e.a ?? '') + '\n';
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function claimAppears(c: Check): boolean {
|
|
const haystack = (c.claim_source === 'brag_list' ? bragText() : faqText()).toLowerCase();
|
|
return haystack.includes(c.claim_phrase.toLowerCase());
|
|
}
|
|
|
|
function anchorSatisfied(a: AnchorSpec): boolean {
|
|
if (a.kind === 'file_exists') {
|
|
return existsSync(join(REPO, a.path));
|
|
}
|
|
if (a.kind === 'any_of') {
|
|
return a.specs.some(anchorSatisfied);
|
|
}
|
|
// grep
|
|
for (const p of a.paths) {
|
|
const abs = join(REPO, p);
|
|
if (!existsSync(abs)) continue;
|
|
try {
|
|
// rg if available; else fall back to grep -rE
|
|
const cmd = `grep -rqE ${JSON.stringify(a.pattern)} ${JSON.stringify(abs)}`;
|
|
execSync(cmd, { stdio: 'ignore' });
|
|
return true;
|
|
} catch {
|
|
// no match in this path; keep trying
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ─── Run ──────────────────────────────────────────────────────────
|
|
console.log(`wiring-completeness smoke: ${CHECKS.length} checks\n`);
|
|
|
|
let failed = 0;
|
|
let deferred = 0;
|
|
let okCount = 0;
|
|
const failures: string[] = [];
|
|
const deferrals: string[] = [];
|
|
|
|
for (const c of CHECKS) {
|
|
const claimed = claimAppears(c);
|
|
const wired = anchorSatisfied(c.anchor);
|
|
|
|
if (c.status === 'deferred') {
|
|
// Deferred rows: claim should still appear (it's a known
|
|
// pre-launch state) but anchor is allowed to be missing.
|
|
// We REPORT but do not fail.
|
|
deferred++;
|
|
const tag = wired ? '⚠ DEFERRED → now wired (promote!)' : '⚠ DEFERRED';
|
|
console.log(` ${tag} ${c.id}`);
|
|
console.log(` claim: ${c.claim_source}/"${c.claim_phrase.slice(0, 60)}"`);
|
|
console.log(` reason: ${c.reason}`);
|
|
deferrals.push(c.id);
|
|
continue;
|
|
}
|
|
|
|
// Live rows: BOTH must hold.
|
|
if (claimed && wired) {
|
|
okCount++;
|
|
console.log(` ✓ ${c.id}`);
|
|
continue;
|
|
}
|
|
|
|
failed++;
|
|
console.log(` ✗ ${c.id}`);
|
|
if (!claimed) {
|
|
console.log(
|
|
` MISSING CLAIM: phrase "${c.claim_phrase}" not found in ${c.claim_source}. ` +
|
|
'Either the claim was removed and this check is stale, or the check phrase needs updating.'
|
|
);
|
|
}
|
|
if (!wired) {
|
|
console.log(` MISSING WIRING: code anchor not found. Spec: ${JSON.stringify(c.anchor)}`);
|
|
}
|
|
failures.push(c.id);
|
|
}
|
|
|
|
console.log('');
|
|
console.log(`Summary: ${okCount} live + ${deferred} deferred + ${failed} failed`);
|
|
if (deferred > 0) {
|
|
console.log(`Deferred items (visible every run — promote when wired):`);
|
|
for (const d of deferrals) console.log(` · ${d}`);
|
|
}
|
|
if (failed === 0) {
|
|
const total = okCount + deferred;
|
|
console.log(`\n✓ all ${total} wiring-completeness checks hold (${okCount} live, ${deferred} deferred)`);
|
|
process.exit(0);
|
|
} else {
|
|
console.error(`\n✗ ${failed} wiring gaps`);
|
|
process.exit(1);
|
|
}
|