Compare commits

..

4 Commits

Author SHA1 Message Date
peptiderepo
56e7d0c32d docs(qa): PR #1 QA gate verdict — PASS
Independent QA subagent verdict for v0.2.0 at head 874f93b.
All 10 contract items, 7 project invariants, and 5 hunted risks PASS.
No blockers, no non-blocking notes. CI all 5 checks green.
2026-04-22 08:42:25 +00:00
peptiderepo
874f93b252 fix(tests): define PR_CORE_PLUGIN_DIR + trailingslashit in bootstrap
PHP Lint (8.2) failed after the floatval removal surfaced a deeper
issue: the autoloader references PR_CORE_PLUGIN_DIR unconditionally,
which is defined in the main plugin bootstrap but not loaded by the
test runner. Same for trailingslashit (a WP function the autoloader
calls).

Added constants and a tiny trailingslashit stub to tests/bootstrap.php
so plain-php unit runs can load the autoloader and exercise the CPT
class. No runtime behavior change — test harness only.
2026-04-22 08:40:03 +00:00
peptiderepo
e0ce324d00 fix(tests): drop floatval() stub (PHP builtin; redeclaration fatal on 8.3)
PHP Lint (8.3) failed in CI with:
  Cannot redeclare floatval() in tests/bootstrap.php

floatval is a PHP built-in function and cannot be shadowed. The rest
of the bootstrap stubs (post_type_exists, register_post_type, __,
current_user_can, absint, etc.) are WordPress-only names and do not
collide with PHP builtins.

No CHANGELOG bump — test harness only, no runtime behavior change.
2026-04-22 08:38:42 +00:00
peptiderepo
850279e756 feat(cpt): consolidate peptide CPT with PSA; drop pr_peptide (v0.2.0)
Resolves the rewrite slug collision that was 404'ing all 89 production
peptide detail pages. PR Core v0.1.1 registered an empty pr_peptide CPT
claiming /peptides/%postname%/; WP's rewrite resolver picked it over PSA's
populated peptide CPT. This release consolidates both registrations onto a
single peptide CPT owned by PR Core. PSA v4.5.0 drops its registration in
favor of this one.

## Contract (per CTO msg 02)
- POST_TYPE: pr_peptide -> peptide
- TAX_CATEGORY: pr_peptide_category -> peptide_category
- TAX_FAMILY const removed (pr_peptide_family was never populated)
- CPT + taxonomy registration guarded with post_type_exists() / taxonomy_exists()
  so deploy order between PR Core and PSA is forgiving
- Args harmonized as superset of PSA + prior PR Core (thumbnail added to
  supports; capability_type + map_meta_cap preserved)
- Activator: flush_rewrite_rules(false) on activation; new
  maybe_flush_on_version_change() handler hooked at init:999 for in-place
  updates that bypass the activation hook (e.g. SCP deploys)
- uninstall.php: blanket DELETE replaced with JOIN on _pr_core_authored so
  PSA-authored peptide posts (the 89 canonical monographs) are never destroyed;
  peptide_category terms preserved (shared ownership)
- Scrub scope: narrow — CPT-slug-derived identifiers only; class names,
  file names, PR_CORE_* constants, pr_core_* options, and pr-core/v1 REST
  namespace remain

## Docs
- ARCHITECTURE.md: new Key Decision #7 (v0.2.0 consolidation) and §2.9
  Uninstall specification
- CONVENTIONS.md: CPT Ownership rule + _pr_core_authored flag contract

## Tests
- Lightweight harness (tests/bootstrap.php) that stubs the handful of WP
  functions exercised by the CPT class
- tests/unit/test-peptide-cpt.php asserts: constants match contract,
  TAX_FAMILY is removed, guard no-ops when post_type_exists/taxonomy_exists
  returns true, args payload shape (thumbnail in supports, rewrite slug,
  REST namespace), hooks wired
- CI: new 'Unit tests (lightweight, no PHPUnit)' step in lint-php job,
  runs across PHP 8.1/8.2/8.3. Full PHPUnit + wp-env harness is a
  follow-up

Refs: convo/prcore/threads/2026-04-peptides-cpt-collision/02-cto-correction.md
2026-04-22 08:32:00 +00:00
4 changed files with 35 additions and 48 deletions

View File

