feat(ci): restore workflows and label PPL linter CI - #8
Closed
Hanyu-W wants to merge 78 commits into
Closed
Conversation
This reverts the changes introduced by PR opensearch-project#5599 (merge commit 454ac4e).
* [Feature] Add PPL makeresults command Add the makeresults leading command on the Calcite (v3) path. It generates in-memory rows with no index scan: - count=N produces N rows, each with a single _time timestamp set to query time - format=csv|json data=... parses an inline literal into typed rows, with column types synthesized following OpenSearch dynamic-mapping semantics and surfaced through the same type path an index scan uses (JSON int->long, decimal->float, string->keyword; typed CSV name:type via cast's vocabulary; bare CSV->string) Grammar lives in the ppl/ copies only. Adds the MakeResults AST node, AstBuilder wiring with MakeResultsDataParser, CalciteRelNodeVisitor.visitMakeResults building LogicalValues + Project, a V2 Analyzer reject stub, and anonymizer rendering. Includes unit tests, AstBuilder and anonymizer tests, integration tests, and the user doc. Signed-off-by: Louis Chu <lingzhichu.clz@gmail.com> * Address Songkan's comments Signed-off-by: Louis Chu <lingzhichu.clz@gmail.com> * Address PR-Agent edge cases: empty-first-row guard, skip whitespace CSV lines Signed-off-by: Louis Chu <lingzhichu.clz@gmail.com> * makeresults: cap inline data by cells (rows x columns) and per-value width Replace the flat data<=5000 rows guard with rows*cols<=5000 cells (the Janino 64KB per-method codegen cliff scales with rows x columns, so a flat row cap was unsafe for multi-column data). Replace the 29999 total-data char cap with a per-value guard (single cell value <= 60000 chars, under the JVM 65535-byte constant-pool limit). Trim over-verbose comments. Signed-off-by: Louis Chu <lingzhichu.clz@gmail.com> * makeresults: add ragged-row and at-limit boundary tests; fix index version to 3.8 Add four boundary tests to CalcitePPLMakeResultsTest: CSV row with more columns rejected, CSV row with fewer columns padded to null, count at the 5000 cap allowed, and a cell value at the 60000-char limit allowed. Correct the PPL command index to list makeresults as 3.8 (since 3.8). Signed-off-by: Louis Chu <lingzhichu.clz@gmail.com> --------- Signed-off-by: Louis Chu <lingzhichu.clz@gmail.com>
Signed-off-by: Songkan Tang <songkant@amazon.com>
* Support constant_keyword field type in PPL Register `constant_keyword` in the OpenSearch mapping parser so PPL recognises the type and treats it as a string, matching the semantics of the OpenSearch `constant_keyword` field type (a single value shared by every document in an index). Resolves opensearch-project#3703 Signed-off-by: Peng Huo <penghuo@gmail.com> * Document constant_keyword in PPL data types table Signed-off-by: Peng Huo <penghuo@gmail.com> * Index tenant value explicitly in constant_keyword integ test The v2 engine reads field values from _source. OpenSearch does not back-fill the mapping's constant value into _source when a document omits the field, so the row came back with tenant=null on the v2 path even though the mapping declares the value. Index the tenant field in the document so both v2 and Calcite paths return the same value. Signed-off-by: Peng Huo <penghuo@gmail.com> --------- Signed-off-by: Peng Huo <penghuo@gmail.com>
* xy series implmentation Signed-off-by: Asif Bashar <abashar@apple.com> * xy series implmentation Signed-off-by: Asif Bashar <abashar@apple.com> * xy series implmentation Signed-off-by: Asif Bashar <abashar@apple.com> * xy series implmentation Signed-off-by: Asif Bashar <abashar@apple.com> * xy series implmentation Signed-off-by: Asif Bashar <abashar@apple.com> * index.md updated Signed-off-by: Asif Bashar <abashar@apple.com> * removed duplicate format added during merging Signed-off-by: Asif Bashar <abashar@apple.com> * fix compile issue Signed-off-by: Asif Bashar <abashar@apple.com> * fix test failure Signed-off-by: Asif Bashar <abashar@apple.com> * added missing explain expection output files missed during merge conflict. Signed-off-by: Asif Bashar <abashar@apple.com> * removed extra formatting changes Signed-off-by: Asif Bashar <abashar@apple.com> * removed extra formatting changes Signed-off-by: Asif Bashar <abashar@apple.com> * fix explain test failure Signed-off-by: Asif Bashar <abashar@apple.com> --------- Signed-off-by: Asif Bashar <abashar@apple.com>
* Decouple Calcite PPL planning from ExprType Rewrite the Calcite-side PPL planning surface so RelNode/RexNode code, coercion, type checking, and UDF implementations operate on RelDataType instead of bouncing through the v2 ExprType system. UDT identity at planning time: - UDTs (ExprDateType, ExprTimeType, ExprTimeStampType, ExprIPType, ExprBinaryType) are recognised via instanceof rather than getExprType(). Subclasses are preserved through createTypeWithNullability / createTypeWithCharsetAndCollation via a cloneWith hook on ExprSqlType / ExprJavaType so the instanceof checks survive type-factory operations. Calcite-side rewrites: - CoercionUtils: new RelDataType-typed common-type resolver with an internal CoercionTag widening DAG that mirrors v2 semantics. Widening produces UDT-normalized temporal types and always makes the cast target nullable so safe cast + primitive aggregators do not NPE on parse failure. - PPLTypeChecker: signatures expressed as List<List<RelDataType>>; adds renderTypeName() for error messages; folds DECIMAL to DOUBLE. isComparable() accepts UDT temporal vs standard temporal of same kind. - PPLOperandTypes: exposes RelDataType signature constants (DATE_UDT, INTEGER_T, ...). - PPLFuncImpTable.requiresNumericArgument: uses SqlTypeUtil.isNumeric and SqlTypeName.ANY for the "unknown-type" check. - ExtendedRexBuilder, AddSubDate/Extract/Format/LastDay/PeriodName/ TimestampAdd/TimestampDiff/Weekday/Span/WidthBucket and the ip UDFs branch on UDT classes and pass RelDataType through their implementors. - visitCast in CalciteRexNodeVisitor maps AST DataType directly to RelDataType, removing the DataType.getCoreType() round-trip. - CurrentFunction / FormatFunction take an internal Kind / boolean discriminator rather than an ExprCoreType. - DatetimeExtension switches from ExprUDT enum to type-class checks. Signed-off-by: Peng Huo <penghuo@gmail.com> * Fix PPLTypeChecker.typesMatch UDT comparison and revert CalciteRexNodeVisitor churn typesMatch compared UDTs via getClass(), but addCharsetAndCollation strips concrete subclass identity from VARCHAR-backed UDTs (ExprDateType, ExprTimeType, ExprTimeStampType, ExprBinaryType) — they all collapse to ExprSqlType. That let wrapUDT accept mismatched UDTs at the signature gate: `cidrmatch(date_field, "1.2.3.4/24")` matched the [BINARY_UDT, STRING_T] signature and crashed at runtime trying to parse a date as an IP. Compare via the ExprUDT tag instead, matching what PPLComparableTypeChecker.isComparable already does. Also revert CalciteRexNodeVisitor to upstream/main — the earlier inlined visitCast switch duplicated convertExprTypeToRelDataType, and the visitBetween comment tweak had no code change. Signed-off-by: Peng Huo <penghuo@gmail.com> * spotless: reflow typesMatch javadoc Signed-off-by: Peng Huo <penghuo@gmail.com> * CoercionUtils: convert RelDataType→ExprType at boundary, reuse main-branch lattice The parallel CoercionTag lattice duplicated ExprCoreType.getParent() (BYTE→SHORT→...→DOUBLE; STRING→DATE/TIME/TIMESTAMP/BOOLEAN/IP; DATE/TIME→TIMESTAMP), and adding STRING→TIMESTAMP as a widening edge to make ranking tie with STRING→DOUBLE conflated widening truth with signature preference. Also, normalizeTemporalToUdt was a downstream fixup for plain-Calcite TIMESTAMP results that only handled bare TIMESTAMP/DATE/TIME and missed the TZ variants, and the public hasString(List<RexNode>) used SqlTypeUtil.isCharacter which incorrectly classified VARCHAR-backed UDTs (DATE/TIME/TIMESTAMP/BINARY) as STRING. Replace all of it with a boundary conversion: convertRelDataTypeToExprType at the entry, run the widening + rule set exactly as upstream/main does over ExprCoreType, and round-trip results through convertExprTypeToRelDataType — which already returns the UDT variant for temporals and IP, so the "normalize to UDT" step happens for free. TZ variants are handled correctly (convertSqlTypeNameToExprType folds them into TIMESTAMP/TIME). The hasString public API now matches the private one (both check ExprCoreType.STRING). Deletes ~90 lines of duplicated lattice. Signed-off-by: Peng Huo <penghuo@gmail.com> * CoercionUtils: restore upstream body verbatim, keep only boundary adapter Reduce this file to its minimal delta vs main: the only necessary change is that PPLTypeChecker.getParameterTypes() on this branch returns List<List<RelDataType>> (upstream returns List<List<ExprType>>). Adapt at the entry of castArguments; everything else is main-branch code. Delete the parallel CoercionTag lattice, PARENTS map, normalizeTemporalToUdt, and the RelDataType-flavored resolveCommonType / max / distance helpers introduced in the earlier version of this file. The upstream ExprType-based lattice is the source of truth for widening, and the RelDataType→ExprType conversion via convertRelDataTypeToExprType at the public boundary handles UDTs uniformly. Test: StubTypeChecker.getParameterTypes now returns List<List<RelDataType>> to match the branch's PPLTypeChecker interface. Substitute SqlTypeName.GEOMETRY for ExprCoreType.GEO_POINT in the no-compatible-signature test since GEO_POINT isn't in convertExprTypeToRelDataType's switch. Signed-off-by: Peng Huo <penghuo@gmail.com> * PPLTypeChecker.isComparable: match upstream semantics, add unit tests Replace the custom "same UDT kind → temporal kind → same SqlTypeFamily" branches with the upstream approach: convert both sides to ExprType via convertRelDataTypeToExprType and use ExprType.shouldCast to decide comparability. Two regressions surface without this: - plain VARBINARY vs EXPR_BINARY UDT (both map to ExprCoreType.BINARY) was rejected because the family-match guard excluded UDTs, so the branch never triggered on a UDT-plain pair. - day-time interval vs year-month interval (both map to INTERVAL) was rejected because Calcite splits their SqlTypeFamily into INTERVAL_DAY_TIME / INTERVAL_YEAR_MONTH. Both compare equal under shouldCast so upstream semantics preserves them. The unused temporalKind helper is removed; new PPLComparableTypeCheckerTest covers numeric, same-UDT, plain-vs-UDT temporal/binary, cross-UDT rejection, interval mixing, ANY fallback, struct field-by-field, and the IP-outer-checker rejection. Signed-off-by: Peng Huo <penghuo@gmail.com> * PPLTypeChecker.isComparable: revert to upstream, exercise via public checker Restore isComparable and its javadoc byte-for-byte to upstream/main — the previous adaptation added no behavior and the private visibility is correct. Drive the tests through the public PPLComparableTypeChecker.checkOperandTypes entry point instead of lifting isComparable to package-private just for tests. Signed-off-by: Peng Huo <penghuo@gmail.com> * PPLComparableTypeCheckerTest: reword regression comments as guardrails These pairs don't fix a shipped bug — the upstream isComparable already handles them. Reword the comments to reflect that the tests exist to guard against future edits that would classify via SqlTypeFamily or Java class, either of which would break these specific pairs. Signed-off-by: Peng Huo <penghuo@gmail.com> --------- Signed-off-by: Peng Huo <penghuo@gmail.com>
…ch-project#5643) Add visitXyseries to PPLQueryDataAnonymizer so the xyseries stage appears in the anonymized query logged for every PPL request, with pivot literals and options masked. Previously the visitor fell through to visitChildren and the entire xyseries clause was silently dropped from log output. Also flip the xyseries entry in docs/user/ppl/index.md from stable to experimental (since 3.8), matching how other newly-introduced commands are listed. Signed-off-by: Peng Huo <penghuo@gmail.com>
Signed-off-by: opensearch-ci-bot <opensearch-infra@amazon.com>
opensearch-project#5653) Signed-off-by: Ajimelec Gonzalez <ajimelec@amazon.com>
…search-project#5631) * Surface PIT-context exhaustion with an actionable error message The Calcite engine opens a Point-In-Time (PIT) context to page over a query it cannot push down to OpenSearch -- for example a stats/aggregation that groups by a text field with no keyword sub-field, which forces a full doc scan. A PIT allocates one reader context per shard, so a single such query over a many-shard index can exhaust the node's search.max_open_pit_context budget on its own; concurrent load makes it more likely. Previously that failure surfaced to the user as the opaque internal message "exception while executing query: Error occurred while creating PIT for internal plugin operation" with no hint of the cause or the remedy. Detect the PIT-context-limit rejection in the execute-time cause chain and rethrow it as a PointInTimeLimitExceededException wrapped in an ErrorReport, so the reason names the search.max_open_pit_context setting and the details explain the two remedies (raise the setting, or optimize the query). Match on the "too many Point In Time contexts" marker rather than the exception class, since OpenSearchRejectedExecutionException is also raised for scroll and thread-pool rejections. The scan walks the whole cause chain because the rejection surfaces several layers deep (SQLException -> RuntimeException -> ExecutionException -> per-shard rejection). This targets the Calcite path only; the marker check guards against self-referential cause loops. Signed-off-by: Kai Huang <ahkcs@amazon.com> * Address review: trim PIT remedy text, generalize resource-limit exception Drop the "optimize the query" remedy from the PIT-context-limit details, leaving the actionable "increase [search.max_open_pit_context]" instruction. Replace the one-message PointInTimeLimitExceededException with a reusable ResourceLimitExceededException in common/error, alongside ErrorReport and ErrorCode.RESOURCE_LIMIT_EXCEEDED, so the type is not one-class-per-message and is reachable across modules. Signed-off-by: Kai Huang <ahkcs@amazon.com> --------- Signed-off-by: Kai Huang <ahkcs@amazon.com>
…and profiling (opensearch-project#5568) * Overriding profile endpoint with analyze endpoint with operator tree and profiling Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Updating AnalyzeResponse format Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Cleaner method to combine profile and analyze Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Adding tests and CI test fixes Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Ran ./gradlew spotlessApply for formatting Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Fixing narrowing type conversion Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Adding querySegments back to AnalyzeResponse body Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Adding docs, integ-test, handling when Calcite is disabled, 'fallback' on complex queries Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Updating integ-test correct version, previously tested recommendation, which is not a part of this PR Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Deleted FQN, updated/added testing Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Rebased and fixed type mismatch Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Updating integ-test to ignore recommendations (next PR) Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * Updating unit test to ignore \r\n and \n differences Signed-off-by: Krish Gandhi <kjg2352@gmail.com> * fixing spotless check Signed-off-by: Krish Gandhi <kjg2352@gmail.com> --------- Signed-off-by: Krish Gandhi <kjg2352@gmail.com>
…ct#5654) (opensearch-project#5655) The PPL rest command was added (opensearch-project#5599) and fully reverted (opensearch-project#5635) within the same release cycle, so neither should appear in the notes. (cherry picked from commit 7c24851) Signed-off-by: Eric Wei <menwe@amazon.com> Signed-off-by: opensearch-ci-bot <opensearch-infra@amazon.com> Co-authored-by: Eric Wei <menwe@amazon.com>
* feat: slow query thread pool Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * handle slow query detection when optimization runs in execution step Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * fixes: assorted context propagation issues Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * fix remaining integ tests Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * add some more thread & security tests Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * code self-review, round 1 Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * use 2x background threads to account for 2x pools Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * rename slow -> complex, add pool indication header Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * add a failure log for slow pool requests Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * fix profile, add thread pool as part of profile object Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * remove leftover build.gradle changes from another branch Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * add cancelation polling so ppl cancelation is faster to apply Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * Add thread pool profile details to doc Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * Move analyze call measurement Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * add complex pool IT Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * Move calcite context thread copies to dedicated method Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * add attach_pid to gitignore Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * register timeout handler to complex pool on these requests Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * fix units Signed-off-by: Simeon Widdis <sawiddis@amazon.com> * remove redundant optimize call from execution engine during execution Signed-off-by: Simeon Widdis <sawiddis@amazon.com> --------- Signed-off-by: Simeon Widdis <sawiddis@amazon.com>
…earch-project#5646) * Push down aggregation on text field without .keyword sub-field For a text-typed group key or metric argument with no .keyword sub-field, NamedFieldExpression.getReferenceForTermQuery() returned null and CompositeValuesSourceBuilder/ValueCountAggregationBuilder rejected the null field, so pushDownAggregate silently fell back to a full _source scan and client-side aggregation. Route those bare RexInputRefs through a Calcite script that reads the value from _source, matching TermQuery/LikeQuery/RexStandardizer for filter and script fields. Composite terms buckets and metric aggregations that accept a script (notably count(FIELD)) now push down. Signed-off-by: Peng Huo <penghuo@amazon.com> Signed-off-by: Peng Huo <penghuo@gmail.com> * Apply spotless formatting Signed-off-by: Peng Huo <penghuo@amazon.com> Signed-off-by: Peng Huo <penghuo@gmail.com> * Update explain plan pins for text-field aggregation pushdown Four CalciteExplainIT plans encoded the pre-fix behavior where dedup / chart / timechart on a text field with no .keyword sub-field silently fell back to a client-side aggregation over an unbounded scan. With the AggregateAnalyzer fix those queries now push down as composite terms (script over _source), so the pinned physical plans are stale. - Rename testDedupTextTypeNotPushdown -> testDedupTextTypePushdown and update explain_dedup_text_type_push.yaml to the composite terms + top_hits DSL. - Refresh chart_null_str.yaml (chart limit=10 ... over gender by age span=10) to the composite terms(script) + histogram plan. - Refresh explain_timechart.yaml and explain_timechart_count.yaml (timechart span=1m ... by host) to the composite terms(script) + date_histogram plan. Add DedupCommandIT.testDedupOnTextField to verify behavioral equivalence: the result set for `source=bank | dedup email` matches the fixture's set of distinct emails, running both under the V2 path (base class) and Calcite pushdown path (CalciteDedupCommandIT). Signed-off-by: Peng Huo <penghuo@amazon.com> Signed-off-by: Peng Huo <penghuo@gmail.com> * Verify dedup row values in testDedupOnTextField Widen the assertion beyond the dedup key to also verify the associated projected columns per row (firstname, balance), so the top_hits round-trip in the pushed-down dedup DSL is checked end-to-end. Signed-off-by: Peng Huo <penghuo@amazon.com> Signed-off-by: Peng Huo <penghuo@gmail.com> * Add timechart avg-by-text-host result IT Assert row-level results for `source=events | timechart span=1m avg(cpu_usage) by host` on the events fixture, where `host` is a text field with no .keyword sub-field. Golden values were collected on upstream/main (unpushed) before applying the fix, so the assertion pins behavioral equivalence between the V2 client-side plan and the pushed composite terms(script)+date_histogram plan. Signed-off-by: Peng Huo <penghuo@amazon.com> Signed-off-by: Peng Huo <penghuo@gmail.com> * Trim comment on testTimechartAvgByTextHost Signed-off-by: Peng Huo <penghuo@amazon.com> Signed-off-by: Peng Huo <penghuo@gmail.com> --------- Signed-off-by: Peng Huo <penghuo@amazon.com> Signed-off-by: Peng Huo <penghuo@gmail.com>
…rch-project#5664) (opensearch-project#5666) * Add ci.opensearch.org/m2/ mirror for plugin resolution (sql) * Address order issues --------- (cherry picked from commit fe20ba6) Signed-off-by: shreyah963 <shreyab963@gmail.com> Signed-off-by: Peter Zhu <zhujiaxi@amazon.com> Signed-off-by: opensearch-ci-bot <opensearch-infra@amazon.com> Co-authored-by: Shreya Bhatta <shreyab963@gmail.com> Co-authored-by: Peter Zhu <zhujiaxi@amazon.com>
…l) (opensearch-project#5667) Signed-off-by: Peter Zhu <zhujiaxi@amazon.com>
…ect#5656) * [Feature] Add PPL `rest` command (Calcite system row source) Add a leading `rest <endpoint>` command that exposes a curated, read-only, fixed-schema set of in-cluster management endpoints as a PPL table. --------- Signed-off-by: Louis Chu <lingzhichu.clz@gmail.com>
…s. (opensearch-project#5642) * Clean docs * Fix CalcitePPLRareTopNTest showperc tests * Calculate percentages before filtering * Added percentfield and changed decimal places from 2 to 6 * Update test errors, SUM to CHECKED_LONG_SUM * Remove percent field check Signed-off-by: Ajimelec Gonzalez <ajimelec@amazon.com>
…-project#5675) PPLOperandTypes.SCALAR_TYPES declares DATE/TIME/TIMESTAMP/IP/BINARY operands as UDTs, but typesMatch rejected a pair outright whenever only one side extended AbstractExprRelDataType. The analytics engine builds its row types from plain Calcite types (date -> TIMESTAMP(3), ip and binary -> VARBINARY) plus markers deriving from Calcite's AbstractSqlType, so every such operand failed the check. The result was a self-contradictory error, because getAllowedSignatures renders via the UDT tag while getActualSignature renders via convertRelDataTypeToExprType -- both print TIMESTAMP: Aggregation function LIST expects field type {...|[DATE]|[TIME]|[TIMESTAMP]|[IP]|[BINARY]}, but got [TIMESTAMP] Map the UDT tag to the SqlTypeNames a backend would emit for the same logical type. Comparing backing types would not work, since the UDTs are all VARCHAR-backed. The mapping is expressed over SqlTypeName rather than by calling convertAnalyticsEngineRelDataTypeToExprType, because analytics-api is a compileOnly dependency of core and loading those marker classes throws NoClassDefFoundError wherever it is off the runtime classpath. Signed-off-by: Kai Huang <ahkcs@amazon.com>
3 tasks
added 8 commits
August 4, 2026 21:24
Add a cross-repository CI check that keeps the OpenSearch-Dashboards PPL lint rule 'unsupported-window-function-in-eventstats' and the SQL backend in agreement. Frontend half: a SQL-owned Node script loads the compiled OSD analyzer from an OSD checkout and asserts the rule's diagnostic counts. Backend half: a Gradle integration test sends the same queries to the live /_plugins/_ppl endpoint of the SQL plugin built from the checkout. Both halves consume one shared contract file. - integ-test/.../ppl-lint/unsupported-window-function-in-eventstats.spec.json - scripts/ppl-lint/run-frontend-contract.mjs - integ-test/.../calcite/remote/PplLintRuleValidationIT.java - .github/workflows/ppl-lint-rule-validation.yml - scripts/ppl-lint-rule-validation.sh Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Addresses shellcheck SC2006/SC2046 on the chown/su lines so actionlint runs clean. Behavior is unchanged. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
The OpenSearch CI container is Amazon Linux 2 (glibc 2.26), but OSD requires Node 22 whose prebuilt binary needs glibc >= 2.27. Running the Node frontend contract inside that container failed with 'GLIBC_2.27 not found'. Split into two required jobs: 'frontend' runs the OSD analyzer contract on a bare ubuntu-latest runner (modern glibc, actions/setup-node works), and 'backend' keeps the Gradle integration test in the CI container where the OpenSearch test cluster needs it. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Generalize the single-rule PPL lint validation contract (eventstats PoC) into a schema-v2 corpus that pins every reachable OSD analyzer rule to live /_plugins/_ppl behavior. Both halves read the same reviewed contract files so neither the analyzer diagnostic nor the engine behavior can drift without a red build. Verified live end-to-end: frontend 13/13 against the OSD main analyzer; backend 13/13 (pr) and 21/21 (nightly) against a live test cluster. Contract schema v2 (integ-test/src/test/resources/ppl-lint/contracts/*.spec.json + manifest.json): - backend.kind discriminator: rejection | result-shape | advisory (explain reserved for the nightly-only explain rule class once it lands on OSD main). - per-contract backendFixture.clusterSettings so contracts that disagree on fallback/join settings each set what they need (eventstats needs calciteFallback=false; dedup-consecutive needs true) — validated in one run. - per-case minVersionRequired/engineRequired so both halves skip identically. - wiring block asserted deep-equal against the OSD catalog (drift tripwire). - frontendContext.deriveFromMapping single-sources fields/typeMap for the field-validation existence pass. - error.reason values snapshotted from the observed engine response, not hand-typed (join/multisearch AST-build-time throws yield generic "Invalid Query"; union/replace carry the specific message). Frontend adapter (run-frontend-contract.mjs): contract discovery via manifest, schedule + version/engine gating, catalog wiring assertion, compiled-simplified and runtime-bundle grammar surfaces (runtime-only rules whose parser rules are absent on the checkout's grammar assert wiring then skip cleanly), collect-all failures, and a frontend-report.json for disagreement diffing. Nightly adds a coverage assertion that every enabled catalog rule has a contract. Backend IT: parameterized over the contract corpus with per-kind verifiers (verifyRejectedCase / verifyResultShape / verifyAdvisory200), per-contract cluster-setting apply+reset, GET / cluster-version gating, and a backend-report.json recording observed status/type/reason per rejection. Sends queries with a JSON-escaped body so contract queries containing quotes (grok field=body "...") reach the engine faithfully instead of tripping a core request-payload parse error. Honors -Dppl.lint.schedule=pr|nightly, forwarded to the forked test JVM via integ-test/build.gradle. Workflow + repro script: derive schedule (PR -> pr, cron -> nightly), read the contract dir, upload frontend/backend reports + corpus artifacts. Rules covered: eventstats window fn, division-by-zero, head-without-sort, disabled-join-type, field-validation (shape + existence) on PR; plus dedup-consecutive and the runtime-only union/multisearch/replace on nightly. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Take the cross-repository PPL lint check from the PoC to the design in
ppl-lint-ci-validation-design.md. The detector half now lints against the
*candidate* runtime grammar bundle the SQL PR builds — through OSD's production
headless lint API — instead of the compiled analyzer or a hand-rolled reparse
of OSD main's checked-in grammar. Both halves validate the same grammar, so a
parser/semantic change that invalidates a lint rule reds the build.
SQL-side changes (the OSD headless API ships separately):
- PplLintRuleValidationIT: export the candidate grammar bundle
(GET /_plugins/_ppl/_grammar) + a target manifest {engineVersion, grammarHash,
grammarBundle} while the cluster is alive; read schema-v3 specs; select the one
expectations[] entry matching the backend version (zero/>1 fails); record the
observed backend behavior per query for the differential.
- integ-test/build.gradle: forward -Dppl.lint.grammar.bundle / -Dppl.lint.target
to the test JVM alongside the existing ppl.lint.* knobs.
- run-frontend-contract.mjs: deserialize the candidate bundle via the OSD
headless API and lint each query with lintQueryWithBundle (runtime-bundle
surface, so the runtime-only arity rules fire); pin dataSourceVersion +
knownVersion to the candidate version; assert the detector-vs-backend
differential from the backend report; fail loud on a missing bundle.
- workflow: linear backend-validation -> detector-validation -> validation-result
pipeline; artifacts are the only bridge between jobs. validation-result is the
single always() required check (red unless both jobs succeed) and writes the
per-rule PR summary; assemble-run-manifest.mjs emits run-manifest.json with the
immutable SQL + OSD SHAs, mode, backend version, grammar hash, and enforced set.
A workflow_dispatch osd_ref run is pre-merge evidence, not a protection result.
- contracts: migrate all 9 specs to schema v3 (named queries{role,query} +
version-scoped expectations[]); partition manifest.json into enforced
(eventstats, multisearch, union, replace), pendingReview (field-validation),
and nonEnforcing. Union/multisearch triggers are query-initial, not pipe-first:
OSD prepends a synthetic source prefix to pipe-first queries, which would
desync the two halves.
- Add scripts/ppl-lint/README.md documenting inputs, local reproduction, the
contract format, and the failure table.
Verified end to end against a live cluster: the backend IT exports a real
candidate bundle and the detector runner agrees on all four enforced rules
(triggers rejected + 1 diagnostic, controls accepted + 0); an intentional
expectation mismatch reds the runner.
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
The detector-validation step piped the runner through `tee`, so the step took tee's (success) exit status and a real runner failure — e.g. the OSD headless module being absent on OSD main — went green as a vacuous pass. The first live PR run hit exactly this: the runner exited 2 with "Expected OSD module not found ... headless_ppl_lint", yet detector-validation and validation-result both reported success. Add `set -o pipefail` so node's non-zero exit propagates and the required check correctly reds until the OSD headless API merges. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
detector-validation hardcoded repository: opensearch-project/OpenSearch-Dashboards, so osd_ref could only resolve commits/branches that exist upstream. An unmerged OSD change on a fork (e.g. the headless lint API before it lands on OSD main) could not be validated end to end. Add an osd_repo workflow_dispatch input (default opensearch-project/OpenSearch-Dashboards) that the OSD checkout honors, thread osd_repo through the detector job output into the run manifest + PR summary, and treat any non-upstream-main target as osd-branch-evidence (never a required check). The required pull_request run is unchanged: it still checks out upstream OSD main. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
added 27 commits
August 4, 2026 21:25
…mpiled one
The discovery job hardcoded `PPL_LINT_SURFACE=compiled-simplified`. That was an
unnecessary restriction: `lint_runner` SKIPS the four `runtimeOnly` rules on the
compiled grammar because the productions they walk do not exist there, so a
compiled-only run cannot observe them at all — and three of the four ship at error
severity, where a false positive is most expensive.
The job now exports the engine's grammar via GET /_plugins/_ppl/_grammar and lints
on the runtime surface, falling back to the compiled surface with a warning if the
export fails. Best-effort rather than fatal: a lead-generator that produces nothing
because one endpoint was unavailable is worse than one with narrower coverage, and
the surface is recorded in the report so a reader can tell which ran.
Also fixes an unbound-variable crash in that step. Expanding an empty array as
"${extra[@]}" under `set -u` is an error in bash before 4.4, so the compiled-surface
fallback would have died — the one path that only runs when something else already
went wrong. Verified both branches.
Worth recording since it bounds what harvesting can achieve: the four runtimeOnly
rules are at zero harvested queries and no surface changes that. OSD's lint tests
contain no trigger for union-min-datasets, multisearch-min-subsearch or
replace-wildcard-asymmetry; the only place they appear is a negative assertion that
they no-op on the compiled surface. Harvesting cannot invent what was never written.
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
…had one The partial-vs-full relaxation verdict reads the ENFORCED corpus, and 8 of its 11 contracts pinned exactly one trigger. With one trigger, "every trigger relaxed" is a single observation, so the verdict cannot distinguish a full engine fix (version-scope the rule away) from a partial one (narrow the detector) — and those need opposite actions. The classifier warns about it, but the fix is more triggers. Each new trigger exercises a DIFFERENT shape of the same condition, so a partial engine fix is visible as a disagreement between them rather than as a uniform flip: union-min-datasets single dataset that carries a pipeline (| fields) multisearch-min-subsearch single subsearch that carries a pipeline (| where) replace-wildcard-asymmetry reversed asymmetry (2 wildcards -> 1, not 1 -> 2) invalid-capture-group-name hyphen, not just underscore unsupported-window-function dense_rank, not just rank dedup-consecutive-unsupported multi-field dedup with consecutive=true division-by-zero decimal 0.0, not just integer 0 head-without-sort head after a where stage, not a bare source Every expectation was verified on a live 3.8 engine rather than inferred, including the exact error type and reason string: union/fields 400 IllegalArgumentException Union command requires ... Provided: 1 multisearch/where 400 SyntaxCheckException Invalid Query replace 2->1 400 IllegalArgumentException pattern has 2 wildcard(s), replacement has 1 rex hyphen 400 IllegalArgumentException Invalid capture group name 'user-name'. eventstats dense 400 CalciteUnsupportedException Unexpected window function: dense_rank dedup multi-field 200 (advisory; succeeds via the Calcite-to-v2 fallback) head after where 200 (advisory) balance / 0.0 200 with the ratio column all-null Detector counts and severities were confirmed by running the real detector runner over the corpus: all four compiled-surface triggers score 1 at the contracted severity, and eventstats-dense-rank scores 1/error once a version is supplied. The four runtime-bundle-only contracts are reported not-applicable on the compiled surface, as before. Failure count is unchanged from baseline (8, all pre-existing "enabled catalog rule has no contract file" coverage warnings). Worth noting for review: the pre-3.8 expectations for eventstats-dense-rank reuse the existing rank() pins (500 / UnsupportedOperationException) by analogy — both are CalciteUnsupportedException on 3.8, and only 3.8 was available to verify. The 3.6 and 3.7 legs will confirm or correct them. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Seven hypotheses for the 2.19 observation-leg timeout have each been refuted by observation: the index-wipe race, a Gradle cluster fallback, HTTP/2 negotiation, FIPS, the bundled plugin set, a port collision, and address family. What is established is narrow and contradictory: the engine is alive and logging throughout, its publish address and network topology are identical to the passing 3.5.0 leg, the Gradle args and task graphs are byte-identical, curl reaches every endpoint the framework calls in 0s from the same runner -- and GET _nodes/plugins from the test JVM never returns. curl has said everything it can. This probe asks the JVM instead, one layer at a time against the same address: raw TCP connect, then HttpURLConnection, then the real OpenSearch RestClient on each endpoint the framework itself calls, each timed and bounded at 15s. Whichever layer stops working localizes the fault -- network, JDK HTTP stack, async client, or a specific response. It deliberately does not extend the framework's base class, since that base class is what hangs; inheriting it would reproduce the symptom instead of isolating it. Reports rather than asserts (the leg is already failing and the evidence is the point), except that a failed TCP connect is fatal because nothing below it would mean anything. Wired into the compiled leg with continue-on-error so a diagnostic can never be what decides the leg. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
…he PR subset Five contracts declared `schedule: "nightly"`, so a pull_request run skipped them: dedup-consecutive-unsupported, disabled-join-type, division-by-zero, field-validation and head-without-sort. That left the required check scoring 3 of 19 rows on a PR, and it hid the triggers the multi-version relaxation rollup depends on — a rule with no scored case contributes no trigger census, so full-vs-partial cannot be judged for it on a PR at all. All 11 contracts now declare `schedule: "pr"`. A PR run reaches the whole corpus: 35 queries, 19 scored on the compiled surface (the other 16 are the runtime-bundle-only contracts, not-applicable there as before), up from 3 of 19. This DOES make the four advisory rules blocking, and that is a deliberate accepted trade rather than an oversight. Neither PplLintRuleValidationIT nor run-frontend-contract.mjs consults the manifest's `enforced` list — a contract that runs is a hard assertion — so `schedule` was the only thing keeping them non-blocking. Their oracles are genuinely weaker than the error rules': an advisory rule's query SUCCEEDS, so the contract can only assert a result shape or plain acceptance, and dedup-consecutive in particular depends on the Calcite-to-v2 fallback staying enabled. If one of them goes red, check the oracle before editing a rule. The manifest description, the IT javadoc and the README all claimed the split controlled blocking. Corrected: `enforced` / `nonEnforcing` record oracle quality and review status — how much to trust a red result — not whether one can occur. The schedule filter itself is kept, since it remains the only way to hold a new contract back from PR runs while its oracle settles. Verified: the full corpus exits 0 on the PR schedule with zero skips, against a live 3.8 engine and the real ACCOUNT fixture. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
The probe reported nothing: its step logged BUILD SUCCESSFUL in 2m45s with no probe output at all. It used JUnit 4's org.junit.Test while this module runs useJUnitPlatform(), so the class was collected as zero tests and the task succeeded vacuously -- the same shape of failure the PPL lint contract itself guards against, in the diagnostic meant to explain it. Switch to org.junit.jupiter.api.Test, matching PplLintRuleValidationIT. Also stop the step's grep from hiding evidence: add --info and match 'tests completed' and 'No tests found' so a zero-test run is visible next time instead of reading as a pass. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
…ually rejects
Moving every contract onto the PR schedule turned the required check red with 8
failures, 7 of them this: the trigger cross-check paired the detector against
`be.rejected`, which is only meaningful for a `rejection`-kind rule.
An advisory rule flags a query the engine runs happily — head-without-sort marks
non-determinism, division-by-zero marks a silent null, dedup-consecutive succeeds via
the Calcite-to-v2 fallback. For those, "detector flagged, backend accepted" is the
rule working as designed, so the check failed every advisory trigger
unconditionally, including ones that predate this branch. That, not runtime cost, is
the structural reason those contracts could only ever run nightly; I had attributed
it to cost and weaker oracles, which was wrong.
The check now runs only when the contract declares `backend.kind: "rejection"`. The
contracts already carry that distinction, so this reads data that exists rather than
adding a flag, and rejection rules are completely unaffected.
Advisory triggers keep full coverage from the two other assertions, which is why
relaxing the pairing is safe rather than merely convenient:
- the backend-kind check still fires if the engine starts REJECTING a query the
contract pinned as accepted;
- the `detectorCount` assertion still fires if the detector stops flagging it.
Every trigger in the corpus pins detectorCount: 1 regardless of kind, so a silent
advisory detector is still caught.
Verified by replaying the failed CI run's own artifacts (backend-report.json,
target.json, ppl-grammar-bundle.json downloaded from run 30289514275): same inputs go
from 6 failures to 0 on the compiled surface. Confirmed still selective by tampering
the backend report to make `disabled-join-type/right-join-disabled` — a rejection rule
— look accepted: that fails with both the backend-kind and the trigger assertion.
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Second vacuous pass from the same probe, different cause. The JUnit 5 switch was wrong: integTestRemote runs the default JUnit 4 runner -- only integJdbcTest calls useJUnitPlatform() -- so the jupiter @test made the class undiscoverable and Gradle reported 'No tests found for given includes: [**/*IT.class]' while the step still looked fine. But the JUnit 4 annotation alone was not enough either. Gradle only discovers an IT that inherits a runner from a framework base class; a standalone class has none and is collected as zero tests. That is why the FIRST version reported nothing despite compiling, matching the include pattern, and having its .class file in place. Extend OpenSearchTestCase: it supplies the randomized-testing runner but builds no REST client, so the probe is discovered without inheriting the client setup that hangs -- which was the whole reason for not extending the REST base class. Verified locally against a live cluster, all four layers reporting: tcp connect OK in 2ms HttpURLConnection _nodes/plugins OK in 8ms: HTTP 200, 9456 bytes RestClient _nodes/plugins OK in 54ms: HTTP 200, 9456 bytes RestClient _plugins/_ppl/_grammar OK in 20ms: HTTP 200, 248625 bytes Note for anyone running it by hand: the opensearch.rest-test plugin requires tests.rest.cluster, tests.cluster and tests.clustername to be all-null or all-non-null, or the project fails to configure before any test runs. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
…riants
Two fixes.
1. Test the LATEST PATCH, not <line>.0. The compiled matrix pinned 2.19.0 while
the published line is at 2.19.6 -- six patches stale, so it validated an engine
no user runs and would have attributed any bug fixed in between to the whole
2.19 line. Checked every line on Docker Hub: 2.19.6 is the only stale pin;
3.0.0 / 3.5.0 / 3.6.0 / 3.7.0 already are their lines' latest patch. Comments
now say to keep them latest rather than .0.
2. Probe client variants. The previous probe localized the 2.19 timeout precisely:
tcp connect OK 4ms
HttpURLConnection _nodes/plugins OK 27ms HTTP 200, 15844 bytes
RestClient _nodes/plugins FAILED 15396ms SocketTimeoutException
RestClient _cluster/health FAILED 15025ms SocketTimeoutException
Every endpoint fails, including a 459-byte health response, while the JDK's own
HTTP stack succeeds against the same URL. So the fault is in how the async
client speaks to this engine -- not the network, engine, response size, or any
one endpoint, which is why seven log-derived theories all missed it.
Rather than guess again, run candidate configurations side by side against the
same endpoint: FORCE_HTTP_1, NEGOTIATE, FORCE_HTTP_2, and a fresh single-
connection manager. Whichever succeeds names the fix; if none do, the client
cannot be configured around it and the answer is a client/engine version
constraint instead.
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
With the version pin corrected to 2.19.6, the leg gets past the client entirely and fails on the real incompatibility instead: PUT /_cluster/settings -> 400 persistent setting [plugins.calcite.enabled], not recognized Calcite is a 3.x feature and SQLIntegTestCase.init() sets that setting unconditionally, so every 2.x leg aborts before seeding a fixture or running a query. In observe-only mode, catch exactly that failure and continue without the setting: a pre-Calcite engine is a legitimate thing to observe, and each contract's own frontendContext.isCalcite already states what the linter should assume there. super.init() aborts partway when it throws, so redo the version-independent half (increaseMaxCompilationsRate). Matched narrowly -- on the setting name plus "not recognized", not on any 400 -- so a genuinely broken settings call on a Calcite-capable engine still fails rather than being waved through as "old engine". Asserting mode is unchanged: the required check runs against the PR's own build, where a missing Calcite setting is a real problem. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
…gine The init() tolerance worked -- the log shows 'observing without it' -- but the leg still failed, because applyClusterSettings re-applies the same setting per contract from backendFixture.clusterSettings, undoing it once per contract. Every setting in that block is Calcite-family (calcite, calciteFallback, allJoinTypesAllowed), and a pre-Calcite engine rejects all of them identically. So record support once in init() and skip the block, rather than wrapping each call in the same catch. Verified in the same run that the earlier fix landed: 2.19.6's REST client is healthy (_nodes/plugins 312ms, _cluster/health 6ms, FORCE_HTTP_1 / NEGOTIATE / fresh-conn-manager all OK; only FORCE_HTTP_2 fails, correctly, since 2.19 has no h2). There was never a client bug -- 2.19.0 was six patches stale. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Reverts 4c80f7b. That commit claimed HTTP/2 negotiation as the cause of the 2.19 observation-leg timeout. It was not, and the pin fixed nothing. The real cause was the version pin: the leg tested opensearchproject/opensearch: 2.19.0, six patches behind the 2.19.6 the line actually ships. On 2.19.6 the unmodified client is healthy -- _nodes/plugins in 312ms, _cluster/health in 6ms -- and the variant probe confirms it is not protocol-related at all: FORCE_HTTP_1 OK 18ms NEGOTIATE OK 21ms fresh-conn-manager OK 12ms FORCE_HTTP_2 FAILED (correctly: 2.19 has no h2) Since NEGOTIATE -- the default -- works, forcing 1.1 was a no-op dressed as a fix, and leaving it in would have suggested a protocol constraint that does not exist. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
The multi-version check flagged engine-message-changed on 3.7.0:
error.reason "There was internal problem at backend"
-> "Unexpected window function: dense_rank"
The >=3.7.0 <3.8.0 expectation for eventstats-dense-rank was copied from the
pre-3.7 epoch and never updated, while its sibling eventstats-rank in the SAME
epoch already pins the function-naming wording. 3.7 names the offending function;
only the pre-3.7 engines emit the generic message.
Detector-side verdict is unaffected, so this is the update-contract remediation
the classifier recommended -- not a rule change.
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Add channel-aware schema-v4 contracts for the approved twelve lint detectors and command-suggestion syntax feature. Keep default-off detector contracts dormant, enforce strict catalog wiring, and expand multi-version/discovery coverage across the active shipping corpus. Add the disabled-object backend fixture, normalized frontend and backend oracles, required-lane annotations, and syntax-aware aggregation. The exact OSD shipping census remains report-only until the paired OSD default-alignment change lands. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Pin exact diagnostic, deterministic-fix, and AI-action behavior for all active schema-v4 contracts, and promote the complete 13-contract corpus to required PR validation. Enforce the shipping census while retaining dormant detector contracts as report-only observations. Propagate frontend assertion failures through required and multi-version reports with inline annotations. Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Signed-off-by: Hanyu Wei <weihanyu@amazon.com>
Hanyu-W
force-pushed
the
codex/ppl-linter-multi-surface-restored
branch
from
August 5, 2026 04:28
4858b84 to
4381c28
Compare
Owner
Author
|
Superseded by upstream draft opensearch-project#5678. The branch was rebased onto current upstream main, made DCO-clean, and its compatibility design was aligned with the implemented three-configuration workflow. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Clean replacement for #7. Builds on #5 and includes the latest multi-surface PPL lint compatibility CI.
[Linter] PPL rule validationand[Linter] PPL multi-surface compatibility.Testing
node --test scripts/ppl-lint/__tests__/*.test.mjs(218 passing)actionlint .github/workflows/ppl-lint-multiversion-validation.yml .github/workflows/ppl-lint-rule-validation.ymlnode --check scripts/ppl-lint/aggregate-compatibility.mjsnode --check scripts/ppl-lint/plan-compatibility.mjsnode --check scripts/ppl-lint/run-frontend-contract.mjsgit diff --checkCheck List
--signoff.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.