Skip to content

[js] Add binding-neutral BiDi schema with cddl2ts-gated fidelity#17700

Open
titusfortner wants to merge 4 commits into
trunkfrom
project_bidi_schema
Open

[js] Add binding-neutral BiDi schema with cddl2ts-gated fidelity#17700
titusfortner wants to merge 4 commits into
trunkfrom
project_bidi_schema

Conversation

@titusfortner

@titusfortner titusfortner commented Jun 21, 2026

Copy link
Copy Markdown
Member

🔗 Related Issues

Builds on #17657 (shared BiDi CDDL ast/model artifacts).

💥 What does this PR do?

Generates a single, binding-neutral BiDi schema (commands + events + types) from the shared CDDL artifacts, so the non-JS bindings can create client generators that consume one explicit, normalized artifact instead of each re-deriving the native CDDL shapes from the raw AST.

That re-derivation is where the bindings currently diverge and silently drop data — inline string-literal enums, variant-union params, composed-in base-type fields, optional/nullable fields. The schema normalizes all of these once (hoists inline enums to named types, canonicalizes variant unions into self-contained records, flattens group composition, preserves wire names and nullability verbatim) and the generation step fails the build if anything is dropped or dangling.

It lands the artifact and its fidelity gate only; no binding consumes it yet.