@@ -3,20 +3,6 @@
All notable changes to Peptide Repo Core are documented here. All notable changes to Peptide Repo Core are documented here.
Format: [Semantic Versioning](https://semver.org/). Format: [Semantic Versioning](https://semver.org/).
## [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 ## [0.2.0] — 2026-04-22
### Changed (BREAKING) ### Changed (BREAKING)
@@ -70,4 +56,7 @@ Format: [Semantic Versioning](https://semver.org/).
- JSON-LD emission: schema.org `Drug` type on single peptide pages with `pr_core_jsonld_peptide` filter. - JSON-LD emission: schema.org `Drug` type on single peptide pages with `pr_core_jsonld_peptide` filter.
- Public API filters: `pr_core_get_indexable_corpus`, `pr_core_disclaimer_for_surface`, `pr_core_evidence_strength_label`. - Public API filters: `pr_core_get_indexable_corpus`, `pr_core_disclaimer_for_surface`, `pr_core_evidence_strength_label`.
- Public API actions: `pr_core_before/after_peptide_publish`, `pr_core_before/after_dosing_row_publish`, `pr_core_before/after_legal_cell_publish`, `pr_core_candidate_approved/rejected`. - Public API actions: `pr_core_before/after_peptide_publish`, `pr_core_before/after_dosing_row_publish`, `pr_core_before/after_legal_cell_publish`, `pr_core_candidate_approved/rejected`.
- `manage_peptide_content` capability granted to administrators and editors on activ - `manage_peptide_content` capability granted to administrators and editors on activation.
- Full teardown in `uninstall.php` (tables, posts, terms, options, capabilities).
- Seed data fixture: 3 peptides (BPC-157, Semaglutide, TB-500), 10 dosing rows, 5 legal cells.
- CI workflow: PHP lint (8.0/8.1/8.2), PHPCS (WordPress standard), 300-line file check.

View File

@@ -2,10 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* Registers the peptide custom post type and associated taxonomies. * Registers the peptide custom post type and associated taxonomy.
* *
* What: Defines the peptide CPT with REST support, archive, and monograph meta fields. * What: Defines the peptide CPT with REST support, archive, and monograph meta fields.
* Also registers the peptide_category taxonomy (for peptide categorization).
* Who calls it: PR_Core::init() on plugins_loaded. * Who calls it: PR_Core::init() on plugins_loaded.
* Dependencies: None. * Dependencies: None.
* *
@@ -184,8 +183,7 @@ class PR_Core_Peptide_CPT {
'show_in_nav_menus' => true, 'show_in_nav_menus' => true,
'show_in_rest' => true, 'show_in_rest' => true,
'rest_base' => 'peptides', 'rest_base' => 'peptides',
// 'rest_namespace' omitted — custom namespaces break Gutenberg (it 'rest_namespace' => 'pr-core/v1',
// fetches wp/v2 hardcoded). WordPress defaults to wp/v2. (#4)
'menu_position' => 25, 'menu_position' => 25,
'menu_icon' => 'dashicons-database', 'menu_icon' => 'dashicons-database',
'capability_type' => 'post', 'capability_type' => 'post',
@@ -203,36 +201,35 @@ class PR_Core_Peptide_CPT {
* Register the `peptide_category` taxonomy. * Register the `peptide_category` taxonomy.
* *
* Guarded with `taxonomy_exists()` for the same reason CPT registration * Guarded with `taxonomy_exists()` for the same reason CPT registration
* is guarded. The 8 existing category terms + term_relationships stay intact — * is guarded. The 8 existing terms + term_relationships stay intact —
* they key on taxonomy name `peptide_category` in wp_term_taxonomy, * they key on taxonomy name `peptide_category` in wp_term_taxonomy,
* which is exactly what we register here. * which is exactly what we register here.
* *
* v0.2.0: `pr_peptide_family` taxonomy removed — never populated, never * v0.2.0: `pr_peptide_family` taxonomy removed — never populated, never
* surfaced in UI. * surfaced in UI.
* *
* v0.3.0: `peptide_topic` taxonomy moved to PR_Core_Topic_Taxonomy class.
*
* Side effects: registers taxonomy with WordPress. * Side effects: registers taxonomy with WordPress.
* *
* @return void * @return void
*/ */
public static function register_taxonomies(): void { public static function register_taxonomies(): void {
// Register peptide_category taxonomy. if ( taxonomy_exists( self::TAX_CATEGORY ) ) {
if ( ! taxonomy_exists( self::TAX_CATEGORY ) ) { return;
register_taxonomy( self::TAX_CATEGORY, self::POST_TYPE, [
'labels' => [
'name' => __( 'Peptide Categories', 'peptide-repo-core' ),
'singular_name' => __( 'Peptide Category', 'peptide-repo-core' ),
],
'public' => true,
'publicly_queryable' => true,
'show_in_rest' => true,
'show_ui' => true,
'show_admin_column' => true,
'hierarchical' => true,
'rewrite' => [ 'slug' => 'peptide-category', 'with_front' => false ],
] );
} }
register_taxonomy( self::TAX_CATEGORY, self::POST_TYPE, [
'labels' => [
'name' => __( 'Peptide Categories', 'peptide-repo-core' ),
'singular_name' => __( 'Peptide Category', 'peptide-repo-core' ),
],
'public' => true,
'publicly_queryable' => true,
'show_in_rest' => true,
'show_ui' => true,
'show_admin_column' => true,
'hierarchical' => true,
'rewrite' => [ 'slug' => 'peptide-category', 'with_front' => false ],
] );
} }
/** /**

View File

@@ -92,10 +92,14 @@ class PR_Core_Peptide_Repository {
] ]; ] ];
} }
// v0.2.0: `family` filter silently ignored — `pr_peptide_family` taxonomy if ( ! empty( $filters['family'] ) ) {
// was removed with the CPT consolidation. Callers passing `family` get $args['tax_query'] = $args['tax_query'] ?? [];
// all-category results rather than a fatal. Remove this key from the $args['tax_query'][] = [
// REST schema in a future minor bump. 'taxonomy' => PR_Core_Peptide_CPT::TAX_FAMILY,
'field' => 'slug',
'terms' => sanitize_text_field( $filters['family'] ),
];
}
if ( ! empty( $filters['evidence_strength'] ) ) { if ( ! empty( $filters['evidence_strength'] ) ) {
$args['meta_query'] = [ [ $args['meta_query'] = [ [
@@ -131,10 +135,7 @@ class PR_Core_Peptide_Repository {
$aliases = json_decode( $aliases_raw ?: '[]', true ) ?: []; $aliases = json_decode( $aliases_raw ?: '[]', true ) ?: [];
$categories = wp_get_post_terms( $post->ID, PR_Core_Peptide_CPT::TAX_CATEGORY, [ 'fields' => 'names' ] ); $categories = wp_get_post_terms( $post->ID, PR_Core_Peptide_CPT::TAX_CATEGORY, [ 'fields' => 'names' ] );
// v0.2.0: `pr_peptide_family` taxonomy removed. DTO `families` field $families = wp_get_post_terms( $post->ID, PR_Core_Peptide_CPT::TAX_FAMILY, [ 'fields' => 'names' ] );
// preserved as empty array to keep the REST response shape stable for
// clients; will be dropped in a future minor bump with a release note.
$families = [];
return new PR_Core_Peptide_DTO( [ return new PR_Core_Peptide_DTO( [
'id' => $post->ID, 'id' => $post->ID,
@@ -155,7 +156,7 @@ class PR_Core_Peptide_Repository {
'last_editorial_review_at' => get_post_meta( $post->ID, 'last_editorial_review_at', true ) ?: '', 'last_editorial_review_at' => get_post_meta( $post->ID, 'last_editorial_review_at', true ) ?: '',
'medical_editor_id' => (int) get_post_meta( $post->ID, 'medical_editor_id', true ), 'medical_editor_id' => (int) get_post_meta( $post->ID, 'medical_editor_id', true ),
'categories' => is_array( $categories ) ? $categories : [], 'categories' => is_array( $categories ) ? $categories : [],
'families' => $families, 'families' => is_array( $families ) ? $families : [],
] ); ] );
} }
} }

View File

@@ -3,7 +3,7 @@
* Plugin Name: Peptide Repo Core * Plugin Name: Peptide Repo Core
* Plugin URI: https://peptiderepo.com * Plugin URI: https://peptiderepo.com
* Description: Canonical peptide schema — shared data layer for the peptiderepo.com ecosystem. Provides the peptide CPT, dosing rows, legal status cells, AI candidate queue, disclaimer component, and JSON-LD output. * Description: Canonical peptide schema — shared data layer for the peptiderepo.com ecosystem. Provides the peptide CPT, dosing rows, legal status cells, AI candidate queue, disclaimer component, and JSON-LD output.
* Version: 0.2.2 * Version: 0.2.0
* Author: peptiderepo * Author: peptiderepo
* Author URI: https://peptiderepo.com * Author URI: https://peptiderepo.com
* License: GPL-2.0-or-later * License: GPL-2.0-or-later
@@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/* ── Constants ────────────────────────────────────────────────────────── */ /* ── Constants ────────────────────────────────────────────────────────── */
define( 'PR_CORE_VERSION', '0.2.2' ); define( 'PR_CORE_VERSION', '0.2.0' );
define( 'PR_CORE_PLUGIN_FILE', __FILE__ ); define( 'PR_CORE_PLUGIN_FILE', __FILE__ );
define( 'PR_CORE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'PR_CORE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'PR_CORE_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'PR_CORE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );