The build spec for the implementing agent: an open-source WordPress plugin that syncs approved JSON-LD from the AIVIS Public API into local storage and injects exactly one script per page — fail-open, local-only render path, zero telemetry.
00 · Front matter
aivis-wordpress-connector (org per shared Q-8 of the edge spec), plugin slug aivis-os, license GPL-2.0-or-later (WordPress ecosystem requirement — the deliberate divergence from the edge connector's Apache-2.0), plugin 1.0.0 target, API contract: AIVIS Public API v1.0.0.wp_update_post(), no revisions, no post_modified changes).https://aivis-new.dev.onepoint.ro/api/public/v1 (docs at /docs, spec at /openapi.json) — development and staging only; the release ships the confirmed production hostname (Q-01).| Edge principle | WordPress implementation |
|---|---|
| Pull approved JSON-LD from Public API v1 | Background synchronization from the same read-only API |
| Cache close to the serving path | Last-known-good rows in the WP database; block then rides in any full-page cache/CDN HTML |
One additive script before </head> | One tagged script via wp_head |
| Fail open (edge I1) | WP-I1 — public rendering never calls AIVIS; serves stored artifact or nothing |
| User-agent parity (edge I3) | WP-I3 — no crawler classification or conditional output |
| Zero-trust ingestion (edge I7 / S-01…S-06) | WP-I7 + ZT-01…ZT-06 |
| Retraction wins over resilience (edge C-10) | R-01…R-03 (added in this revision) |
| Targeted invalidation (edge C-07) | Cache-adapter contract + honest publication state (WP-I10) |
| Reversibility (edge I6) | Disable/deactivate + cache purge; content never rewritten (WP-I5) |
| Open-source reviewability (edge D10/§12) | Public GPL repo, CI-built reproducible release ZIPs (§18) |
| Crawler analytics (edge A-*) | Permanently out of WordPress — measured at the edge instead (§22) |
Two intentional architecture differences: (1) WordPress syncs asynchronously instead of fetching on cold public requests — PHP origin latency must never depend on the remote API; (2) analytics is excluded (§22). Shared prerequisite with the edge connector: business-scoped API tokens (edge spec V-02) — a user-scoped token inside a customer's WordPress database (backups, exports, migrations) is a wider exposure than a Worker secret, so V-02 lands before any customer-managed install.
0R · Review verdict
Verdict: the draft is sound and is accepted as the base. Its architecture (async local sync, local-only render path, no republishing, zero-trust pipeline, honest cache states, GPL) is exactly right for WordPress. This revision makes six changes:
/jsonld?url= multi-chain "winner" rule (non-stale first, then newest) and the loopback self-fetch used by live verification are flagged verify at build with fallbacks.01 · Scope
Interpretation note carried from the draft: "inject all available AIVIS plugins" means "inject all available JSON-LD artifacts exposed by Public API v1." The v1 API exposes JSON-LD and browsing/status endpoints only; any future AIVIS output types need their own versioned schemas and delivery specs — nothing generic is implemented speculatively.
/me)jsonLd.ready = truellms.txt deliveryCustomer responsibility for other JSON-LD. The customer ensures no other plugin, SEO tool, theme, tag manager, or custom code emits JSON-LD on AIVIS-enabled pages. The connector assumes there is none: it must not scan plugins, inspect HTML for other JSON-LD, keep SEO-plugin lists, remove third-party markup, block activation, or vouch for combined validity. The settings screen states this as information — it never runs a conflict check.
02 · Guarantees
| ID | Invariant |
|---|---|
| WP-I1 | Fail open. Any connector failure returns the normal WordPress page — never an error page, redirect, maintenance response, or non-2xx on the public path. |
| WP-I2 | Local-only render path. No public request makes a blocking network call to AIVIS. A render performs at most one indexed local lookup + serialization. |
| WP-I3 | User-agent parity. Identical JSON-LD for humans, search crawlers, AI crawlers, logged-in and anonymous viewers of the same canonical URL. |
| WP-I4 | Additive output only. Exactly one tagged AIVIS script when a valid artifact matches; no other markup modified or removed. |
| WP-I5 | No content mutation. Never wp_update_post(), status changes, revisions, or post_modified updates from sync or invalidation. |
| WP-I6 | Secret containment. The bearer token never appears in HTML, JS, REST responses, logs, exceptions, Site Health, or support bundles. |
| WP-I7 | Zero-trust artifact handling. AIVIS responses are size-limited, strictly decoded, schema-checked, business- and host-bound, re-serialized, and escaped so a JSON string can never close the script element or execute (ZT-01…06). |
| WP-I8 | Last-known-good delivery — bounded by retraction. A failed refresh never overwrites a valid artifact; transient API/auth/rate-limit/parse failures keep serving it. It is withdrawn only when an administrator disables injection, an authoritative fetch-404 confirms absence (R-01), or the inventory retirement rule fires (R-02). |
| WP-I9 | No telemetry. No visitor, crawler, page-view, IP, user-agent, or WP-user data is sent to AIVIS — the only outbound data is API auth plus the URLs/IDs needed to retrieve content. Permanent, not v1-only (§22). |
| WP-I10 | Honest cache status. "Live on the site" is reported only after a supported adapter confirms invalidation or public-page verification finds the marker + expected hash — never merely because the database was updated. |
03 · Architecture
| Component | Responsibility | Must not do |
|---|---|---|
| API client | Authenticated, bounded, version-pinned requests | Interpret Schema.org semantics |
| Inventory service | Traverse businesses/chains/URLs + pagination | Edit WordPress content |
| Synchronizer | Fetch changed artifacts, commit valid rows | Run on the visitor's critical path |
| Artifact validator | Structural, size, scope, output-safety checks | Repair or improve AIVIS JSON-LD |
| Repository | Persistent URL-keyed last-known-good storage | Depend on expiring transients alone |
| URL resolver | Determine the canonical current URL | Crawl the site |
| Injector | Emit one safe script in wp_head | Detect or alter other JSON-LD |
| Cache invalidator | Purge changed public URLs via adapters | Republish pages; flush unrelated caches indiscriminately |
| Admin UI | Configuration, status, actions, notices | Expose the token or raw sensitive diagnostics |
| Scheduler | Resumable batches + retries | Sleep in-request; create overlapping jobs |
04 · API contract
Base /api/public/v1; every request carries Authorization: Bearer aivis_… and Accept: application/json.
| Endpoint | Plugin use |
|---|---|
GET /me | Validate token; display account/token name |
GET /businesses | Populate the business selector |
GET /businesses/{id}/chains | Discover non-archived chains + pipeline state |
GET /chains/{id}/urls | Discover URLs with jsonLd.ready / stale / generatedAt |
GET /jsonld?url={absoluteUrl} | Retrieve the artifact per distinct URL. Draft assumes AIVIS resolves multi-chain membership "non-stale first, then newest" verify at build |
GET /urls/{urlId}/jsonld | Diagnostics / exact-ID verification only |
limit=200; follow nextCursor; a sync is authoritative only when every page of every chain completed. A partial traversal never deactivates stored artifacts.AIVIS_API_BASE_URL constant or test-only filter.wp_safe_remote_get() (unsafe URLs rejected), TLS verified, zero redirects (no token forwarding), 10 s background timeout, 1 MiB response cap, connector version in the user agent, never on the public render path.| Status | Required behaviour |
|---|---|
200 | Validate through ZT-01…06 before storing or displaying anything |
400 | Mark the URL request invalid; no retry without changing the request |
401 | Stop the sync, keep last-known-good rows, show "API key invalid or revoked" |
403 | Stop the sync, keep rows, show "AIVIS account unavailable or deactivated" |
404 (new URL) | Negative-cache the miss; retry only after inventory change/expiry |
404 (stored URL) | Retraction R-01: if API reachability is confirmed in the same run, deactivate the row immediately + purge that URL; if reachability is unconfirmed, keep serving and re-check next run |
429 | Honor Retry-After; schedule a later batch; never sleep in-process |
5xx / network | Keep last-known-good; retry via scheduled backoff |
05 · Administrator experience
WordPress Settings API, standard components, accessible labels, nonces, manage_options by default (filterable, §16).
/me; escaped account/token name shownAIVIS_API_TOKEN in wp-config.php (UI shows "Managed in wp-config.php"); else a dedicated non-autoloaded option; masked placeholder ≠ new value06 · Synchronization & retraction
/me, then the selected business is still accessible.jsonLd.ready = true./jsonld?url=… per unique URL (AIVIS applies its winner rule); validate through ZT-01…06; upsert only valid artifacts.Each scheduled run traverses inventory metadata and refetches a URL only when: no local artifact exists; any candidate's generatedAt changed; readiness flipped to true; a forced refresh was requested; a prior failure's retry is due; or the local row failed its integrity check. If the fetched content hash is unchanged: update metadata, do not purge.
wp-cron.php or WP-CLI where the freshness interval matters operationally.R-01 — authoritative 404 deactivates immediately. When /jsonld?url=… returns HTTP 404 for a stored URL and API reachability is confirmed within the same run (another request succeeded, or a /me probe passes), the row is deactivated at once (soft-retire, error code AIVIS_RETRACTED) and that URL's page cache is purged. A confirmed 404 is a statement of absence, not a failure — last-known-good exists to absorb transport problems only (WP-I8). Without this rule, a deliberately unpublished bad JSON-LD would keep serving for two full sync cycles.
Public request with no local row: output nothing, never call the API synchronously, schedule one deduplicated background refresh (if connected + enabled), and negative-cache that scheduling decision for 15 minutes to prevent bot-driven job floods.
07 · URL identity
AIVIS matches exactly plus a trailing-slash variant — so the plugin derives a stable canonical URL per request:
Host header.aivis_connector_current_url filter for compatibility integrations.Normalization contract (local keys): http/https only; lowercase scheme+host; strip default ports and fragments; drop tracking-only params (utm_*, gclid, fbclid, msclkid, _ga); keep semantically meaningful params, sorted deterministically; preserve path + percent-encoding semantics; treat both trailing-slash variants as aliases; key = SHA-256 of the normalized result. The AIVIS response url is stored for audit. Normalization must never make two semantically distinct WP URLs share one artifact.
Host + business binding before storing: response businessId equals the selected business; response host ∈ allowed site hosts (defaults from home_url()/site_url(), aliases configurable for domain-mapped/multilingual setups); requested vs returned URL exact or approved alias; urlId/chainId non-empty; chain belongs to the selected business when inventory context exists.
08 · Storage
Custom table {$wpdb->prefix}aivis_jsonld (posts, pages, CPTs, archives, taxonomy URLs, commerce routes, and multilingual URLs don't all map to post meta). Created/upgraded with dbDelta() + a schema-version option.
| Column | Type | Purpose |
|---|---|---|
id | bigint unsigned | Primary key |
url_key | char(64) | SHA-256 normalized-URL key — unique index |
source_url | text | Absolute URL returned by AIVIS (audit) |
url_id / chain_id / business_id | varchar(191) | AIVIS identities; winning chain |
language_code | varchar(16) | AIVIS language |
json_ld | longtext | Safely re-serialized JSON document (ZT-05) |
content_hash | char(64) | SHA-256 of the exact stored serialization |
source_generated_at | datetime | AIVIS generation time (UTC) |
source_stale | tinyint(1) | AIVIS stale flag — stale artifacts remain serviceable (serve-gate is AIVIS-side, edge V-04) |
active | tinyint(1) | Runtime injection permitted |
local_post_id | bigint unsigned null | Optional WP object mapping for cache invalidation |
last_seen_sync_id | char(36) | Last job that observed the URL |
missing_complete_runs | smallint unsigned | Consecutive authoritative absences (R-02) |
last_synced_at / retired_at | datetime / datetime null | Last successful validation · soft-retirement time |
Indexes: PK; unique url_key; (business_id, active); url_id; last_seen_sync_id; retired_at. Options (all non-autoloaded, separate): token source/status + DB fallback; selected business + allowed hosts; delivery settings; sync state/cursors; capped diagnostics; schema version; uninstall-retention preference. Transients may vanish before expiry — never the only artifact store.
09 · Zero-trust pipeline
| Gate | Requirements |
|---|---|
| ZT-01 Transport | HTTPS only · TLS verified · zero redirects · fixed AIVIS host · 1 MiB cap · JSON content type (charset param allowed) |
| ZT-02 Envelope | Require v1 fields: urlId, chainId, businessId, url (absolute), languageCode, stale (bool), generatedAt (valid timestamp), jsonLd (object|array). Unknown additional fields are ignored (forward-compatible); violations of the pinned schema reject |
| ZT-03 Scope | URL, host, business, chain binding per §07 |
| ZT-04 Structure | Exception-based decode, bounded depth; object/array root only; reject invalid UTF-8, non-finite numbers, scalar roots, excess nesting, oversize. Never validate Schema.org semantics — that is AIVIS business logic |
| ZT-05 Safe re-serialization | Store/output a plugin-generated serialization, never the raw API substring. PHP flags: JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION. Hex-encoding < makes </script> breakout impossible |
| ZT-06 Atomic commit | Validate + serialize fully before the write transaction; atomic upsert; prior row preserved on any failure; hash computed from the exact stored bytes; purge fires only after successful commit and only when hash or activation state changed |
10 · Front-end injection
wp_head at priority 100. No output buffering or whole-document rewriting in core v1 (compatibility and memory risk; the standard hook already solves it). Themes must call wp_head() — absence is a Site Health warning, not an output-buffer fallback.<script type="application/ld+json" data-aivis="1">…</script> — no token, IDs, debug, comments, pixels, or executable JS; no variation by UA, cookie, consent, login, referrer, or IP (WP-I3); per-request guard against duplicate emission if the hook fires twice; never searches for or touches other JSON-LD; on any runtime check failure emit nothing and continue (WP-I1).11 · Cache & publication
An artifact is "stored" after local commit; it is "publicly live" only after a compatible adapter confirms invalidation for the URL or live verification finds data-aivis="1" with the expected hash in public HTML (WP-I10). Cache invalidation is infrastructure, never a content edit (WP-I5).
clean_post_cache() (object caches only — not assumed to purge page caches/CDNs).do_action( 'aivis_connector_purge_urls', $urls, $context ).verify.Adapter interface: is_available(): bool; purge_urls(array $urls): PurgeResult; optional purge_all() (first activation/deactivation only); provider-safe batching; sanitized errors; no AIVIS credentials. Targeted purges preferred; full-site purge only when the provider can't purge URLs and the admin initiated enable/disable/deactivate/full-resync. Never flush the global WP object cache just to publish JSON-LD. No supported adapter: finish the sync, mark "manual purge required" with affected-URL count + instructions, never claim live, allow verification after the manual purge. Bundled adapter matrix is a release decision (Q-03); the neutral interface + manual fallback are mandatory.
12 · Failure behaviour
| Failure | Public page | Background / admin |
|---|---|---|
| AIVIS DNS/TLS/timeout | Stored artifact or no AIVIS output | Retain data; back off; sanitized status |
401 / revoked token | Last-known-good | Stop sync; request new token |
403 / deactivated account | Last-known-good | Stop sync; show account status |
404 new URL | No AIVIS output | Negative-cache; retry on inventory change/expiry |
404 stored URL, API reachable | Deactivated immediately (R-01) | Purge URL; record AIVIS_RETRACTED |
404 stored URL, reachability unconfirmed | Keep serving | Re-check next run |
429 | Last-known-good | Honor Retry-After; reschedule |
| Invalid/malformed JSON | Prior valid artifact | Reject; record validation code only |
| URL/business mismatch | Prior valid artifact | Reject as security error; never cache |
| DB write failure | Prior valid artifact | Roll back; no purge |
| Cache purge failure | Origin new; cached HTML may be old | Report not-live; retry or request manual purge |
Theme lacks wp_head() | No injection | Site Health warning; no fallback rewriting |
| Plugin exception during output | Normal WordPress response | Emit nothing; sanitized error when debugging |
| WP-Cron delayed/disabled | Current artifact keeps serving | Overdue status; document system-cron/WP-CLI |
Stable error codes only — e.g. AIVIS_AUTH_401, AIVIS_HTTP_TIMEOUT, AIVIS_SCHEMA_INVALID, AIVIS_SCOPE_MISMATCH, AIVIS_RETRACTED, AIVIS_DB_WRITE, AIVIS_PURGE_UNSUPPORTED. Raw bodies and authorization headers are never logged.
13 · Security & privacy
manage_options (filterable) for all config/sync actions; nonces on every state change; sanitize on input, escape at output; prepared queries via WP DB APIs; no direct PHP file execution; no public unauthenticated connector REST endpoints (internal progress polling requires REST nonce + capability).Authorization header to the fixed host; redirects disabled; bearer-shaped strings redacted from exceptions; excluded from export/support tools; rotation + revocation documented. Preferred custody: AIVIS_API_TOKEN constant (wp-config or environment). DB fallback follows the WordPress security model and is described honestly — no fake "encryption" with a key stored beside the ciphertext.PRIVACY.md and a clear external-service disclosure in readme.txt.SECURITY.md with private disclosure + supported versions; dependency/static-analysis/secret/license scans in CI.14 · Plugin lifecycle
dbDelta() table create/upgrade; non-autoloaded defaultsuninstall.php / register_uninstall_hook()15 · Compatibility
wp_head().16 · Extension contract
apply_filters( 'aivis_connector_current_url', $url, $query_context );
apply_filters( 'aivis_connector_allowed_hosts', $hosts );
apply_filters( 'aivis_connector_manage_capability', 'manage_options' );
apply_filters( 'aivis_connector_sync_interval', $seconds );
do_action( 'aivis_connector_artifact_changed', $url, $old_hash, $new_hash );
do_action( 'aivis_connector_purge_urls', $urls, $context );
do_action( 'aivis_connector_sync_completed', $summary );
do_action( 'aivis_connector_sync_failed', $error_code );
wp aivis connection test · wp aivis sync --all · wp aivis status · wp aivis verify [--url=…].17 · Observability
wp_head()/live-marker verification; stale artifacts; unsupported/failed cache invalidation; DB schema health.18 · Open source & releases
aivis-wordpress-connector/
├── aivis-os.php bootstrap + plugin headers (incl. Update URI)
├── src/ {Admin, Api, Cache, Delivery, Domain, Security, Storage, Sync}
├── assets/ ├── languages/
├── docs/ {SPECIFICATION, ARCHITECTURE, CACHE-INTEGRATIONS, PRIVACY, RUNBOOK, THREAT-MODEL}.md
├── tests/ {Unit, Integration, EndToEnd}
├── .github/ {ISSUE_TEMPLATE, workflows, dependabot.yml}
├── composer.json · composer.lock · phpcs.xml.dist · phpunit.xml.dist
├── readme.txt · uninstall.php
└── CONTRIBUTING.md · SECURITY.md · CHANGELOG.md · LICENSE
Update URI header points at the canonical repo (prevents update-collision hijacks); v1 documents manual GitHub-release upgrades — WordPress.org distribution is a separate decision (Q-07).19 · Testing
| Suite | Key cases |
|---|---|
| API client | Valid /me; 401/403/404/429/5xx/DNS/TLS/timeout/oversize/redirect/wrong content type/malformed JSON; pagination past 200 items; no bearer forwarded on redirect; token absent from all exception/log output |
| Synchronization | Initial sync covers every ready distinct URL across chains; not-ready listed but not fetched; duplicates use /jsonld?url= winner; unchanged generatedAt ⇒ no refetch; unchanged hash ⇒ no purge; failed replacement preserves LKG; partial inventory never retires; R-02: two authoritative absences retire; jobs resume from cursors; lock prevents overlap + recovers |
| Retraction (added) | R-01: fetch-404 with confirmed reachability deactivates immediately + purges + records AIVIS_RETRACTED; 404 with unconfirmed reachability keeps serving; deactivated/retired rows never inject; a subsequent 200 for the same URL reactivates cleanly |
| Security | </script>/HTML-like strings stay encoded and non-executable; depth/UTF-8/scalar-root/oversize/timestamp/host/business/URL violations rejected; CSRF + capability enforcement; API base immutable via untrusted settings; token absent from page source, REST, notices, logs, support data, snapshots |
| Injection | One matching row ⇒ exactly one data-aivis="1" script in head (object + array roots); no row/disabled/404/feed/REST/sitemap/preview/admin ⇒ nothing; human vs crawler UA byte-identical; third-party JSON-LD untouched and non-suppressing; repeated wp_head cannot duplicate; zero remote requests on render |
| Cache / publication | Changed content ⇒ one deduplicated purge batch after commit; failed commit ⇒ no purge; clean_post_cache() without post updates; no revisions/modified-date/editorial hooks from sync; unsupported provider ⇒ "manual purge required", never "live"; enable/disable/deactivate purge behaviour; verification distinguishes stored / purge-requested / live |
| Compatibility / E2E | Supported WP/PHP matrix; pretty + plain permalinks; homepage/post/page/CPT/taxonomy/plugin route; www + trailing-slash + tracking-param + translated-domain aliases; with and without persistent object cache; ≥ 1 page-cache adapter + the unsupported path; release ZIP installs → activates → configures → syncs → injects → disables → deactivates → uninstalls cleanly |
20 · Acceptance criteria
| ID | Criterion |
|---|---|
| AC-01 | Valid token connects via /me; invalid token rejected without exposure |
| AC-02 | Admin selects a matching business and completes a paginated full sync |
| AC-03 | Every AIVIS-ready distinct URL has a valid active local artifact or an explicit per-URL error |
| AC-04 | Enabling site-wide injection changes no WP content records and creates no revisions |
| AC-05 | A matched public page contains exactly one safe data-aivis="1" script in <head> |
| AC-06 | A page without a ready artifact is unchanged |
| AC-07 | Existing non-AIVIS JSON-LD is neither inspected nor changed |
| AC-08 | Public requests make no AIVIS call and survive a complete AIVIS outage |
| AC-09 | Script-breakout payloads cannot create executable markup |
| AC-10 | Refresh failure preserves and serves last-known-good |
| AC-11 | Changed artifacts invalidate affected caches without republishing content |
| AC-12 | Unsupported cache infra reported as manual-purge-required — never falsely "live" |
| AC-13 | Identical markup for browser, Googlebot, GPTBot, and arbitrary user agents |
| AC-14 | Disable/deactivate + purge restores pre-connector output |
| AC-15 | No token, telemetry, IP, or raw UA in public output or diagnostics |
| AC-16 | Official release ZIP reproducibly built, checksummed, installable, matrix-green |
| AC-17 | Unpublishing in AIVIS deactivates on the next fetch-404 (R-01) with cache purge; the block does not resurrect from last-known-good during a subsequent outage |
21 · Milestones
wp_head injector; performance + fail-open tests; UA parity + zero-trust tests.22 · Analytics decision
AN-01 (decision): AI-crawler analytics is permanently excluded from the WordPress connector — not deferred. The developer's v1 exclusion reasons (no ingest endpoint yet; PHP-only visibility; minimal-scope priority) are correct, but the deeper problem decides it: a PHP plugin only sees cache-miss traffic, and AI crawlers overwhelmingly hit cached, popular pages. The sample isn't just incomplete — it is systematically biased. Selling the Monitoring subscription on skewed counts would contradict the Evidence & Monitoring positioning that is AIVIS's entire brand promise. A JS/pixel beacon can't fix it either: AI crawlers don't execute JavaScript.
The architecture answer — measure where every request is visible:
INJECT=off; its idempotency check detects the origin-shipped data-aivis block and records injected:"origin"). Delivery at origin, measurement at the edge — each layer does the one thing it can do truthfully.Appendix · Release decisions
| ID | Decision | Recommended default |
|---|---|---|
| Q-01 | Production AIVIS API base URL | Dedicated production hostname; never ship the dev hostname as release default |
| Q-02 | API token scope | Business-scoped read-only token (edge V-02 — treated as prerequisite for customer-managed installs, §00); until then, account token with strict business/host binding on AIVIS-controlled pilots only |
| Q-03 | Bundled cache adapters | Pick from actual pilot/customer infrastructure; core stays provider-neutral; manual purge always supported |
| Q-04 | Default freshness target | 15-minute polling; 5 minutes for high-freshness/retraction-sensitive managed sites with reliable system cron |
| Q-05 | Minimum platforms | WordPress 6.5+ · PHP 8.1+, subject to target-customer hosting survey |
| Q-06 | Multisite | Per-site only if fully test-covered; otherwise block network activation in 1.0 |
| Q-07 | Distribution beyond GitHub | GitHub Releases for v1; evaluate WordPress.org after pilot |
| Q-08 | Public repo coordinates | aivis-wordpress-connector under the org chosen in the shared edge Q-8 decision |