feat: WordPress authority bridge — the snapshot is the contract#10
Open
Eth-Interchained wants to merge 2 commits into
Open
feat: WordPress authority bridge — the snapshot is the contract#10Eth-Interchained wants to merge 2 commits into
Eth-Interchained wants to merge 2 commits into
Conversation
campaign.ts already named 'blog' as a valid surface and orlando's contentPlan already listed 'articles' as a collection — neither was ever built. This ships the first writer for it: WordPress posts, pulled through the wp-portal-bridge signed tunnel, become salon-platform articles. No new render pipeline, no new styling. WordPress content lands in the SAME articles collection pages read from, rendered through the SAME pageShell()/Park Avenue Warm CSS every other page uses. By the time it hits the renderer it's indistinguishable from hand-seeded content. The tightly zod-modeled salon/stylist/service data (working well) is untouched. - src/server/wordpress-sync.ts: syncWordPressArticles() — same provenance discipline as seed.ts (content-addressed idempotency keyed on the bridge's own contentHash, a seed_runs provenance root every article's caused_by chains to, real writes measured via engine seq delta so idempotency is reported honestly, not just claimed) - scripts/sync-wordpress.ts: thin CLI wrapper matching seed.ts's exact shape (npm run sync:wordpress), verify()-gated - src/server/entities.ts: articles collection + typed reads (listArticles, getArticleBySlug, articleId — same deterministic-id pattern as identityId) - src/server/articles.ts: GET /blog + /blog/:slug, same esc()/pageShell()/ CSS-class vocabulary as pages.ts - src/server/render.ts: sitemap coverage for articles, computed independent of anchorSalonHandle (a directory/national campaign with no anchor salon can still have a journal) - src/server/config.ts: optional PORTAL_BRIDGE_BASE_URL/PORTAL_TMK, must be set together or not at all (validateConfig rejects half-configured) - @interchained/portal-source-wordpress added for BridgeClient + the sanitizer (WordPress HTML is sanitized before it ever reaches the articles collection, verified in the unit suite with a live <script> payload) Verified end to end against a real WordPress (wp-portal-bridge sandbox) and a real nedbd: 2 published posts synced, second run correctly reports 0 writes (hash-chain head unchanged, confirmed independent of the counter), engine verify:true both times. Booted the real Express app against the synced db: /blog and /blog/:slug render with correct SEO title (slotted into orlando's titleTemplate), Park Avenue Warm CSS applied, zero unsanitized script survives from a deliberately hostile test payload. 35/35 unit tests green (5 new). TypeScript clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t importer Course correction (Mark, 2026-07-08): the previous commit on this branch wedged WordPress in as a slug-cherry-picking sync that invented /blog/:slug URLs and dropped the link graph — a content importer. The spec's deal was an AUTHORITY bridge: all links in the snapshot, every route at its earned path. This makes it so. - WordPressBridgeSource consumes the FULL snapshot (all routes, exact paths, link graph, menus, one contentHash), persisted in NEDB via the bridge's own NedbSnapshotStore — hash-chained, caused_by provenance, verify-able. No per-post copying; the articles collection is gone. - WordPress routes render at their EARNED paths in the campaign theme: /botanical-repair-guide/ stays /botanical-repair-guide/. Internal links in content resolve because every published route resolves. - Precedence (decided): entity surfaces win — pages.ts mounts first, WordPress fills everything the platform didn't claim, SPA shell last. - Canonical posture (decided, built for migration scenarios): PORTAL_BRIDGE_CANONICAL=source (default) keeps authority at the origin WordPress site (canonicalUrl override in buildHead — the ONE deliberate exception to origin+path discipline, documented in head.ts); =self flips when a migration lands. Sitemap honesty follows the posture: WP routes join the sitemap only in self posture (sitemaps list canonical URLs). - Boot resilience: stored snapshot boots with WordPress down; nothing stored + WordPress down = loud warning + lazy retry with cooldown; the storefront never dies over the bridge. - /blog journal index remains, linking to REAL paths only. Verified live (real WordPress + real nedbd + this server): - COLLISION: a 'services' page created in WordPress; storefront /services still serves the entity price tables — WP trap page never rendered - /botanical-repair-guide/ at its earned path, Park Avenue theme, canonical -> the SOURCE WordPress origin (source posture) - internal link inside the post content resolves on the storefront (200) - sitemap in source posture: entity surfaces only, zero WP routes - PORTAL_BRIDGE_CANONICAL=self flip: canonical becomes self-origin, WP routes join the sitemap at earned paths - boot log: snapshot adopted from NEDB store, then refreshed from tunnel 36/36 unit tests. TypeScript clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
(Reworked mid-review after a course correction — the first commit on this branch wedged WordPress in as a slug-cherry-picking sync inventing /blog/:slug URLs. That was a content importer, not the authority bridge the spec defines. The second commit makes the snapshot the contract.)
WordPress (mintontheavenue.com, plugin installed) becomes a full content source for this storefront: the bridge snapshot — every published route at its EARNED path, the internal link graph, menus, one contentHash — is consumed via WordPressBridgeSource and persisted in NEDB through the bridge's own NedbSnapshotStore (hash-chained, caused_by provenance). A WordPress post at /botanical-repair-guide/ renders at /botanical-repair-guide/ on this domain, in Park Avenue Warm, and every internal link in the content resolves because every published route resolves.
Decisions encoded (Mark, 2026-07-08)
Resilience
Stored snapshot in NEDB boots the bridge with WordPress down (that's snapshot-first). Nothing stored + WordPress down = loud warning + lazy retry with cooldown — the storefront never dies over the bridge.
Tests run
Verification scope (honest)
Verified against local sandboxes (WP on SQLite + nedbd 2.6.1), not yet against the production Mint WordPress or live salon nedbd. First production connection should re-run this battery — especially the collision list (Mint's real WP has more pages than the sandbox).
Follow-up work
Webhook-triggered snapshot refresh · redirect map for migration completion (source paths → new domain) · Mint production connection + battery re-run
🤖 Generated with Claude Code