Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
80d42c3
native: support bounded SQL metadata reads
chubes4 Sep 9, 2026
42081b8
native: match bounded shadow SQL forms
chubes4 Sep 9, 2026
63804ef
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
8f5d577
native: support tableless scalar projections
chubes4 Sep 9, 2026
9b64bdf
test: trace native shadow runtime phases
chubes4 Sep 9, 2026
abbaf14
test: expose native shadow trace path
chubes4 Sep 9, 2026
387d202
test: keep shadow tracing non-blocking
chubes4 Sep 9, 2026
c4f7cdb
fix(native): preserve catalog predicate semantics
chubes4 Sep 9, 2026
bb7cf5e
Merge branch 'main' into fix/mdi-parity-sql-families-20260909
chubes4 Sep 9, 2026
52860b1
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
89e739e
fix(native): bound reviewed SQL semantics
chubes4 Sep 9, 2026
610c39e
fix(native): match MySQL JSON depth
chubes4 Sep 9, 2026
2a0aea3
test(shadow): retain token type traces
chubes4 Sep 9, 2026
26a7fae
fix(shadow): keep runtime trace optional
chubes4 Sep 9, 2026
6cfcf4b
fix(shadow): capture tableless and catalog reads
chubes4 Sep 9, 2026
bc71a84
fix(shadow): retain snapshot capture evidence
chubes4 Sep 9, 2026
28ab023
test(shadow): retain redacted mismatch receipts
chubes4 Sep 9, 2026
c316a46
fix(native): match MySQL field descriptors
chubes4 Sep 9, 2026
086d08d
fix(shadow): bind authoritative catalog schema
chubes4 Sep 9, 2026
577b4d9
test(shadow): retain catalog schema receipts
chubes4 Sep 9, 2026
f6dbd61
test(shadow): retain snapshot provenance
chubes4 Sep 9, 2026
f39abfa
fix(native): match catalog column ordering
chubes4 Sep 9, 2026
8cf423c
fix(shadow): compare unordered catalog rows as bags
chubes4 Sep 9, 2026
3d946ef
test(shadow): retain catalog lifecycle receipt
chubes4 Sep 9, 2026
ea40bc0
fix(shadow): exclude temporary catalog tables
chubes4 Sep 9, 2026
0d66361
fix(shadow): capture permanent catalog schemas
chubes4 Sep 9, 2026
c181939
fix(shadow): atomically bound trace output
chubes4 Sep 9, 2026
5b065e7
Merge branch main into fix/mdi-parity-sql-families-20260909
chubes4 Sep 9, 2026
99e032b
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
9d87378
fix(native): bound post title lookups
chubes4 Sep 9, 2026
b85dfe1
fix(native): cap title lookup source work
chubes4 Sep 9, 2026
f3c9768
fix(native): migrate legacy multisite user snapshots
chubes4 Sep 9, 2026
471d8cd
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
a5b28d8
fix(native): support bounded not-equal table deletes
chubes4 Sep 9, 2026
afad2b7
fix(native): implicitly commit table DDL
chubes4 Sep 9, 2026
548042c
test(native): cover event date schema DDL
chubes4 Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions REMAINING_102_GAPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Remaining Native Consumer Gaps

The exact full DME1053 pair at candidate `99e032be6c5cf65b85a64ce863eb0e0065084682` recorded native 942 passed, 70 failures, 32 errors, and 9 skipped. The MySQL control recorded 1,022 passed, 24 errors, and 7 skipped.

## Raw-Diagnostic Classification

