Files
peptide-repo-core/CHANGELOG.md
peptiderepo a1c77de332 feat: PRAB article JSON-LD emitter + meta reader (v0.8.0) (#25)
prcore emits MedicalWebPage+Article JSON-LD for any post carrying _prab_* meta (ratified contract v1): citation[], about->Drug @ids, honest reviewedBy (Person only with a real human reviewer, else Organization), lastReviewed; integrates into Yoast's graph (no duplicate nodes), standalone @graph fallback; all _prab_* sanitized as untrusted. Unblocks the Phase-2b SEO stage.

QA: REJECT-P1 (DoD doc/test gaps) -> APPROVE @ dbfc50c (qa-reviews/peptide-repo-core/2026-06-23-dbfc50c.md). CEO greenlight 2026-06-22 (complete-all-tasks directive).

Agent-Session: prcore-jsonld-merge-20260623
2026-06-23 12:29:14 +07:00

18 KiB

Changelog

All notable changes to Peptide Repo Core are documented here. Format: Semantic Versioning.

[0.8.0] — 2026-06-23

Added

  • PRAutoBlogger article JSON-LD emitter (ratified contract v1 — convo/prcore/decisions/2026-06-11-jsonld-contract-v1.md):
    • PR_Core_Jsonld_Article (frontend/class-pr-core-jsonld-article.php, ~235 lines) — meta-triggered emitter for post posts carrying _prab_schema_version=1. Hooks into Yoast's graph via wpseo_schema_webpage_type (priority 10) and wpseo_schema_graph (priority 13). Retypes the page node to MedicalWebPage; enriches the Yoast Article piece with citation[], about[] (Drug @id stubs), lastReviewed, and reviewedBy. Standalone @graph fallback (Article + MedicalWebPage) when Yoast is absent. Posts without _prab_schema_version meta are byte-unchanged.
    • PR_Core_Prab_Meta_Reader (frontend/class-pr-core-prab-meta-reader.php, ~268 lines) — reads and sanitises all contract-v1 _prab_* meta at read time (not trusting PRAB's write-time sanitization). URLs validated (http/https only), DOIs matched against canonical pattern, JSON arrays validated per-element, WP user ID resolved via get_userdata(). Malformed values degrade gracefully (field skipped) — no page-render fatals.
    • Honest reviewedBy: Person node emitted ONLY when _prab_review_mode=human AND _prab_reviewed_by resolves to an existing WP user. All other cases (editorial-system mode, missing/zero/nonexistent user ID) emit the Organization node ("Peptide Repo"). Never emits a fabricated person — the core contract guarantee.
    • about Drug linkage: _prab_about_peptides (JSON array of peptide post IDs) is resolved to Drug stub arrays using the stable {permalink}#drug @id established in v0.6.0. Each ID must resolve to a published peptide post; unresolvable IDs are skipped.
    • Citation nodes: ScholarlyArticle when DOI present (DOI normalised to https://doi.org/ URI as sameAs), else CreativeWork. quality_score never emitted (no schema.org mapping per contract v1).
    • No duplicate nodes: integrates via Yoast mutation (priority 13, after PR_Core_Jsonld priority-12 Drug/FAQ injection). Never adds new WebPage or Article pieces.
    • Unit tests: tests/unit/JsonldArticleTest.php — 22 assertions covering is_triggered, review_mode, reviewed_by (Person/Org decision tree), citation sanitization, about linkage, retype, enrich, no-new-nodes, untriggered passthrough, editorial-system-never-person.

Changed

  • class-pr-core.php: boots PR_Core_Jsonld_Article alongside PR_Core_Jsonld in init().
  • tests/bootstrap.php: enhanced get_userdata() stub to read from $GLOBALS['pr_test_userdata']; added WP_User class stub; initialised $GLOBALS['pr_test_userdata'] global.
  • ARCHITECTURE.md: JSON-LD class table updated with new classes; §2.7 PRAB article emission added.
  • CONVENTIONS.md: JSON-LD extension guide updated with PRAB-article hook pattern.

Technical

  • Meta keys _prab_* are owned by PRAutoBlogger namespace (PRAB registers/writes; prcore reads only). uninstall.php unaffected — prcore never writes _prab_* meta, so no teardown needed.
  • Yoast hook order: wpseo_schema_graph priority 11 (peptide MedicalWebPage enrich) → priority 12 (Drug/FAQ inject) → priority 13 (PRAB Article enrich + page retype). Three priorities prevent race conditions between the peptide and article enrichment paths.

[0.7.1] - 2026-06-17

Fixed

  • Critical: Settings page PHP Fatal Undefined constant PR_Core_Settings_Renderer::OPTION_GROUP (and 6 other self::CONST references). Root cause: PR #22 split the renderer into its own class but left all constants defined only on PR_Core_Settings. Fix: promote the 6 option-key private constants to public const on PR_Core_Settings; update renderer to reference them via PR_Core_Settings::*. Regression introduced in v0.7.0.

[0.7.0] - 2026-06-16

Changed

  • CI: adopt estate reusable workflow (thin caller, tests: stubs, has_js: false).
  • PHPUnit test suite: 7 hand-rolled test scripts converted to 7 TestCase classes.
  • Added phpunit/phpunit ^9.6, yoast/phpunit-polyfills ^2.0 to require-dev.
  • Added phpunit.xml.dist and composer test script.
  • deploy.yml: added permissions: contents: write.

[0.6.2] — 2026-06-13

Fixed

  • Critical: Peptide monograph pages (e.g. /peptides/bpc-157/) returned HTTP 500 after v0.6.0 launch. Root cause: PR_Core_Jsonld::inject_graph_pieces() appended plain PHP arrays (Drug, FAQ) to Yoast's wpseo_schema_graph_pieces filter. Yoast's Schema_Generator::filter_graph_pieces_to_generate() then called get_class() and is_needed() on every piece, expecting Abstract_Schema_Piece objects — a plain array causes PHP Fatal: get_class(): Argument #1 ($object) must be of type object, array given. Non-peptide pages (/, /about/, /peptides/ archive) were unaffected because the is_singular('peptide') guard prevented entry.
  • Fix: removed wpseo_schema_graph_pieces hook entirely. Drug and FAQPage nodes are now injected via a new inject_graph_nodes() method hooked on wpseo_schema_graph at priority 12, which receives Yoast's fully-assembled graph as an array of plain arrays — safe for plain array appends.
  • Added regression test tests/unit/test-jsonld-peptide-piece.php that reproduces the v0.6.1 get_class(array) fatal and verifies the v0.6.2 inject_graph_nodes path emits Drug + FAQ correctly across peptide, non-peptide, and empty-FAQ scenarios.

Changed

  • PR_Core_Jsonld::register_hooks(): no longer registers wpseo_schema_graph_pieces; registers wpseo_schema_graph at priority 12 for Drug/FAQ injection (in addition to priority 11 for enrich_webpage_piece, unchanged).
  • PR_Core_Jsonld::inject_graph_pieces() renamed to inject_graph_nodes(); signature updated from (array $pieces, $context) to (array $graph, $context) to reflect that it now receives the assembled graph, not the pieces array.

[0.6.1] — 2026-06-13

Fixed

  • Fix: Yoast wpseo_schema_webpage_type passes string|array on singulars — accept string|array in retype_to_medical_webpage() signature; return type updated to match (PHP 8.1+ union). Eliminates TypeError → HTTP 500 on all singular pages (monographs + plain Pages) under strict_types=1.
  • Fix: enrich_webpage_piece compared 'WebPage' === $piece['@type'] but Yoast sets @type as an array on singular pages. Normalise @type to array before in_array() check; lastReviewed, reviewedBy, and audience now inject correctly.
  • Add regression test tests/unit/test-jsonld-webpage.php: exercises retype_to_medical_webpage() with array input for peptide and non-peptide contexts (no TypeError), and enrich_webpage_piece with @type as array (enrichment injects). Tests fail against v0.6.0, pass after fix.

[0.6.0] — 2026-06-13

Added

  • Backfill migration 0004 (class-pr-core-migration-0004-backfill-peptide-meta.php): Copies PSA-stored PubChem data into new _pr_* schema-input meta keys across all published peptide posts. Source: psa_molecular_formula → _pr_molecular_formula, psa_molecular_weight → _pr_molecular_weight (strip "Da", floatval), psa_aliases → _pr_aliases (comma-split, sanitize, JSON array). For 4 posts without PSA data (igf-1-lr3, cagrilintide, ghk-cu, epitalon), falls back to PubChem REST API via new PR_Core_Pubchem_Client. Migration is idempotent + re-runnable; skips posts where all three keys are already populated.
  • Schema-input meta keys (registered in CPT, v0.6.0):
    • _pr_molecular_formula — sanitized molecular formula string.
    • _pr_molecular_weight — float as string (g/mol); "Da" suffix normalized out.
    • _pr_aliases — JSON array of sanitized alternate names.
    • _pr_faq_items — JSON array of {question, answer} objects for FAQPage emission.
  • PR_Core_Schema_Sanitizers (cpt/class-pr-core-schema-sanitizers.php): Static sanitizers for the four schema-input meta keys, split from PR_Core_Peptide_CPT to keep the CPT file under 300 lines.
  • PR_Core_Pubchem_Client (migrations/class-pr-core-pubchem-client.php): Lightweight PubChem REST client with CID/name lookup, synonym fetch, and MAX_RETRIES=3 exponential backoff. Used exclusively by migration 0004.
  • Yoast-integrated JSON-LD emission (replaces standalone wp_head script):
    • PR_Core_Jsonld_Drug — builds Drug (+ MolecularEntity) schema node with stable @id = {permalink}#drug, reads _pr_* schema-input meta, alternateName/molecularFormula/molecularWeight (g/mol) emitted only when non-empty.
    • PR_Core_Jsonld_Webpage — retypes Yoast's WebPage to MedicalWebPage via wpseo_schema_webpage_type; enriches it with lastReviewed, reviewedBy (Person or Organization), and audience via wpseo_schema_graph.
    • PR_Core_Jsonld_Faq — builds FAQPage node from _pr_faq_items; emitted only when items exist.
    • PR_Core_Jsonld — orchestrator hooking all three builders into Yoast's graph; maintains standalone @graph fallback when Yoast is inactive.
  • CONTEXT.md — new domain glossary covering all new terms (_pr_* keys, Drug node, MedicalWebPage, FAQPage, Yoast integration contract, PubChem CID).

Changed

  • PR_Core_Jsonld rewritten: replaces standalone wp_head script-tag emission with Yoast graph-filter integration. Standalone @graph retained as fallback only. No duplicate WebPage/BreadcrumbList — Yoast owns those.
  • PR_Core_Peptide_CPT::get_meta_fields() — adds 4 new _pr_* fields; existing aliases/molecular_formula/molecular_weight fields unchanged.
  • PR_Core_Migration_Runner::MIGRATIONS — adds PR_Core_Migration_0004_Backfill_Peptide_Meta at index 3.
  • PR_CORE_TARGET_SCHEMA_VERSION bumped from 3 to 4.
  • uninstall.php — purges _pr_molecular_formula, _pr_molecular_weight, _pr_aliases, _pr_faq_items from all peptide posts on uninstall.
  • ARCHITECTURE.md — §#6 JSON-LD rewritten; §2.9 uninstall spec updated; file tree updated for new classes.
  • CONVENTIONS.md — schema-input meta convention added; Yoast JSON-LD extension pattern added.

Fixed

  • tests/unit/test-migration-0004.php: error_log() stub now guarded with function_exists() to prevent PHP fatal redeclaration error in CI PHP Lint (8.1/8.2/8.3).
  • tests/bootstrap.php: wp_json_encode stub updated to accept int $flags = 0 second argument, matching production call signature; guarded with function_exists().
  • cpt/class-pr-core-schema-sanitizers.php: sanitize_molecular_formula() rewritten from character-allowlist to element-formula grammar validation (Option B). Now strips control characters first, then extracts the maximal leading run of valid [A-Z][a-z]?\d* element tokens and group separators. Inputs containing trailing English words (e.g., "C10H12\ninjection") correctly return the formula prefix only ("C10H12"); fully invalid inputs (e.g., "<script>alert(1)</script>") return "". Parenthesised formulas (e.g., "C2H5(OH)") preserved unchanged. Seven-case unit test coverage added.

Technical

  • Schema version: PR_CORE_TARGET_SCHEMA_VERSION = 4.
  • New test files: tests/unit/test-migration-0004.php, tests/unit/test-jsonld.php.
  • Yoast filter names verified against Yoast 27.6: wpseo_schema_graph_pieces, wpseo_schema_webpage_type, wpseo_schema_graph.
  • No CAS/DrugBank emission in v0.6.0 (no reliable source — documented in ARCHITECTURE.md for future enrichment sprint).

[0.5.0] — 2026-04-27

Added

  • Repo Daily CPT: New repo_daily custom post type at /daily/[slug]/ for editorial publications (articles, guides, comparisons, news) generated by PRAutoBlogger and published under the Boo Sheeran byline.
  • Repo Daily Category Taxonomy: Non-hierarchical repo_daily_category taxonomy with four seeded terms (article, guide, comparison, news) for editorial classification.
  • Repo Daily Meta Fields:
    • _repo_daily_author (string, default "Boo Sheeran") — byline displayed in theme templates. Never hardcoded; always read from meta.
    • _repo_daily_clinical_review_required (bool, stored as '1'/'') — editorial flag marking posts that require PR Clinical Review before publication.
  • Repo Daily Admin Meta Box: "Article Settings" meta box on repo_daily edit screen with author byline field and clinical review flag checkbox.
  • Bulk Author Migration: On activation, sets _repo_daily_author = 'Boo Sheeran' on all post_type=post posts bearing the psa_source meta key (autoblogged articles). Idempotent; existing meta values preserved. Posts remain post_type=post; forward-only migration for byline retroactivity.

Changed

  • PR_Core_Admin::register_hooks() now registers repo_daily metaboxes alongside peptide metaboxes.

Technical

  • New classes: PR_Core_Repo_Daily_CPT, PR_Core_Repo_Daily_Taxonomy, PR_Core_Repo_Daily_Metaboxes.
  • New method: PR_Core_Activator::migrate_autoblogged_author_meta() — idempotent bulk meta setter.
  • Taxonomy seeding: PR_Core_Repo_Daily_Taxonomy::seed_terms() called on activation.

Documentation

  • ARCHITECTURE.md: Added repo_daily CPT to content-type table; documented scanner exclusion by CPT separation.
  • CONVENTIONS.md: Added "How to add a new post meta field to repo_daily posts" extension pattern.

[0.4.0] — 2026-04-27

Added

  • Verification Scanner: Automated periodic scanning of all published peptides to compute verification status (current/due/overdue) based on days since last verification and configurable velocity-based thresholds.
  • Verification Settings: New admin settings page under Peptides menu with configuration for scan cadence (daily/weekly/monthly), staleness thresholds (default 180 days, high-velocity 60 days, low-velocity 365 days), and notification email recipients.
  • Dashboard Widget: "Monographs Needing Review" dashboard widget showing all peptides due or overdue for verification, sorted by staleness, with direct edit links and one-click scan trigger.
  • Editor Sidebar Meta Box: New "Verification Status" sidebar meta box on peptide edit screen showing last-verified date, velocity selector, status badge, and one-click "Mark Verified Today" button that sets verification date to current time and recomputes status.
  • Admin AJAX Handlers: Two new admin-ajax actions:
    • pr_core_mark_verified: Sets _pr_last_source_verified to today, saves optional notes, recomputes status.
    • pr_core_scan_now: Runs verification scanner immediately from the dashboard widget.
  • Frontend Verification Display: On single-peptide pages, displays "Last verified: [date] — methodology" text after the verdict card div (requires non-empty _pr_last_source_verified meta field).
  • Verification Meta Fields (registered in CPT):
    • _pr_last_source_verified: ISO datetime of most recent source verification.
    • _pr_last_reviewed: ISO datetime of editorial review (phase 2 integration point).
    • _pr_next_review_by: ISO datetime of next scheduled review (phase 2 integration point).
    • _pr_verification_velocity: Enum (low/medium/high) controlling threshold application.
    • _pr_verification_notes: Textarea for reviewer notes on the most recent verification pass.
    • _pr_verification_status: Enum (current/due/overdue) computed by scanner; drives widget sorting and badge color.
  • Scan Log: Option pr_core_verification_scan_log stores last 90 scan summaries (timestamp, total count, due count, overdue count) for audit trail and health monitoring.
  • Email Digest: When scans detect due/overdue peptides, a digest email is sent to configured recipients (default: none, must opt-in) with links to edit each peptide and the verification dashboard.

Changed

  • CPT meta field registration now includes 6 new verification fields alongside existing dosing/legal/editorial fields.

Technical

  • New classes: PR_Core_Verification_Scanner, PR_Core_Settings, PR_Core_Verification_Widget, PR_Core_Verification_Display, PR_Core_Ajax_Handlers.
  • New unit tests: test-verification-scanner.php (status computation logic), test-verification-settings.php (cadence/cron scheduling).
  • WordPress cron integration: pr_core_verification_scan hook scheduled at activation, cleared at deactivation, rescheduled when cadence changes.

[0.2.2] — 2026-04-25

Fixed

  • Remove rest_namespace from peptide CPT registration. Custom namespace prevented Gutenberg block editor from loading peptide posts for editing (404 on wp/v2 REST route). All 89 peptide entries are now editable in the block editor.

[0.2.1] — 2026-04-22

Fixed

  • P1 hotfix: fatal Undefined constant PR_Core_Peptide_CPT::TAX_FAMILY on every peptide page. The v0.2.0 release removed TAX_FAMILY from PR_Core_Peptide_CPT but left two stale references in PR_Core_Peptide_Repository (the family filter branch in find_all() and the families lookup in post_to_dto()). JSON-LD emission on single-peptide templates called find_by_id(), which called post_to_dto(), which hit the undefined constant and killed page rendering mid-wp_head(). QA gate on 874f93b missed this because the unit tests cover the CPT class in isolation, not the repository's consumption of its constants. Post-mortem + QA checklist update to follow.

Changed

  • PR_Core_Peptide_Repository::find_all() — family filter is now silently ignored rather than throwing. Next minor bump will remove it from the REST schema.
  • PR_Core_Peptide_DTO::$families is preserved but always populated as []. Keeps REST response shape stable for existing clients; will be dropped with a release note.

[0.2.0] — 2026-04-22

Changed (BREAKING)

  • CPT renamed from pr_peptide to peptide. Consolidates with PSA's existing peptide CPT which owns the 89 canonical peptide posts on production.
  • Taxonomy renamed from pr_peptide_category to peptide_category. Same reason.
  • CPT args harmonized as superset of PSA + prior PR Core registration (supports now includes thumbnail + revisions + custom-fields).
  • Defensive registration: guards with post_type_exists() / taxonomy_exists() so PSA's parallel registration no-ops cleanly during the PSA v4.5.0 transition.

Added

  • PR_Core_Activator::maybe_flush_on_version_change() — one-shot rewrite flush on in-place version bumps (hooked at init priority 999). Eliminates the need for a manual wp rewrite flush after updates that change CPT/taxonomy slugs.
  • _pr_core_authored post-meta flag contract — peptide posts created via PR Core UI carry this flag; uninstall.php uses it to scope teardown to plugin-owned posts only.