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
18 KiB
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 forpostposts carrying_prab_schema_version=1. Hooks into Yoast's graph viawpseo_schema_webpage_type(priority 10) andwpseo_schema_graph(priority 13). Retypes the page node toMedicalWebPage; enriches the YoastArticlepiece withcitation[],about[](Drug@idstubs),lastReviewed, andreviewedBy. Standalone@graphfallback (Article + MedicalWebPage) when Yoast is absent. Posts without_prab_schema_versionmeta 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 viaget_userdata(). Malformed values degrade gracefully (field skipped) — no page-render fatals.- Honest
reviewedBy: Person node emitted ONLY when_prab_review_mode=humanAND_prab_reviewed_byresolves 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. aboutDrug 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 publishedpeptidepost; unresolvable IDs are skipped.- Citation nodes:
ScholarlyArticlewhen DOI present (DOI normalised to https://doi.org/ URI assameAs), elseCreativeWork.quality_scorenever 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: bootsPR_Core_Jsonld_ArticlealongsidePR_Core_Jsonldininit().tests/bootstrap.php: enhancedget_userdata()stub to read from$GLOBALS['pr_test_userdata']; addedWP_Userclass 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.phpunaffected — prcore never writes_prab_*meta, so no teardown needed. - Yoast hook order:
wpseo_schema_graphpriority 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 otherself::CONSTreferences). Root cause: PR #22 split the renderer into its own class but left all constants defined only onPR_Core_Settings. Fix: promote the 6 option-key private constants topublic constonPR_Core_Settings; update renderer to reference them viaPR_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'swpseo_schema_graph_piecesfilter. Yoast'sSchema_Generator::filter_graph_pieces_to_generate()then calledget_class()andis_needed()on every piece, expectingAbstract_Schema_Pieceobjects — a plain array causesPHP Fatal: get_class(): Argument #1 ($object) must be of type object, array given. Non-peptide pages (/, /about/, /peptides/ archive) were unaffected because theis_singular('peptide')guard prevented entry. - Fix: removed
wpseo_schema_graph_pieceshook entirely. Drug and FAQPage nodes are now injected via a newinject_graph_nodes()method hooked onwpseo_schema_graphat 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.phpthat reproduces the v0.6.1get_class(array)fatal and verifies the v0.6.2inject_graph_nodespath emits Drug + FAQ correctly across peptide, non-peptide, and empty-FAQ scenarios.
Changed
PR_Core_Jsonld::register_hooks(): no longer registerswpseo_schema_graph_pieces; registerswpseo_schema_graphat priority 12 for Drug/FAQ injection (in addition to priority 11 forenrich_webpage_piece, unchanged).PR_Core_Jsonld::inject_graph_pieces()renamed toinject_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_typepassesstring|arrayon singulars — acceptstring|arrayinretype_to_medical_webpage()signature; return type updated to match (PHP 8.1+ union). EliminatesTypeError→ HTTP 500 on all singular pages (monographs + plain Pages) understrict_types=1. - Fix:
enrich_webpage_piececompared'WebPage' === $piece['@type']but Yoast sets@typeas an array on singular pages. Normalise@typeto array beforein_array()check;lastReviewed,reviewedBy, andaudiencenow inject correctly. - Add regression test
tests/unit/test-jsonld-webpage.php: exercisesretype_to_medical_webpage()with array input for peptide and non-peptide contexts (no TypeError), andenrich_webpage_piecewith@typeas 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 newPR_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 fromPR_Core_Peptide_CPTto 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_headscript):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 toMedicalWebPageviawpseo_schema_webpage_type; enriches it withlastReviewed,reviewedBy(Person or Organization), andaudienceviawpseo_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@graphfallback 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_Jsonldrewritten: replaces standalonewp_headscript-tag emission with Yoast graph-filter integration. Standalone@graphretained as fallback only. No duplicate WebPage/BreadcrumbList — Yoast owns those.PR_Core_Peptide_CPT::get_meta_fields()— adds 4 new_pr_*fields; existingaliases/molecular_formula/molecular_weightfields unchanged.PR_Core_Migration_Runner::MIGRATIONS— addsPR_Core_Migration_0004_Backfill_Peptide_Metaat index 3.PR_CORE_TARGET_SCHEMA_VERSIONbumped from 3 to 4.uninstall.php— purges_pr_molecular_formula,_pr_molecular_weight,_pr_aliases,_pr_faq_itemsfrom 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 withfunction_exists()to prevent PHP fatal redeclaration error in CI PHP Lint (8.1/8.2/8.3).tests/bootstrap.php:wp_json_encodestub updated to acceptint $flags = 0second argument, matching production call signature; guarded withfunction_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_dailycustom 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_categorytaxonomy 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_verifiedto 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_verifiedmeta 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_logstores 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_scanhook scheduled at activation, cleared at deactivation, rescheduled when cadence changes.
[0.2.2] — 2026-04-25
Fixed
- Remove
rest_namespacefrom 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_FAMILYon every peptide page. The v0.2.0 release removedTAX_FAMILYfromPR_Core_Peptide_CPTbut left two stale references inPR_Core_Peptide_Repository(thefamilyfilter branch infind_all()and the families lookup inpost_to_dto()). JSON-LD emission on single-peptide templates calledfind_by_id(), which calledpost_to_dto(), which hit the undefined constant and killed page rendering mid-wp_head(). QA gate on874f93bmissed 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()—familyfilter is now silently ignored rather than throwing. Next minor bump will remove it from the REST schema.PR_Core_Peptide_DTO::$familiesis 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_peptidetopeptide. Consolidates with PSA's existingpeptideCPT which owns the 89 canonical peptide posts on production. - Taxonomy renamed from
pr_peptide_categorytopeptide_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 atinitpriority 999). Eliminates the need for a manualwp rewrite flushafter updates that change CPT/taxonomy slugs._pr_core_authoredpost-meta flag contract — peptide posts created via PR Core UI carry this flag;uninstall.phpuses it to scope teardown to plugin-owned posts only.