- The MySQL control errors are WP_CLI bootstrap failures and must remain separate from native engine parity.
- Native errors include the same missing WP_CLI class plus three physical `mysqli` root-access failures in `VenueProfileMutationsTest`; neither proves a native SQL mismatch.
- Native assertion failures include harness/application state differences such as user initialization and the physical-`mysqli` expectation in `WordPressLifecycleTest`.
- A repeated native query symptom is empty event candidate sets in `EventDateQueryAbilitiesTest` and duplicate/upsert paths. The posts schema did not classify exact `post_title` predicates as lookups. This branch supports ASCII case-insensitive, trailing-space-padded `=` and `IN` comparisons, with non-ASCII values failing closed. A title lookup without a reusable scoped snapshot explicitly fails after 1,024 canonical source files; ASCII validation is a collation constraint, not a scan-cost bound.

## Still Unresolved

- Full Unicode MySQL collation semantics for title lookups remain unsupported.
- Title lookups over larger uncached canonical corpora require a reusable source index before they can execute without the explicit 1,024-file work limit.
- Physical `mysqli` and WP_CLI-dependent tests require separate Codebox/bootstrap ownership.
- Transaction semantics require a dedicated end-to-end framework repair; reporting an InnoDB engine string alone would not supply them.
- The remaining native assertions need paired, per-test diagnosis after this focused repair; aggregate full-suite counts are not parity evidence.
6 changes: 6 additions & 0 deletions db.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ function markdown_database_integration_native_plugin_dir( string $content_dir ):
define( 'MARKDOWN_DB_NATIVE_SHADOW_REPORT_PATH', $markdown_db_shadow_report_path );
}
}
if ( ! defined( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' ) ) {
$markdown_db_shadow_trace_path = getenv( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' );
if ( is_string( $markdown_db_shadow_trace_path ) && '' !== $markdown_db_shadow_trace_path ) {
define( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH', $markdown_db_shadow_trace_path );
}
}

// Downstream capability resolution, health, and CLI read the same identifier,
// so the backend is settled before it is published. An operator who named a
Expand Down
113 changes: 107 additions & 6 deletions inc/native/class-wp-markdown-native-authoritative-snapshot-runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ final class WP_Markdown_Native_Authoritative_Snapshot_Runtime implements WP_Mark
private const MAX_ROWS_PER_TABLE = 10000;
private const MAX_BYTES_PER_TABLE = 8388608;

/** @param array<int,array{table:string,exists:bool,rows?:int,sha256?:string,schema_sha256?:string}> $provenance */
public function __construct( private WP_Markdown_Query_Runtime $runtime, private array $provenance ) {}
/** @param array<int,array{table:string,exists:bool,temporary?:bool,rows?:int,sha256?:string,schema_sha256?:string}> $provenance */
public function __construct( private WP_Markdown_Query_Runtime $runtime, private array $provenance, private ?string $database_name = null ) {}

public static function capture( object $database, string $sql, string $prefix ): self {
self::trace_runtime_phase( 'capture', $sql );
$connection = method_exists( $database, 'markdown_db_mysql_connection' )
? $database->markdown_db_mysql_connection()
: ( $database->dbh ?? null );
Expand All @@ -26,6 +27,8 @@ public static function capture( object $database, string $sql, string $prefix ):
}

$prefixes = self::schema_prefixes( $database, $prefix );
$database_name = self::database_name( $connection );
$catalog_tables = WP_Markdown_Native_Schema_Introspection::requested_information_schema_tables( $sql );
$registry = new WP_Markdown_Native_Table_Registry();
$provenance = array();
foreach ( $tables as $table ) {
Expand All @@ -43,11 +46,60 @@ public static function capture( object $database, string $sql, string $prefix ):
if ( ! $schema instanceof WP_Markdown_Native_Table_Schema ) {
throw new WP_Markdown_Native_Snapshot_Input_Exception( 'markdown_db_native_snapshot_input_unavailable', 'source_schema_unavailable' );
}
$temporary = 1 === preg_match( '/^CREATE\s+TEMPORARY\s+TABLE\b/i', $definition );
if ( $temporary && is_array( $catalog_tables ) && in_array( $table, $catalog_tables, true ) ) {
// SHOW CREATE resolves the session temporary table; metadata must come from the permanent catalog.
$definition = self::permanent_catalog_definition( $connection, $table );
$compiled = '' === $definition ? array() : WP_Markdown_Native_Schema_Catalog::compile( $definition, $prefixes, array( $table ) );
$schema_definition = 1 === count( $compiled ) ? reset( $compiled ) : null;
$schema = is_array( $schema_definition ) ? WP_Markdown_Native_Schema_Catalog::indexed_snapshot_schema( $schema_definition ) : null;
if ( ! $schema instanceof WP_Markdown_Native_Table_Schema ) {
throw new WP_Markdown_Native_Snapshot_Input_Exception( 'markdown_db_native_snapshot_input_unavailable', 'permanent_catalog_schema_unavailable' );
}
$registry->register( $table, $schema, new WP_Markdown_Native_Authoritative_Snapshot_Provider( array(), $schema ) );
$provenance[] = array( 'table' => $table, 'exists' => true, 'temporary' => true, 'schema_sha256' => hash( 'sha256', $definition ) );
continue;
}
$rows = self::rows( $connection, 'SELECT * FROM ' . $quoted . ' LIMIT ' . ( self::MAX_ROWS_PER_TABLE + 1 ) );
$registry->register( $table, $schema, new WP_Markdown_Native_Authoritative_Snapshot_Provider( $rows, $schema ) );
$provenance[] = array( 'table' => $table, 'exists' => true, 'rows' => count( $rows ), 'sha256' => hash( 'sha256', self::encode_rows( $rows ) ), 'schema_sha256' => hash( 'sha256', $definition ) );
$provenance[] = array( 'table' => $table, 'exists' => true, 'temporary' => $temporary, 'rows' => count( $rows ), 'sha256' => hash( 'sha256', self::encode_rows( $rows ) ), 'schema_sha256' => hash( 'sha256', $definition ) );
}
return new self( new WP_Markdown_Native_Query_Runtime( $registry, database_name: $database_name ), $provenance, $database_name );
}

private static function trace_runtime_phase( string $phase, ?string $sql = null ): void {
$path = defined( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' ) ? MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH : getenv( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' );
if ( ! is_string( $path ) || '' === $path ) {
return;
}
if ( ( is_file( $path ) ? (int) filesize( $path ) : 0 ) >= 65536 ) {
return;
}
$event = array( 'phase' => $phase, 'file_sha256' => hash_file( 'sha256', __FILE__ ) );
if ( null !== $sql && strlen( $sql ) <= 65536 ) {
try {
$event['sql_sha256'] = hash( 'sha256', $sql );
$event['token_types'] = array_slice( array_map( static fn( WP_Markdown_Native_SQL_Token $token ): string => $token->type(), ( new WP_Markdown_Native_SQL_Tokenizer() )->tokenize( $sql ) ), 0, 128 );
$event['table_count'] = count( self::tables_in( $sql ) );
} catch ( WP_Markdown_Native_Snapshot_Input_Exception|WP_Markdown_Native_SQL_Parse_Error ) {
$event['token_types'] = array( 'parse_error' );
}
}
$encoded = json_encode( $event, JSON_UNESCAPED_SLASHES ) . "\n";
if ( strlen( $encoded ) <= 4096 && false !== ( $trace = @fopen( $path, 'c' ) ) ) {
try {
if ( flock( $trace, LOCK_EX ) ) {
$size = fstat( $trace )['size'] ?? 0;
if ( $size + strlen( $encoded ) <= 65536 ) {
fseek( $trace, 0, SEEK_END );
fwrite( $trace, $encoded );
}
flock( $trace, LOCK_UN );
}
} finally {
fclose( $trace );
}
}
return new self( new WP_Markdown_Native_Query_Runtime( $registry ), $provenance );
}

/** @return array<int,string> */
Expand All @@ -59,6 +111,12 @@ private static function schema_prefixes( object $database, string $prefix ): arr
return array_values( array_unique( array_filter( $prefixes, static fn( string $candidate ): bool => '' !== $candidate ) ) );
}

private static function database_name( object $connection ): ?string {
$row = self::one_row( $connection, 'SELECT DATABASE()' );
$value = is_array( $row ) ? reset( $row ) : null;
return is_string( $value ) ? $value : null;
}

public function execute( WP_Markdown_Query_Request $request ): WP_Markdown_Query_Result {
$result = $this->runtime->execute( $request );
$diagnostic = $result->diagnostic() ?? array();
Expand All @@ -85,15 +143,58 @@ private function has_explicitly_absent_source( string $sql ): bool {
return array() !== array_intersect( self::tables_in( $sql ), $absent );
}

/** @return array{read_connection:string,tables:array<int,array{table:string,exists:bool,rows?:int,sha256?:string,schema_sha256?:string}>} */
/** @return array{read_connection:string,database_sha256:?string,tables:array<int,array{table:string,exists:bool,rows?:int,sha256?:string,schema_sha256?:string}>} */
public function provenance(): array {
return array( 'read_connection' => 'authoritative_mysql_connection_pre_query', 'tables' => $this->provenance );
return array_filter(
array(
'read_connection' => 'authoritative_mysql_connection_pre_query',
'database_sha256' => null === $this->database_name ? null : hash( 'sha256', $this->database_name ),
'tables' => $this->provenance,
),
static fn( mixed $value ): bool => null !== $value
);
}

private static function permanent_catalog_definition( object $connection, string $table ): string {
$escaped = str_replace( "'", "''", $table );
$rows = self::rows( $connection, "SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA, COLUMN_DEFAULT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{$escaped}' ORDER BY ORDINAL_POSITION" );
if ( array() === $rows ) {
return '';
}
$columns = array();
$primary = array();
foreach ( $rows as $row ) {
$name = (string) ( $row['COLUMN_NAME'] ?? '' );
$type = (string) ( $row['COLUMN_TYPE'] ?? '' );
if ( 1 !== preg_match( '/^[A-Za-z0-9_]+$/', $name ) || 1 !== preg_match( '/^[A-Za-z]+(?:\([0-9,]+\))?(?:\s+unsigned)?$/i', $type ) ) {
return '';
}
$line = '`' . $name . '` ' . $type . ( 'NO' === ( $row['IS_NULLABLE'] ?? null ) ? ' NOT NULL' : '' );
if ( null !== ( $row['COLUMN_DEFAULT'] ?? null ) ) {
$line .= " DEFAULT '" . str_replace( "'", "''", (string) $row['COLUMN_DEFAULT'] ) . "'";
}
if ( str_contains( strtolower( (string) ( $row['EXTRA'] ?? '' ) ), 'auto_increment' ) ) {
$line .= ' AUTO_INCREMENT';
}
$columns[] = $line;
if ( 'PRI' === ( $row['COLUMN_KEY'] ?? null ) ) {
$primary[] = '`' . $name . '`';
}
}
if ( array() !== $primary ) {
$columns[] = 'PRIMARY KEY (' . implode( ',', $primary ) . ')';
}
return 'CREATE TABLE `' . $table . '` (' . implode( ',', $columns ) . ')';
}

/** @return array<int,string> */
private static function tables_in( string $sql ): array {
$plan = ( new WP_Markdown_Native_Query_Parser() )->parse( $sql );
if ( $plan instanceof WP_Markdown_Query_Result ) {
$catalog_tables = WP_Markdown_Native_Schema_Introspection::requested_information_schema_tables( $sql );
if ( null !== $catalog_tables ) {
return $catalog_tables;
}
$diagnostic = $plan->diagnostic() ?? array();
throw new WP_Markdown_Native_Snapshot_Input_Exception(
(string) ( $diagnostic['code'] ?? 'markdown_db_native_unsupported_query' ),
Expand Down
Loading