🔧 Implementation Notes

  • Variant unions are flattened into self-contained variant records rather than cddl2ts's (A | B) & common intersection form, because the non-TS bindings can't express TS-style intersections cleanly. This duplicates common fields across variants but keeps each variant complete and the discriminator-conditional rule structural.
  • Test added to compare output with cddl2ts — to ensure nothing was dropped in the implementation
  • Synthesized types (hoisted enums/records, variant-union arms) carry { synthetic: true, owner, label }owner is the type the construct was lifted out of, label the member name within it — so a binding can keep the flat name or nest it (Owner::Label) without parsing the synthetic def name. Generation fails the build if a synthetic owner doesn't resolve.
  • The schema becomes the only binding-facing artifact: bidi-ast.json / bidi-model.json are made package-private (dropping the cross-binding visibility added in [js] Expose BiDi CDDL ast and model as shared artifacts #17657), since the schema folds in the model and supersedes both for bindings. They remain internal inputs to the schema and to the existing JS generation. Nothing consumed the old visibility yet, so this changes a not-yet-used contract.

🤖 AI assistance

  • No substantial AI assistance used
  • AI assisted (complete below)
    • Tool(s): Claude Code
    • What was generated: the AST normalizer, schema projector, the cddl2ts differential test, and the Bazel wiring
    • I reviewed all AI output and can explain the change

💡 Additional Considerations

  • No binding consumes the schema yet — will need to re-evaluate whether it is missing functionality needed by the bindings when it is actually used
  • Location — consider moving this tooling from in javascript/selenium-webdriver to javascript/bidi-support, the additional wiring seemed out of scope for this PR

🔄 Types of changes

  • New feature (non-breaking change which adds functionality and tests!)

@selenium-ci selenium-ci added C-nodejs JavaScript Bindings B-build Includes scripting, bazel and CI integrations labels Jun 21, 2026
Comment thread javascript/selenium-webdriver/bidi_schema_diff_test.mjs Fixed
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add binding-neutral BiDi schema generator with cddl2ts fidelity gate
✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Description

• Generate a normalized, binding-neutral BiDi schema artifact (commands/events/types) from shared
 AST/model.
• Normalize CDDL AST to hoist inline enums/records, canonicalize variants, and flatten composition
 safely.
• Gate schema fidelity with mocha tests that diff against cddl2ts and fail on drift.
Diagram

graph TD
  AST[("BiDi AST JSON")] --> Norm["normalize_bidi_ast.mjs"] --> Proj["project_bidi_schema.mjs"] --> Schema[("BiDi schema JSON")]
  Model[("BiDi model JSON")] --> Proj
  AST --> Diff["bidi_schema_diff_test.mjs"] --> Oracle["cddl2ts (TS oracle)"]
  Model --> Diff
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Have bindings consume the normalized AST directly
  • ➕ Avoids introducing a new schema vocabulary to maintain
  • ➕ Keeps representation closer to upstream CDDL constructs
  • ➖ Still forces each binding to implement CDDL-shape interpretation and type mapping
  • ➖ Harder to enforce completeness/fidelity guarantees across languages
2. Emit a standard JSON Schema / OpenAPI-like spec
  • ➕ Leverages existing validators and potential codegen ecosystems
  • ➕ More familiar artifact for non-JS ecosystems
  • ➖ BiDi’s union/variant semantics and wire-level naming/nullability don’t map cleanly
  • ➖ Significant additional design work; likely still needs custom extensions
3. Extend the existing model artifact instead of adding a new schema artifact
  • ➕ Fewer artifacts to version and distribute
  • ➕ Potentially smaller incremental change if model is already widely consumed
  • ➖ Model is command/event-focused; it’s not a complete, normalized type system
  • ➖ Risks re-introducing implicit derivation and divergence for complex types

Recommendation: Keep the current approach: a dedicated, binding-neutral schema projection with a fail-closed validation and an independent cddl2ts differential gate. It provides a single explicit artifact for non-JS bindings, removes per-binding re-derivation, and makes fidelity regressions visible in CI. Revisit standard-schema formats later if/when bindings need broader ecosystem tooling.

Files changed (7) +1468 / -0

Enhancement (2) +744 / -0
normalize_bidi_ast.mjsAdd BiDi AST normalizer to canonicalize enums, variants, and composition +465/-0

Add BiDi AST normalizer to canonicalize enums, variants, and composition

• Adds a pure normalizer pipeline that dedupes defs, hoists inline string-literal unions to named enums, hoists inline records to named defs, canonicalizes variant-union params into self-contained variant records, and flattens group composition (including Extensible wildcard) while avoiding dispatch-hierarchy flattening. Provides a CLI for emitting a normalized AST artifact.

javascript/selenium-webdriver/normalize_bidi_ast.mjs

project_bidi_schema.mjsProject normalized AST+model into a flat, binding-neutral schema with validators +279/-0

Project normalized AST+model into a flat, binding-neutral schema with validators

• Implements schema projection into a small, language-agnostic vocabulary (record/enum/union/alias and ref/primitive/const/list/map), preserving wire names and nullability. Adds referential-integrity validation (checkSchema) and a generator-independent completeness check that re-derives expected command/event methods from the raw AST, with a self-cleaning allowlist for known upstream omissions.

javascript/selenium-webdriver/project_bidi_schema.mjs

Tests (3) +664 / -0
bidi_schema_diff_test.mjsAdd oracle-style diff test comparing schema projection to cddl2ts +289/-0

Add oracle-style diff test comparing schema projection to cddl2ts

• Implements a mocha differential test that parses cddl2ts TypeScript output and compares record fields, optional/nullable/array fidelity, enum values, and union member field sets against the projected schema. Includes explicit allowlists for reviewed, intentional divergences and flags stale allowlist entries.

javascript/selenium-webdriver/bidi_schema_diff_test.mjs

normalize_bidi_ast_test.mjsAdd unit tests for AST normalization transforms +245/-0

Add unit tests for AST normalization transforms

• Adds mocha unit coverage for enum hoisting behavior, variant canonicalization (including name trimming and orphan cleanup), composition flattening rules, dedupe semantics, and immutability of normalizeAst. Tests target the edge cases that previously caused cross-binding divergence.

javascript/selenium-webdriver/normalize_bidi_ast_test.mjs

project_bidi_schema_test.mjsAdd tests for schema projection and fail-closed validation +130/-0

Add tests for schema projection and fail-closed validation

• Adds unit tests covering enum hoisting, inline-record hoisting, extensible map handling, referential-integrity failures, and completeness validation behavior (including allowlisted drops and stale allowlist detection). Uses small representative fixtures to exercise the schema vocabulary and validators.

javascript/selenium-webdriver/project_bidi_schema_test.mjs

Other (2) +60 / -0
BUILD.bazelAdd Bazel targets for schema projector and mocha schema tests +34/-0

Add Bazel targets for schema projector and mocha schema tests

• Introduces a new js_binary for the schema projector and a mocha_test target that runs unit tests plus a cddl2ts differential fidelity test. Wires in generated AST/model artifacts and required node modules as test data.

javascript/selenium-webdriver/BUILD.bazel

generate_bidi.bzlAdd schema generation step to generate_bidi_library macro +26/-0

Add schema generation step to generate_bidi_library macro

• Extends the Bazel macro to run a new schema projection step after AST/model generation, emitting a *_schema.json artifact. Adds a schema_generator parameter with a default and makes schema generation fail-closed via the script’s validations.

javascript/selenium-webdriver/private/generate_bidi.bzl

@qodo-code-review

qodo-code-review Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (10) 📘 Rule violations (4) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 14 rules

Grey Divider


Action required

1. Orphaned synthetic variant defs 🐞 Bug ≡ Correctness
Description
canonicalizeVariantParams() appends synthesized variant-record defs to created before confirming
all choice branches are supported, so a later bailout (continue) can leave behind unreferenced
synthetic defs while keeping the original def unchanged. This makes the normalized AST (and
therefore the projected schema) depend on branch order and can introduce unexpected extra types.
Code

javascript/selenium-webdriver/normalize_bidi_ast.mjs[R332-357]

+    const memberRefs = detected.branches.map((branch, i) => {
+      const entry = branchType(branch)
+      if (!entry) return null
+      const { fields, label, supersedes } = variantRecord(detected.commonFields, entry, defMap, i)
+      const memberLabel = trimRedundantPrefix(owner.local, label)
+      const localName = `${owner.local}_${memberLabel}`
+      const synthName = alloc(owner.domain ? `${owner.domain}.${localName}` : localName)
+      created.push({
+        Type: 'group',
+        Name: synthName,
+        IsChoiceAddition: false,
+        Properties: fields,
+        Comments: [],
+        'x-selenium-synthetic': true,
+        'x-selenium-owner': def.Name,
+        'x-selenium-label': memberLabel,
+      })
+      if (supersedes) {
+        superseded.add(supersedes)
+        supersededBy.set(supersedes, synthName)
+      }
+      return groupRef(synthName)
+    })
+
+    if (memberRefs.some((r) => r === null)) continue // unexpected branch shape: leave def untouched
+
Evidence
The implementation pushes synthesized defs into created inside the branch mapping, and only
afterwards checks for null branches and bails out, leaving those created defs in the final
result/kept list.

javascript/selenium-webdriver/normalize_bidi_ast.mjs[319-377]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`canonicalizeVariantParams()` mutates output even when it decides not to canonicalize a detected inline-variant record. It pushes synthesized variant defs into `created` during the per-branch map, but if any branch is unsupported it bails out (`continue`) and leaves the original def untouched. This results in orphan synthetic defs being appended to the output AST.

### Issue Context
The current control flow creates defs before validating that *all* branches are supported.

### Fix Focus Areas
- javascript/selenium-webdriver/normalize_bidi_ast.mjs[327-367]

### Suggested fix
Refactor to be atomic per `def`:
1. First, pre-validate and compute all `branchType()` entries for `detected.branches`.
2. If any entry is null/unsupported, do not create any synthesized defs and do not modify `def`.
3. Otherwise, build synthesized defs into a temporary local array (e.g., `newDefs`) and only then append them to `created` and rewrite `def` to `Type='variable'`.
4. Add a unit test covering an inline-variant record where one branch is unsupported, asserting that **no** synthesized defs are added to the output.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. AST/model targets made private 📘 Rule violation ≡ Correctness
Description
generate_bidi_library() now makes the generated AST/model artifacts package-private by omitting
visibility = _ARTIFACT_VISIBILITY, while still exposing the new schema target. This is a
backward-incompatible change for any cross-binding code that depended on those previously-visible
artifacts.
Code

javascript/selenium-webdriver/private/generate_bidi.bzl[R162-166]

+    # Step 2: parse the merged CDDL once into the reusable AST artifact. Internal
+    # input to the schema and the JS generator; not consumed by other bindings.
    ast_target = name + "_ast"
    ast_out = name + "_ast.json"
    js_run_binary(
Evidence
The backward-compatibility checklist forbids removing or restricting access to previously-public API
surfaces. In generate_bidi_library(), the new schema output is explicitly published to other
bindings via _ARTIFACT_VISIBILITY, but the AST/model steps are now documented and implemented as
package-private (no visibility), restricting cross-binding consumption.

Rule 389266: Maintain backward-compatible public API and ABI
javascript/selenium-webdriver/private/generate_bidi.bzl[5-11]
javascript/selenium-webdriver/private/generate_bidi.bzl[162-177]
javascript/selenium-webdriver/private/generate_bidi.bzl[179-194]
javascript/selenium-webdriver/private/generate_bidi.bzl[202-216]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR changes the Bazel visibility contract of the generated BiDi artifacts: `_ast` and `_json` are now package-private (no explicit `visibility`), while `_schema` remains exposed to other bindings. This can break existing (or soon-to-be-written) cross-binding dependencies.

## Issue Context
The file defines `_ARTIFACT_VISIBILITY` for `//java`, `//py`, and `//rb` subpackages, and applies it to the new schema generator output. The AST/model generation steps no longer apply the same visibility, effectively removing them from the cross-binding API surface.

## Fix Focus Areas
- javascript/selenium-webdriver/private/generate_bidi.bzl[162-194]
- javascript/selenium-webdriver/private/generate_bidi.bzl[196-216]
- javascript/selenium-webdriver/private/generate_bidi.bzl[5-11]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Undefined types project to null 🐞 Bug ☼ Reliability
Description
projectRef() returns { primitive: 'null', nullable: true } when given an empty type list; because
array/map projection passes potentially-undefined inputs (e.Values?.[0]?.Type, `e.ValueType ??
...`) into projectRef, missing element/value types are mis-projected as null instead of failing
closed. This can let malformed/unhandled AST shapes produce valid-looking schema output and evade
checkSchema’s unknown-type guard.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R77-120]

+const typeList = (t) => (Array.isArray(t) ? t : t === undefined || t === null ? [] : [t])
+const isLiteral = (e) => e && typeof e === 'object' && e.Type === 'literal'
+const isRef = (e) => e && typeof e === 'object' && e.Type === 'group' && typeof e.Value === 'string'
+
+// A `null` keyword or a `nil` prelude ref in a union means the value may be null.
+const isNullAlt = (e) =>
+  e === 'null' || (e && typeof e === 'object' && e.Type === 'group' && PRELUDE[e.Value] === 'null')
+
+function projectRef(type) {
+  const all = typeList(type)
+  const entries = all.filter((e) => !isNullAlt(e))
+  if (entries.length === 0) return { primitive: 'null', nullable: true } // the type is only `null`
+  const node =
+    entries.length > 1
+      ? entries.every(isLiteral)
+        ? { enum: entries.map((e) => e.Value) }
+        : { union: entries.map(projectEntry) }
+      : projectEntry(entries[0])
+  if (entries.length < all.length) node.nullable = true // a `null` alternative means the value may be null
+  return node
+}
+
+function projectEntry(e) {
+  if (typeof e === 'string') return { primitive: PRIMITIVES[e] ?? e }
+  if (!e || typeof e !== 'object') return { primitive: 'unknown' }
+  // A control operator (`.ge` / `.default` / `.le` …) wraps the real type as
+  // `{ Type: <innerType>, Operator: {...} }`; the constraint does not change the
+  // type, so project the inner type.
+  if (e.Type && typeof e.Type === 'object') return projectEntry(e.Type)
+  if (e.Type === 'literal') return { const: e.Value }
+  if (e.Type === 'group' && e.Value) return e.Value in PRELUDE ? { primitive: PRELUDE[e.Value] } : { ref: e.Value }
+  if (e.Type === 'group' && Array.isArray(e.Properties)) {
+    // An inline group that only wraps anonymous ref(s) — e.g. a union arm
+    // `{ DateLocalValue }` — is that ref (or a union of them), not a record.
+    const refs = unionMemberRefs(e)
+    if (refs) return refs.length === 1 ? { ref: refs[0] } : { union: refs.map((r) => ({ ref: r })) }
+    return {
+      record: e.Properties.flat()
+        .filter((p) => p?.Name)
+        .map(projectField),
+    }
+  }
+  if (e.Type === 'array') return { list: projectRef(e.Values?.[0]?.Type) }
+  if (e.Type === 'map') return { map: projectRef(e.ValueType ?? e.Values?.[0]?.Type), extensible: true }
Evidence
typeList collapses undefined to an empty list, and projectRef interprets an empty list as the
null-only type; array/map projection can pass undefined into projectRef via optional chaining;
checkSchema only checks for primitive === 'unknown', so this misprojection is not caught.

javascript/selenium-webdriver/project_bidi_schema.mjs[77-97]
javascript/selenium-webdriver/project_bidi_schema.mjs[119-120]
javascript/selenium-webdriver/project_bidi_schema.mjs[386-399]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`projectRef()` treats an empty `typeList()` as “null-only type” and returns `{ primitive: 'null', nullable: true }`. Because array/map projection calls `projectRef(e.Values?.[0]?.Type)` / `projectRef(e.ValueType ?? e.Values?.[0]?.Type)`, any missing/empty element/value type is silently mapped to `null` rather than to `unknown` or an error.

### Issue Context
This undercuts the schema gate’s stated “fail-closed” behavior, since `checkSchema()` only flags `primitive === 'unknown'`.

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[77-97]
- javascript/selenium-webdriver/project_bidi_schema.mjs[119-120]
- javascript/selenium-webdriver/project_bidi_schema.mjs[386-415]

Suggested approach:
- Make `projectRef(undefined)` (and `projectRef(null)` when it indicates “missing”, not a CDDL null alternative) project to `{ primitive: 'unknown' }` (or throw) rather than the null-only type.
 - One option: change `typeList` to return a sentinel for undefined, or add an explicit guard at the start of `projectRef`:
   - `if (type === undefined) return { primitive: 'unknown' }`
- In the `array`/`map` cases, explicitly validate the presence of an element/value type and project `unknown` (so `checkSchema` fails) when absent.
- Add a unit test asserting `projectRef(undefined)` is treated as `unknown` (or that an empty array/map element type fails schema validation) to prevent regression.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (7)
4. Ordered selector skips aliases 🐞 Bug ≡ Correctness
Description
unionSelector() computes selector.ordered[].requires only when the variant is a direct record;
alias (and other non-record) variants get requires: [], which makes them match trivially and can
cause incorrect structural-union dispatch.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R284-290]

+  // No shared discriminator: dispatch by required-field presence, in spec order.
+  return {
+    ordered: variants.map((ref) => {
+      const t = types[ref]
+      const requires = t?.kind === 'record' ? t.fields.filter((f) => f.required).map((f) => f.name) : []
+      return { ref, requires }
+    }),
Evidence
The codebase itself indicates aliases can appear in union graphs (they are resolved by
unionLeaves() and tagContribution()), but the structural selector branch doesn’t resolve them
and defaults to requires: []. Validation (checkSelector) only checks that refs resolve and
explicitly allows empty requires, so this can ship undetected. Current tests cover only record
variants, so they won’t catch alias cases.

javascript/selenium-webdriver/project_bidi_schema.mjs[200-210]
javascript/selenium-webdriver/project_bidi_schema.mjs[232-246]
javascript/selenium-webdriver/project_bidi_schema.mjs[284-291]
javascript/selenium-webdriver/project_bidi_schema.mjs[419-423]
javascript/selenium-webdriver/project_bidi_schema_test.mjs[114-124]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`unionSelector()` falls back to a structural selector `{ ordered: [...] }` when no discriminator key works. In that branch it derives `requires` only for variants whose projected kind is `record`; if a variant is an `alias` to a record (or a nested `union`), it gets `requires: []`, which makes that variant always “selectable” and can cause the structural dispatch to pick the wrong arm.

### Issue Context
The same file already treats alias-to-record as a normal part of the union graph (`unionLeaves()` follows `kind: 'alias'`), and tagged-discriminator derivation (`tagContribution`) also follows aliases. The structural selector should be consistent with those paths.

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[200-211]
- javascript/selenium-webdriver/project_bidi_schema.mjs[264-292]
- javascript/selenium-webdriver/project_bidi_schema.mjs[406-427]
- javascript/selenium-webdriver/project_bidi_schema_test.mjs[114-124]
- javascript/selenium-webdriver/project_bidi_schema_test.mjs[234-249]

### Suggested fix
1. When building the structural selector, resolve each immediate variant to its leaf records (reuse `unionLeaves(ref, types)`), and build `ordered` entries from those leaf records (preserving spec order by iterating variants in-order, then their leaves in-order).
2. Compute `requires` from the resolved leaf record’s required fields.
3. Add a unit test where a structural union includes an alias variant (e.g. `x.Alias` is a single-ref alias to `x.A`), and assert the `requires` for that ordered entry reflects `x.A` (not `[]`).
4. (Optional but recommended) Strengthen `checkSelector()` to flag an `ordered` selector entry with `requires: []` when it would shadow later variants (e.g., an empty-requires entry that is not last), since that makes subsequent variants unreachable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. projectRef() null-only untested 📘 Rule violation ▣ Testability
Description
projectRef() now treats a null/nil-only type as { primitive: 'null', nullable: true }, but
the new behavior is not covered by an automated test, risking regressions in schema fidelity. This
violates the requirement that new bug fixes include corresponding tests with assertions.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R77-81]

+function projectRef(type) {
+  const all = typeList(type)
+  const entries = all.filter((e) => !isNullAlt(e))
+  if (entries.length === 0) return { primitive: 'null', nullable: true } // the type is only `null`
+  const node =
Evidence
PR Compliance ID 389273 requires tests for new bug fixes. The updated projectRef() now
special-cases the entries.length === 0 case (meaning the type was only null/nil) to return a
concrete null primitive, but the nearby projector tests do not include any case that asserts this
null-only projection behavior.

Rule 389273: Require tests for all new functionality and bug fixes
javascript/selenium-webdriver/project_bidi_schema.mjs[73-81]
javascript/selenium-webdriver/project_bidi_schema_test.mjs[83-162]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`projectRef()` has new behavior for types that are only `null`/`nil`, but there is no regression test asserting the projected schema contains `primitive: 'null'` (not `unknown`) and `nullable: true`.

## Issue Context
This is a schema-fidelity bugfix path; without a targeted test it can regress without being noticed.

## Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[73-88]
- javascript/selenium-webdriver/project_bidi_schema_test.mjs[83-162]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Null-only types become unknown ✓ Resolved 🐞 Bug ≡ Correctness
Description
In projectRef(), null and prelude nil alternatives are filtered out before projecting the
remaining type; if the input type is *only* null/nil, the remaining list is empty and the schema
emits { primitive: 'unknown', nullable: true } instead of a real null type. This can silently
corrupt schema fidelity for any CDDL field/alias defined as exactly null/nil, and the cddl2ts
diff check likely won’t catch it because it only asserts presence of nullability, not the underlying
primitive.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R73-86]

+// A `null` keyword or a `nil` prelude ref in a union means the value may be null.
+const isNullAlt = (e) =>
+  e === 'null' || (e && typeof e === 'object' && e.Type === 'group' && PRELUDE[e.Value] === 'null')
+
+function projectRef(type) {
+  const all = typeList(type)
+  const entries = all.filter((e) => !isNullAlt(e))
+  const node =
+    entries.length > 1
+      ? entries.every(isLiteral)
+        ? { enum: entries.map((e) => e.Value) }
+        : { union: entries.map(projectEntry) }
+      : projectEntry(entries[0])
+  if (entries.length < all.length) node.nullable = true // a `null` alternative means the value may be null
Evidence
The code explicitly maps nil to a null primitive, but then classifies nil as a nullable-union
alternative and filters it out. When all contains only nil/null, entries becomes empty, so
projectEntry(entries[0]) is invoked with undefined and returns { primitive: 'unknown' }, after
which nullable is set because entries.length < all.length.

javascript/selenium-webdriver/project_bidi_schema.mjs[49-95]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`projectRef()` removes `null`/`nil` alternatives via `isNullAlt()`, then projects `entries[0]`. When the type is exactly `null`/`nil` (no non-null alternatives), `entries` becomes empty and `projectEntry(undefined)` returns `{ primitive: 'unknown' }`, producing an incorrect schema node.

### Issue Context
- `PRELUDE.nil` is mapped to `'null'`, and `projectEntry()` can correctly project a `nil` group ref to `{ primitive: 'null' }`.
- But `isNullAlt()` strips it before projection, which is only correct when there is at least one non-null alternative.

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[73-87]

### Suggested fix
- In `projectRef(type)`, after computing `entries`, add a guard:
 - If `entries.length === 0` and `all.length > 0`, return `{ primitive: 'null' }` (and **do not** set `nullable`), because the type is exactly null.
 - Only set `node.nullable = true` when there is at least one non-null alternative (i.e., `entries.length > 0 && entries.length < all.length`).
- Optionally add/extend a unit test to cover `nil`-only and `null`-only projection cases.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Alias refs unchecked ✓ Resolved 🐞 Bug ≡ Correctness
Description
checkSchema() never traverses kind: 'alias' nodes, so an alias whose RHS contains a {ref: ...}
can point at a missing type and still pass validation. This breaks the “fail-closed” schema gate by
allowing dangling references to ship undetected.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R166-200]

+/** Fail-closed validation: every ref resolves, and every ONE_OF entry is real. */
+export function checkSchema(schema) {
+  const errors = []
+  const has = (name) => Object.hasOwn(schema.types, name)
+  const refsIn = (node) =>
+    !node
+      ? []
+      : node.ref
+        ? [node.ref]
+        : node.list
+          ? refsIn(node.list)
+          : node.map
+            ? refsIn(node.map)
+            : node.union
+              ? node.union.flatMap(refsIn)
+              : node.record
+                ? node.record.flatMap((f) => refsIn(f.type))
+                : []
+
+  for (const c of [...schema.commands, ...schema.events]) {
+    for (const r of [...refsIn(c.params), ...refsIn(c.result ?? null)])
+      if (!has(r)) errors.push(`${c.method}: unresolved type ${r}`)
+  }
+  for (const [name, node] of Object.entries(schema.types)) {
+    if (node.kind === 'record')
+      for (const f of node.fields)
+        for (const r of refsIn(f.type)) if (!has(r)) errors.push(`${name}.${f.name}: unresolved type ${r}`)
+    if (node.kind === 'union')
+      for (const v of node.variants) if (!has(v)) errors.push(`${name}: unresolved variant ${v}`)
+    for (const field of node.oneOf ?? [])
+      if (node.kind === 'union' ? false : !node.fields?.some((f) => f.name === field))
+        errors.push(`oneOf(${name}): no such field ${field}`)
+  }
+  for (const name of Object.keys(ONE_OF)) if (!has(name)) errors.push(`oneOf: unknown type ${name}`)
+  return errors
Evidence
The projector can emit kind: 'alias' types, but the validator loop only checks record and
union nodes; it never validates refs that appear inside an alias RHS.

javascript/selenium-webdriver/project_bidi_schema.mjs[106-116]
javascript/selenium-webdriver/project_bidi_schema.mjs[166-200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`checkSchema()` validates refs for command/event params/results, record fields, and union variants, but it does not validate references contained inside `kind: 'alias'` nodes (the alias RHS). This allows unresolved/dangling refs to pass schema validation.

### Issue Context
Aliases are produced by `projectType()` for `variable` defs that are not pure enums and not unions of refs.

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[106-116]
- javascript/selenium-webdriver/project_bidi_schema.mjs[166-200]

### Proposed fix
- In `checkSchema()`, add a branch for `node.kind === 'alias'` that runs `refsIn(node.type)` and errors on any ref that does not exist in `schema.types`.
- Ensure `refsIn` can traverse the alias RHS as-is (it already handles `ref/list/map/union/record` type-ref shapes).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Record map refs unchecked ✓ Resolved 🐞 Bug ≡ Correctness
Description
projectRecord() can emit typed maps via record.map (from * text => T), but checkSchema()
only validates record.fields and ignores record.map. As a result, unresolved refs inside the map
value type won’t be caught by schema validation.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R126-198]

+function projectRecord(def) {
+  const record = { kind: 'record', fields: [] }
+  for (const prop of (def.Properties ?? []).flat()) {
+    if (!prop || typeof prop !== 'object') continue
+    // `m === null` is overloaded in this parser: a key-typed entry is a map
+    // (`* text => value`); an anonymous entry is a structural spread; everything
+    // else is just an optional field (the `?` quantifier). Only the first two
+    // are not real fields.
+    if (prop.Occurrence?.m === null && (!prop.Name || prop.Name in PRIMITIVES || prop.Name in PRELUDE)) {
+      if (prop.Name in PRIMITIVES || prop.Name in PRELUDE) {
+        const value = projectRef(prop.Type)
+        if (value.primitive === 'any') record.extensible = true
+        else record.map = value
+      }
+      continue
+    }
+    if (prop.Name) record.fields.push(projectField(prop))
+  }
+  return record
+}
+
+const typeRef = (name) => (name ? { ref: name } : null)
+
+/** Build the flat schema from the raw AST and the existing command/event model. */
+export function projectSchema(ast, model) {
+  const types = {}
+  for (const def of normalizeAst(ast)) if (def?.Name) types[def.Name] = projectType(def)
+
+  const commands = []
+  const events = []
+  for (const [domain, entry] of Object.entries(model)) {
+    for (const c of entry.commands ?? [])
+      commands.push({ domain, method: c.method, name: c.name, params: typeRef(c.params), result: typeRef(c.result) })
+    for (const e of entry.events ?? [])
+      events.push({ domain, method: e.method, name: e.name, params: typeRef(e.params) })
+  }
+
+  return { schemaVersion: 1, commands, events, types }
+}
+
+/** Fail-closed validation: every ref resolves, and every ONE_OF entry is real. */
+export function checkSchema(schema) {
+  const errors = []
+  const has = (name) => Object.hasOwn(schema.types, name)
+  const refsIn = (node) =>
+    !node
+      ? []
+      : node.ref
+        ? [node.ref]
+        : node.list
+          ? refsIn(node.list)
+          : node.map
+            ? refsIn(node.map)
+            : node.union
+              ? node.union.flatMap(refsIn)
+              : node.record
+                ? node.record.flatMap((f) => refsIn(f.type))
+                : []
+
+  for (const c of [...schema.commands, ...schema.events]) {
+    for (const r of [...refsIn(c.params), ...refsIn(c.result ?? null)])
+      if (!has(r)) errors.push(`${c.method}: unresolved type ${r}`)
+  }
+  for (const [name, node] of Object.entries(schema.types)) {
+    if (node.kind === 'record')
+      for (const f of node.fields)
+        for (const r of refsIn(f.type)) if (!has(r)) errors.push(`${name}.${f.name}: unresolved type ${r}`)
+    if (node.kind === 'union')
+      for (const v of node.variants) if (!has(v)) errors.push(`${name}: unresolved variant ${v}`)
+    for (const field of node.oneOf ?? [])
+      if (node.kind === 'union' ? false : !node.fields?.some((f) => f.name === field))
+        errors.push(`oneOf(${name}): no such field ${field}`)
+  }
Evidence
The projector places the typed map’s value type into record.map, but the validator’s record branch
only iterates node.fields, so refs inside record.map are never checked.

javascript/selenium-webdriver/project_bidi_schema.mjs[126-145]
javascript/selenium-webdriver/project_bidi_schema.mjs[189-194]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`projectRecord()` stores typed-map value types under `record.map`, but `checkSchema()` does not validate refs inside `record.map`. This allows dangling type references to slip through the schema gate for records that use `* text => SomeType`.

### Issue Context
The schema vocabulary explicitly supports maps on records, and `projectRecord()` sets `record.map = value` when the wildcard entry is not `any`.

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[126-145]
- javascript/selenium-webdriver/project_bidi_schema.mjs[166-200]

### Proposed fix
- In `checkSchema()`, within the `node.kind === 'record'` branch, also validate `node.map` if present:
 - `for (const r of refsIn(node.map)) if (!has(r)) errors.push(`${name}: unresolved map value type ${r}`)`
- Keep existing `fields` validation unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. projectSchema() JSDoc incomplete 📘 Rule violation ✧ Quality
Description
Several newly exported schema-related functions are missing complete JSDoc blocks with required
@param and @returns tags (and @throws where applicable), including projectSchema,
checkSchema, checkCompleteness, and multiple exports in normalize_bidi_ast.mjs. This violates
the compliance requirement to fully document exported APIs and reduces contract fidelity and
cross-binding/tooling clarity.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R149-279]

+/** Build the flat schema from the raw AST and the existing command/event model. */
+export function projectSchema(ast, model) {
+  const types = {}
+  for (const def of normalizeAst(ast)) if (def?.Name) types[def.Name] = projectType(def)
+
+  const commands = []
+  const events = []
+  for (const [domain, entry] of Object.entries(model)) {
+    for (const c of entry.commands ?? [])
+      commands.push({ domain, method: c.method, name: c.name, params: typeRef(c.params), result: typeRef(c.result) })
+    for (const e of entry.events ?? [])
+      events.push({ domain, method: e.method, name: e.name, params: typeRef(e.params) })
+  }
+
+  return { schemaVersion: 1, commands, events, types }
+}
+
+/** Fail-closed validation: every ref resolves, and every ONE_OF entry is real. */
+export function checkSchema(schema) {
+  const errors = []
+  const has = (name) => Object.hasOwn(schema.types, name)
+  const refsIn = (node) =>
+    !node
+      ? []
+      : node.ref
+        ? [node.ref]
+        : node.list
+          ? refsIn(node.list)
+          : node.map
+            ? refsIn(node.map)
+            : node.union
+              ? node.union.flatMap(refsIn)
+              : node.record
+                ? node.record.flatMap((f) => refsIn(f.type))
+                : []
+
+  for (const c of [...schema.commands, ...schema.events]) {
+    for (const r of [...refsIn(c.params), ...refsIn(c.result ?? null)])
+      if (!has(r)) errors.push(`${c.method}: unresolved type ${r}`)
+  }
+  for (const [name, node] of Object.entries(schema.types)) {
+    if (node.kind === 'record')
+      for (const f of node.fields)
+        for (const r of refsIn(f.type)) if (!has(r)) errors.push(`${name}.${f.name}: unresolved type ${r}`)
+    if (node.kind === 'union')
+      for (const v of node.variants) if (!has(v)) errors.push(`${name}: unresolved variant ${v}`)
+    for (const field of node.oneOf ?? [])
+      if (node.kind === 'union' ? false : !node.fields?.some((f) => f.name === field))
+        errors.push(`oneOf(${name}): no such field ${field}`)
+  }
+  for (const name of Object.keys(ONE_OF)) if (!has(name)) errors.push(`oneOf: unknown type ${name}`)
+  return errors
+}
+
+// ============================================================
+// CLI: raw ast + model → flat schema (validated)
+//   node project_bidi_schema.mjs --ast <ast.json> --model <model.json> --dump-schema <out.json>
+// ============================================================
+
+async function main() {
+  const { parseArgs } = await import('node:util')
+  const { readFileSync, writeFileSync } = await import('node:fs')
+  const { resolve } = await import('node:path')
+
+  // Under Bazel the js_binary wrapper chdir's to BAZEL_BINDIR, but $(location)
+  // inputs are execroot-relative and already carry that prefix — strip it so the
+  // path is not doubled. Mirrors resolveInputPath() in generate_bidi.mjs.
+  const resolveInput = (p) => {
+    if (!process.env.BAZEL_BINDIR) return resolve(p)
+    const prefix = process.env.BAZEL_BINDIR.replaceAll('\\', '/') + '/'
+    const norm = p.replaceAll('\\', '/')
+    return resolve(norm.startsWith(prefix) ? norm.slice(prefix.length) : norm)
+  }
+
+  const { values: args } = parseArgs({
+    options: { ast: { type: 'string' }, model: { type: 'string' }, 'dump-schema': { type: 'string' } },
+  })
+  if (!args.ast || !args.model || !args['dump-schema']) {
+    console.error('Usage: project_bidi_schema.mjs --ast <ast.json> --model <model.json> --dump-schema <out.json>')
+    process.exit(1)
+  }
+
+  const ast = JSON.parse(readFileSync(resolveInput(args.ast), 'utf8'))
+  const model = JSON.parse(readFileSync(resolveInput(args.model), 'utf8'))
+  const schema = projectSchema(ast, model)
+
+  // Generation is the gate: a broken or incomplete schema fails the build.
+  const errors = [...checkSchema(schema), ...checkCompleteness(ast, schema)]
+  if (errors.length) {
+    console.error('BiDi schema validation failed:')
+    errors.forEach((e) => console.error(`  ${e}`))
+    process.exit(1)
+  }
+
+  writeFileSync(resolve(args['dump-schema']), JSON.stringify(schema, null, 2) + '\n', 'utf8')
+  console.log(
+    `  ${schema.commands.length} commands, ${schema.events.length} events, ${Object.keys(schema.types).length} types → ${args['dump-schema']}`,
+  )
+}
+
+if (import.meta.main) {
+  main().catch((err) => {
+    console.error(err)
+    process.exit(1)
+  })
+}
+
+/**
+ * Independent completeness check: re-derive every command/event method straight
+ * from the raw AST (a leaf def carries a literal `method` property) and assert it
+ * survived into the schema. This compares input to output without trusting the
+ * generator, so a dropped command/event fails the build even if generation and
+ * its own checkSchema agree. Run as a Bazel test over committed fixtures.
+ */
+export function checkCompleteness(rawAst, schema) {
+  const emitted = new Set([...schema.commands, ...schema.events].map((c) => c.method))
+  const errors = []
+  for (const def of rawAst) {
+    const methodProp = (def.Properties ?? []).flat().find((p) => p?.Name === 'method')
+    const literal = methodProp && (Array.isArray(methodProp.Type) ? methodProp.Type[0] : methodProp.Type)
+    if (literal?.Type !== 'literal') continue
+    if (!emitted.has(literal.Value) && !KNOWN_INCOMPLETE.has(literal.Value))
+      errors.push(`dropped from schema: ${literal.Value}`)
+  }
+  // Self-cleaning: if a known-incomplete method is now emitted, the entry is
+  // stale and must be removed — so the allowlist cannot silently rot.
+  for (const known of KNOWN_INCOMPLETE) {
+    if (emitted.has(known)) errors.push(`stale KNOWN_INCOMPLETE entry (now emitted, remove it): ${known}`)
+  }
+  return errors
+}
Evidence
The compliance rule mandates that every exported function has a complete JSDoc block. The cited
exports projectSchema(ast, model), checkSchema(schema), and checkCompleteness(rawAst, schema)
in project_bidi_schema.mjs are documented with prose but lack the required @param and @returns
tags, and normalize_bidi_ast.mjs contains several export function ... declarations (e.g.,
hoistInlineEnums, hoistInlineRecords, canonicalizeVariantParams, flattenGroupComposition,
dedupeDefs, normalizeAst) that similarly have only prose comments and do not include the
required JSDoc tags, demonstrating noncompliance for newly exported APIs.

Rule 389257: Document public API functions with complete JSDoc blocks
javascript/selenium-webdriver/project_bidi_schema.mjs[149-201]
javascript/selenium-webdriver/project_bidi_schema.mjs[263-279]
javascript/selenium-webdriver/normalize_bidi_ast.mjs[152-432]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Exported functions in `javascript/selenium-webdriver/project_bidi_schema.mjs` and `javascript/selenium-webdriver/normalize_bidi_ast.mjs` are missing complete JSDoc blocks. Update each affected exported function to include a full JSDoc comment immediately preceding the declaration with a description plus `@param` tags for all parameters (in order), `@returns` for any non-void return values, and `@throws` where synchronous throws are possible.

## Issue Context
These exports are part of a binding-neutral schema toolchain, so consumers, maintainers, and tooling rely on JSDoc for a clear, machine-readable contract. The compliance rule requires a complete JSDoc block for each exported function declaration, not just prose.

## Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[149-201]
- javascript/selenium-webdriver/project_bidi_schema.mjs[263-279]
- javascript/selenium-webdriver/normalize_bidi_ast.mjs[152-432]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Schema generator never runs 🐞 Bug ≡ Correctness
Description
project_bidi_schema.mjs (and normalize_bidi_ast.mjs) only calls main() behind `if
(import.meta.main), but Selenium’s Bazel Node toolchain is 22.x where import.meta.main` is not
set, so the js_binary will do nothing and the js_run_binary action will fail to write its declared
output.
Code

javascript/selenium-webdriver/project_bidi_schema.mjs[R249-254]

+if (import.meta.main) {
+  main().catch((err) => {
+    console.error(err)
+    process.exit(1)
+  })
+}
Evidence
The generator’s main() is only executed if import.meta.main is truthy, but Bazel pins Node
22.22.0, and the normalizer file itself documents import.meta.main as a Node >=24 feature, so on
Node 22 this guard will be falsy and the CLI entrypoint won’t run.

javascript/selenium-webdriver/project_bidi_schema.mjs[249-254]
javascript/selenium-webdriver/normalize_bidi_ast.mjs[458-465]
MODULE.bazel[78-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`project_bidi_schema.mjs` and `normalize_bidi_ast.mjs` gate execution with `if (import.meta.main)`, but Bazel config pins Node 22, where `import.meta.main` is not available. As a result, these scripts won't run when invoked as `js_binary` tools, and schema generation will fail due to missing outputs.

### Issue Context
- Bazel Node toolchain is pinned to Node 22.
- The schema generator is executed via `js_run_binary`, and must always run when invoked as the entry module.

### Fix
Replace the `import.meta.main` check with a Node-22-compatible “is this the entry module?” test, for example:

```js
import { pathToFileURL, fileURLToPath } from 'node:url'
import { resolve } from 'node:path'

function isMainModule() {
 if (!process.argv[1]) return false
 return pathToFileURL(resolve(process.argv[1])).href === import.meta.url
}

if (isMainModule()) {
 main().catch((err) => {
   console.error(err)
   process.exit(1)
 })
}
```

(Any equivalent guard that works on Node 22 is fine.)

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[249-254]
- javascript/selenium-webdriver/normalize_bidi_ast.mjs[458-465]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

11. Nullable drift unchecked 🐞 Bug ≡ Correctness ⭐ New
Description
In diffAgainstCddl2ts(), nullability mismatches are only reported when cddl2ts marks a field
nullable but the schema does not; the reverse case (schema nullable but cddl2ts not) is never
flagged, so accidental nullability expansion can slip past the fidelity gate.
Code

javascript/selenium-webdriver/bidi_schema_diff_test.mjs[R293-294]

+        if (o.nullable && !field.type?.nullable && !allowNullable.has(fname))
+          errors.push(`${name}.${fname}: cddl2ts is nullable, schema is not`)
Evidence
The record-field fidelity loop only contains a nullable mismatch check guarded by o.nullable, and
there is no corresponding check for !o.nullable && field.type?.nullable, so schema-side
nullability additions are not detected.

javascript/selenium-webdriver/bidi_schema_diff_test.mjs[285-296]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`diffAgainstCddl2ts()` currently checks nullability in only one direction:
- it errors when `cddl2ts` says a field is nullable and the schema is not
- it **does not** error when the schema marks a field nullable but `cddl2ts` does not

This weakens the fidelity gate by allowing schema-side nullability drift (e.g., mistakenly adding `null` to a field type) to pass.

### Issue Context
This logic is in the record-field type fidelity loop that compares optional/nullable/array characteristics between the projected schema and the cddl2ts oracle.

### Fix Focus Areas
- javascript/selenium-webdriver/bidi_schema_diff_test.mjs[285-299]

### Suggested change
Add a symmetric check such as:
- if `!o.nullable && field.type?.nullable` then push an error like: `${name}.${fname}: schema is nullable, cddl2ts is not`

Keep the existing `NULLABLE_DIFFERENCES` allowlist behavior as-is for the known `cddl2ts nullable, schema not` direction (and let the existing `stale NULLABLE_DIFFERENCES` logic catch resolved allowlist entries).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Union diff misses variant drops 🐞 Bug ☼ Reliability
Description
bidi_schema_diff_test compares union fields using schemaTypeFields(), which unions field names
across all variants, so it cannot detect fields dropped from only one variant as long as another
variant still contains the field. This weakens the fidelity gate specifically for the variant-union
normalization, where common fields are duplicated into each synthesized variant record.
Code

javascript/selenium-webdriver/bidi_schema_diff_test.mjs[R224-306]

+/** Collect the flattened field names a schema type contributes (through unions and aliases). */
+function schemaTypeFields(name, types, fields = new Set(), seen = new Set()) {
+  if (seen.has(name)) return fields
+  seen.add(name)
+  const t = types[name]
+  if (!t) return fields
+  if (t.kind === 'record') t.fields.forEach((f) => fields.add(f.name))
+  else if (t.kind === 'union') t.variants.forEach((v) => schemaTypeFields(v, types, fields, seen))
+  else if (t.kind === 'alias' && t.type?.ref) schemaTypeFields(t.type.ref, types, fields, seen)
+  return fields
+}
+
+/**
+ * Compare the generated schema against the cddl2ts oracle.
+ * @param {object} schema The generated schema artifact (`{commands, events, types}`).
+ * @param {object[]} ast The parsed CDDL AST (fed to cddl2ts).
+ * @returns {string[]} Difference messages; empty means the schema matches cddl2ts.
+ */
+function diffAgainstCddl2ts(schema, ast) {
+  const parsed = parseCddl2ts(transform(ast))
+  const { interfaces, enums, aliases } = parsed
+  const errors = []
+
+  for (const [name, node] of Object.entries(schema.types)) {
+    if (node.kind === 'record') {
+      const oracle = interfaces[tsName(name)]
+      if (!oracle) {
+        const alias = aliases[tsName(name)]
+        if (alias?.includes('&')) {
+          // A composed record cddl2ts emits as `Base & {...}` — field-compare it,
+          // so a dropped composition (e.g. an un-flattened base type) is caught.
+          const expected = expectedUnionFields(alias, parsed)
+          const mine = new Set(node.fields.map((f) => f.name))
+          const allow = new Set(RECORD_ALIAS_DIFFERENCES[name]?.fields ?? [])
+          const missing = [...expected].filter((f) => !mine.has(f) && !allow.has(f))
+          if (missing.length) errors.push(`${name}: composed record missing fields cddl2ts has: ${missing.join(', ')}`)
+        } else if (node.fields.length === 0 && !node.map && !node.extensible && alias) {
+          // A fieldless record where cddl2ts emits a list/union alias means the
+          // element type was dropped (e.g. a top-level `[*T]` or `a // b`).
+          errors.push(
+            `${name}: projected as an empty record but cddl2ts emits a type alias (dropped list/union element type)`,
+          )
+        }
+        continue
+      }
+      const oracleNames = Object.keys(oracle)
+      const mine = new Map(node.fields.map((f) => [f.name, f]))
+      const allow = new Set(KNOWN_DIFFERENCES[name]?.fields ?? [])
+      const missing = oracleNames.filter((f) => !mine.has(f) && !allow.has(f))
+      const stale = [...allow].filter((f) => mine.has(f) || !(f in oracle))
+      if (missing.length) errors.push(`${name}: missing fields cddl2ts has: ${missing.join(', ')}`)
+      if (stale.length) errors.push(`${name}: stale KNOWN_DIFFERENCES fields (resolved, remove): ${stale.join(', ')}`)
+      // Type fidelity for fields present in both: optional / nullable / array.
+      for (const [fname, field] of mine) {
+        const o = oracle[fname]
+        if (!o) continue
+        if (o.optional === field.required)
+          errors.push(
+            `${name}.${fname}: optional mismatch (cddl2ts optional=${o.optional}, schema required=${field.required})`,
+          )
+        if (o.nullable && !field.type?.nullable) errors.push(`${name}.${fname}: cddl2ts is nullable, schema is not`)
+        if (o.array && !field.type?.list) errors.push(`${name}.${fname}: cddl2ts is array, schema is not`)
+      }
+    } else if (node.kind === 'enum') {
+      const oracle = enums[tsName(name)]
+      if (!oracle) continue // hoisted/synthetic enums have no named cddl2ts counterpart
+      const mine = new Set(node.values)
+      const missing = [...oracle].filter((v) => !mine.has(v))
+      const extra = [...mine].filter((v) => !oracle.has(v))
+      if (missing.length || extra.length)
+        errors.push(`${name}: enum values differ (cddl2ts-only: [${missing}], schema-only: [${extra}])`)
+    } else if (node.kind === 'union') {
+      const alias = aliases[tsName(name)]
+      if (!alias) continue // cddl2ts represents it some other way; nothing to compare
+      const expected = expectedUnionFields(alias, parsed)
+      const mine = schemaTypeFields(name, schema.types)
+      const allow = new Set(UNION_DIFFERENCES[name]?.fields ?? [])
+      const missing = [...expected].filter((f) => !mine.has(f) && !allow.has(f))
+      const extra = [...mine].filter((f) => !expected.has(f))
+      const stale = [...allow].filter((f) => mine.has(f) || !expected.has(f))
+      if (missing.length) errors.push(`${name}: union missing fields cddl2ts has: ${missing.join(', ')}`)
+      if (extra.length) errors.push(`${name}: union has fields cddl2ts does not: ${extra.join(', ')}`)
+      if (stale.length) errors.push(`${name}: stale UNION_DIFFERENCES (resolved, remove): ${stale.join(', ')}`)
Evidence
schemaTypeFields() accumulates fields into a single Set across all variants, and the union
comparison uses only that Set, so it can’t attribute missing fields to a specific variant arm.

javascript/selenium-webdriver/bidi_schema_diff_test.mjs[224-234]
javascript/selenium-webdriver/bidi_schema_diff_test.mjs[295-306]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The cddl2ts oracle diff for `kind: 'union'` compares a single flattened field-set (`schemaTypeFields`) against a single flattened oracle field-set (`expectedUnionFields`). Because both are flattened, the test cannot catch per-variant regressions (e.g., a common field duplicated into variant A but accidentally omitted from variant B).

### Issue Context
This is especially relevant because the PR intentionally duplicates common fields across synthesized variant records to avoid TS intersections. The fidelity gate should therefore validate that duplication is correct.

### Fix Focus Areas
- javascript/selenium-webdriver/bidi_schema_diff_test.mjs[187-234]
- javascript/selenium-webdriver/bidi_schema_diff_test.mjs[295-306]

### Suggested fix
Augment the union comparison to include a per-variant check for common/intersection fields:
1. Extend parsing of the cddl2ts alias expression to extract common fields from `& { ... }` blocks into a separate `commonFields` set (you already parse these in `expectedUnionFields`).
2. For each schema union variant leaf record, assert it contains all `commonFields` (minus allowlisted fields for that union type, if needed).
3. Add a test case that simulates a union where one variant is missing a common field and assert the diff reports it.

This keeps the existing flattened-set check (still useful), while closing the per-variant gap.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Inline records not fully hoisted 🐞 Bug ≡ Correctness
Description
hoistInlineRecords() only hoists inline anonymous records that appear directly as top-level def
properties, so nested inline groups can remain and be projected into inline { record: [...] }
schema fragments. This contradicts the normalizer’s stated goal (“no inline records left”) and can
also drop anonymous spread/composition entries because the projector filters inline-group properties
by p?.Name.
Code

javascript/selenium-webdriver/normalize_bidi_ast.mjs[R203-233]

+export function hoistInlineRecords(ast) {
+  const out = structuredClone(ast)
+  const alloc = nameAllocator(out.map((d) => d.Name))
+  const created = []
+  const queue = [...out]
+
+  while (queue.length) {
+    const def = queue.shift()
+    if (!def || typeof def !== 'object' || !Array.isArray(def.Properties)) continue
+    const owner = splitName(def.Name ?? '')
+    for (const prop of def.Properties.flat()) {
+      if (!prop || typeof prop !== 'object' || !prop.Name) continue
+      const entries = typeList(prop.Type)
+      const inline = entries.length === 1 && isInlineGroup(entries[0]) && !entries[0].Value
+      if (!inline) continue
+      const localName = `${owner.local}${pascal(prop.Name)}`
+      const synthName = alloc(owner.domain ? `${owner.domain}.${localName}` : localName)
+      const newDef = {
+        Type: 'group',
+        Name: synthName,
+        IsChoiceAddition: false,
+        Properties: entries[0].Properties,
+        Comments: prop.Comments ?? [],
+        'x-selenium-synthetic': true,
+        'x-selenium-owner': def.Name,
+        'x-selenium-label': pascal(prop.Name),
+      }
+      created.push(newDef)
+      queue.push(newDef)
+      prop.Type = [groupRef(synthName)]
+    }
Evidence
The normalizer’s inline-record hoisting is shallow (top-level only), while the projector can still
emit inline record type refs and also drops unnamed properties inside those inline groups;
checkSchema does not reject non-empty inline records, so this can ship silently.

javascript/selenium-webdriver/normalize_bidi_ast.mjs[195-237]
javascript/selenium-webdriver/project_bidi_schema.mjs[108-117]
javascript/selenium-webdriver/project_bidi_schema.mjs[400-416]

Details

Comment thread javascript/selenium-webdriver/project_bidi_schema.mjs Outdated
Comment thread javascript/selenium-webdriver/project_bidi_schema.mjs Outdated
@titusfortner titusfortner force-pushed the project_bidi_schema branch from 5a7fca3 to 5ae00f7 Compare June 21, 2026 16:03
Comment thread javascript/selenium-webdriver/project_bidi_schema.mjs Outdated
Comment thread javascript/selenium-webdriver/project_bidi_schema.mjs
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 5ae00f7

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 421fc38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a binding-neutral WebDriver BiDi schema artifact (commands/events/types) generated from shared CDDL AST/model data, plus Bazel wiring and tests that gate schema fidelity against cddl2ts to prevent silent field/type loss across bindings.

Changes:

  • Introduces a BiDi AST normalizer and schema projector that emit a flat schema with referential-integrity + completeness validation.
  • Extends the Bazel BiDi generation pipeline to produce a shared *_schema.json artifact with cross-binding visibility.
  • Adds mocha tests, including a differential “oracle” check comparing the generated schema to cddl2ts output.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
javascript/selenium-webdriver/project_bidi_schema.mjs Projects normalized AST + model into a flat schema and validates it (integrity + completeness).
javascript/selenium-webdriver/project_bidi_schema_test.mjs Unit tests for schema projection and validation helpers.
javascript/selenium-webdriver/private/generate_bidi.bzl Adds schema-generation step to the Bazel BiDi pipeline and exposes schema to other bindings.
javascript/selenium-webdriver/normalize_bidi_ast.mjs Normalizes raw CDDL AST into canonical forms (hoisted enums/records, flattened unions/composition).
javascript/selenium-webdriver/normalize_bidi_ast_test.mjs Unit tests for AST normalizer transforms.
javascript/selenium-webdriver/BUILD.bazel Adds project_bidi_schema_script and a mocha test target for schema tooling + fidelity gate.
javascript/selenium-webdriver/bidi_schema_diff_test.mjs Differential test comparing generated schema to cddl2ts as an oracle.

Comment thread javascript/selenium-webdriver/project_bidi_schema.mjs
Comment thread javascript/selenium-webdriver/project_bidi_schema_test.mjs Outdated
Comment thread javascript/selenium-webdriver/normalize_bidi_ast_test.mjs Outdated
Comment thread javascript/selenium-webdriver/project_bidi_schema.mjs
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 7c315df

@diemol

diemol commented Jun 22, 2026

Copy link
Copy Markdown
Member

Shouldn't this be a draft until #17657 gets a 👍?

@titusfortner

titusfortner commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@diemol #17657 is merged. Do you mean #17701?
Even if that ADR is not accepted, this code will make bindings-specific code generation much easier, regardless of how we do it. That ADR is proposing to limit auto-generation to just what this schema provides; the alternative is to inject additional orchestration code into the language-specific generation on top of this schema.

@diemol diemol added the A-needs decision TLC needs to discuss and agree label Jun 22, 2026
@diemol

diemol commented Jun 22, 2026

Copy link
Copy Markdown
Member

Yes, sorry. I meant that one.

Thank you for clarifying. From that point of view, then we could move forward on this one.

@titusfortner

Copy link
Copy Markdown
Member Author

Working with this code to generate Ruby code surfaced several real projector type-loss bugs (LocalValue date/regexp arms, js-int/js-uint ranges, the ConsoleLogEntry composition drop) which are now fixed and gated at build time

Comment on lines +77 to +81
function projectRef(type) {
const all = typeList(type)
const entries = all.filter((e) => !isNullAlt(e))
if (entries.length === 0) return { primitive: 'null', nullable: true } // the type is only `null`
const node =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. projectref() null-only untested 📘 Rule violation ▣ Testability

projectRef() now treats a null/nil-only type as { primitive: 'null', nullable: true }, but
the new behavior is not covered by an automated test, risking regressions in schema fidelity. This
violates the requirement that new bug fixes include corresponding tests with assertions.
Agent Prompt
## Issue description
`projectRef()` has new behavior for types that are only `null`/`nil`, but there is no regression test asserting the projected schema contains `primitive: 'null'` (not `unknown`) and `nullable: true`.

## Issue Context
This is a schema-fidelity bugfix path; without a targeted test it can regress without being noticed.

## Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[73-88]
- javascript/selenium-webdriver/project_bidi_schema_test.mjs[83-162]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 439dacd

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 8bc4569

@titusfortner

Copy link
Copy Markdown
Member Author

Pushed 8bc45690d5: synthesized types now carry { synthetic, owner, label } metadata so bindings can name/nest anonymous constructs (enums, inline records, variant arms) without reverse-engineering the synthetic def name. This also surfaced a real bug — two hoisted enums whose owner was a source variant that canonicalizeVariantParams later merged away, leaving a dangling reference; the canonicalizer now re-points the owner to the absorbing record, and checkSchema fails the build if any synthetic owner doesn't resolve.

Comment on lines +284 to +290
// No shared discriminator: dispatch by required-field presence, in spec order.
return {
ordered: variants.map((ref) => {
const t = types[ref]
const requires = t?.kind === 'record' ? t.fields.filter((f) => f.required).map((f) => f.name) : []
return { ref, requires }
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Ordered selector skips aliases 🐞 Bug ≡ Correctness

unionSelector() computes selector.ordered[].requires only when the variant is a direct record;
alias (and other non-record) variants get requires: [], which makes them match trivially and can
cause incorrect structural-union dispatch.
Agent Prompt
### Issue description
`unionSelector()` falls back to a structural selector `{ ordered: [...] }` when no discriminator key works. In that branch it derives `requires` only for variants whose projected kind is `record`; if a variant is an `alias` to a record (or a nested `union`), it gets `requires: []`, which makes that variant always “selectable” and can cause the structural dispatch to pick the wrong arm.

### Issue Context
The same file already treats alias-to-record as a normal part of the union graph (`unionLeaves()` follows `kind: 'alias'`), and tagged-discriminator derivation (`tagContribution`) also follows aliases. The structural selector should be consistent with those paths.

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[200-211]
- javascript/selenium-webdriver/project_bidi_schema.mjs[264-292]
- javascript/selenium-webdriver/project_bidi_schema.mjs[406-427]
- javascript/selenium-webdriver/project_bidi_schema_test.mjs[114-124]
- javascript/selenium-webdriver/project_bidi_schema_test.mjs[234-249]

### Suggested fix
1. When building the structural selector, resolve each immediate variant to its leaf records (reuse `unionLeaves(ref, types)`), and build `ordered` entries from those leaf records (preserving spec order by iterating variants in-order, then their leaves in-order).
2. Compute `requires` from the resolved leaf record’s required fields.
3. Add a unit test where a structural union includes an alias variant (e.g. `x.Alias` is a single-ref alias to `x.A`), and assert the `requires` for that ordered entry reflects `x.A` (not `[]`).
4. (Optional but recommended) Strengthen `checkSelector()` to flag an `ordered` selector entry with `requires: []` when it would shadow later variants (e.g., an empty-requires entry that is not last), since that makes subsequent variants unreachable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 72a51dd

@titusfortner titusfortner force-pushed the project_bidi_schema branch from 72a51dd to 09f7fa7 Compare June 24, 2026 16:25
Comment on lines +162 to 166
# Step 2: parse the merged CDDL once into the reusable AST artifact. Internal
# input to the schema and the JS generator; not consumed by other bindings.
ast_target = name + "_ast"
ast_out = name + "_ast.json"
js_run_binary(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Ast/model targets made private 📘 Rule violation ≡ Correctness

generate_bidi_library() now makes the generated AST/model artifacts package-private by omitting
visibility = _ARTIFACT_VISIBILITY, while still exposing the new schema target. This is a
backward-incompatible change for any cross-binding code that depended on those previously-visible
artifacts.
Agent Prompt
## Issue description
The PR changes the Bazel visibility contract of the generated BiDi artifacts: `_ast` and `_json` are now package-private (no explicit `visibility`), while `_schema` remains exposed to other bindings. This can break existing (or soon-to-be-written) cross-binding dependencies.

## Issue Context
The file defines `_ARTIFACT_VISIBILITY` for `//java`, `//py`, and `//rb` subpackages, and applies it to the new schema generator output. The AST/model generation steps no longer apply the same visibility, effectively removing them from the cross-binding API surface.

## Fix Focus Areas
- javascript/selenium-webdriver/private/generate_bidi.bzl[162-194]
- javascript/selenium-webdriver/private/generate_bidi.bzl[196-216]
- javascript/selenium-webdriver/private/generate_bidi.bzl[5-11]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +77 to +120
const typeList = (t) => (Array.isArray(t) ? t : t === undefined || t === null ? [] : [t])
const isLiteral = (e) => e && typeof e === 'object' && e.Type === 'literal'
const isRef = (e) => e && typeof e === 'object' && e.Type === 'group' && typeof e.Value === 'string'

// A `null` keyword or a `nil` prelude ref in a union means the value may be null.
const isNullAlt = (e) =>
e === 'null' || (e && typeof e === 'object' && e.Type === 'group' && PRELUDE[e.Value] === 'null')

function projectRef(type) {
const all = typeList(type)
const entries = all.filter((e) => !isNullAlt(e))
if (entries.length === 0) return { primitive: 'null', nullable: true } // the type is only `null`
const node =
entries.length > 1
? entries.every(isLiteral)
? { enum: entries.map((e) => e.Value) }
: { union: entries.map(projectEntry) }
: projectEntry(entries[0])
if (entries.length < all.length) node.nullable = true // a `null` alternative means the value may be null
return node
}

function projectEntry(e) {
if (typeof e === 'string') return { primitive: PRIMITIVES[e] ?? e }
if (!e || typeof e !== 'object') return { primitive: 'unknown' }
// A control operator (`.ge` / `.default` / `.le` …) wraps the real type as
// `{ Type: <innerType>, Operator: {...} }`; the constraint does not change the
// type, so project the inner type.
if (e.Type && typeof e.Type === 'object') return projectEntry(e.Type)
if (e.Type === 'literal') return { const: e.Value }
if (e.Type === 'group' && e.Value) return e.Value in PRELUDE ? { primitive: PRELUDE[e.Value] } : { ref: e.Value }
if (e.Type === 'group' && Array.isArray(e.Properties)) {
// An inline group that only wraps anonymous ref(s) — e.g. a union arm
// `{ DateLocalValue }` — is that ref (or a union of them), not a record.
const refs = unionMemberRefs(e)
if (refs) return refs.length === 1 ? { ref: refs[0] } : { union: refs.map((r) => ({ ref: r })) }
return {
record: e.Properties.flat()
.filter((p) => p?.Name)
.map(projectField),
}
}
if (e.Type === 'array') return { list: projectRef(e.Values?.[0]?.Type) }
if (e.Type === 'map') return { map: projectRef(e.ValueType ?? e.Values?.[0]?.Type), extensible: true }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Undefined types project to null 🐞 Bug ☼ Reliability

projectRef() returns { primitive: 'null', nullable: true } when given an empty type list; because
array/map projection passes potentially-undefined inputs (e.Values?.[0]?.Type, `e.ValueType ??
...`) into projectRef, missing element/value types are mis-projected as null instead of failing
closed. This can let malformed/unhandled AST shapes produce valid-looking schema output and evade
checkSchema’s unknown-type guard.
Agent Prompt
### Issue description
`projectRef()` treats an empty `typeList()` as “null-only type” and returns `{ primitive: 'null', nullable: true }`. Because array/map projection calls `projectRef(e.Values?.[0]?.Type)` / `projectRef(e.ValueType ?? e.Values?.[0]?.Type)`, any missing/empty element/value type is silently mapped to `null` rather than to `unknown` or an error.

### Issue Context
This undercuts the schema gate’s stated “fail-closed” behavior, since `checkSchema()` only flags `primitive === 'unknown'`.

### Fix Focus Areas
- javascript/selenium-webdriver/project_bidi_schema.mjs[77-97]
- javascript/selenium-webdriver/project_bidi_schema.mjs[119-120]
- javascript/selenium-webdriver/project_bidi_schema.mjs[386-415]

Suggested approach:
- Make `projectRef(undefined)` (and `projectRef(null)` when it indicates “missing”, not a CDDL null alternative) project to `{ primitive: 'unknown' }` (or throw) rather than the null-only type.
  - One option: change `typeList` to return a sentinel for undefined, or add an explicit guard at the start of `projectRef`:
    - `if (type === undefined) return { primitive: 'unknown' }`
- In the `array`/`map` cases, explicitly validate the presence of an element/value type and project `unknown` (so `checkSchema` fails) when absent.
- Add a unit test asserting `projectRef(undefined)` is treated as `unknown` (or that an empty array/map element type fails schema validation) to prevent regression.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 09f7fa7

@titusfortner titusfortner force-pushed the project_bidi_schema branch from 09f7fa7 to a87d7b9 Compare June 24, 2026 17:17
Comment on lines +332 to +357
const memberRefs = detected.branches.map((branch, i) => {
const entry = branchType(branch)
if (!entry) return null
const { fields, label, supersedes } = variantRecord(detected.commonFields, entry, defMap, i)
const memberLabel = trimRedundantPrefix(owner.local, label)
const localName = `${owner.local}_${memberLabel}`
const synthName = alloc(owner.domain ? `${owner.domain}.${localName}` : localName)
created.push({
Type: 'group',
Name: synthName,
IsChoiceAddition: false,
Properties: fields,
Comments: [],
'x-selenium-synthetic': true,
'x-selenium-owner': def.Name,
'x-selenium-label': memberLabel,
})
if (supersedes) {
superseded.add(supersedes)
supersededBy.set(supersedes, synthName)
}
return groupRef(synthName)
})

if (memberRefs.some((r) => r === null)) continue // unexpected branch shape: leave def untouched

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Orphaned synthetic variant defs 🐞 Bug ≡ Correctness

canonicalizeVariantParams() appends synthesized variant-record defs to created before confirming
all choice branches are supported, so a later bailout (continue) can leave behind unreferenced
synthetic defs while keeping the original def unchanged. This makes the normalized AST (and
therefore the projected schema) depend on branch order and can introduce unexpected extra types.
Agent Prompt
### Issue description
`canonicalizeVariantParams()` mutates output even when it decides not to canonicalize a detected inline-variant record. It pushes synthesized variant defs into `created` during the per-branch map, but if any branch is unsupported it bails out (`continue`) and leaves the original def untouched. This results in orphan synthetic defs being appended to the output AST.

### Issue Context
The current control flow creates defs before validating that *all* branches are supported.

### Fix Focus Areas
- javascript/selenium-webdriver/normalize_bidi_ast.mjs[327-367]

### Suggested fix
Refactor to be atomic per `def`:
1. First, pre-validate and compute all `branchType()` entries for `detected.branches`.
2. If any entry is null/unsupported, do not create any synthesized defs and do not modify `def`.
3. Otherwise, build synthesized defs into a temporary local array (e.g., `newDefs`) and only then append them to `created` and rewrite `def` to `Type='variable'`.
4. Add a unit test covering an inline-variant record where one branch is unsupported, asserting that **no** synthesized defs are added to the output.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a87d7b9

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit eb5ef4f

@pujagani

Copy link
Copy Markdown
Contributor

I think this is the right direction. Building a Java CDDL generator off of this is much better. Looks good to me. Once the bot comments are addressed, let's merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-needs decision TLC needs to discuss and agree B-build Includes scripting, bazel and CI integrations C-nodejs JavaScript Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants