Skip to content

Schema: per-definition parse API and lenient subschema mode - #62

Merged
joshmakestuff merged 3 commits into
mainfrom
issue-60-subschema-parse
Aug 3, 2026
Merged

Schema: per-definition parse API and lenient subschema mode#62
joshmakestuff merged 3 commits into
mainfrom
issue-60-subschema-parse

Conversation

@joshmakestuff

@joshmakestuff joshmakestuff commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Closes #60.

What

Subschema subentries publish bare parenthesized definitions, and a live server's schema cannot be fixed by the consumer. Alongside the strict slapd.conf file parser there is now:

  • LdapAttributeType.Parse / LdapObjectClass.Parse — strict single-definition parsing (RFC 4512 grammar: numeric OID, no unknown keywords, no trailing text; no objectidentifier macro context, which is a schema-file concept).
  • LdapSchema.ParseSubschema — lenient aggregate over attributeTypes / objectClasses values. A definition that fails to parse degrades into UnparsedDefinitions (raw text + error + kind) instead of blanking the schema; unknown non-X- keywords inside a definition skip rather than failing it (X- extensions are captured, as in file mode). The skip heuristic leaves bare words in place — the flag-before-keyword shape is the realistic vendor pattern — and its one blind spot (a bare value colliding with a standard keyword) is documented by a dedicated test rather than hidden.

The strict file parser is unchanged: unknown keywords still throw, matching slapd's own rejection of them.

Proven against real OpenLDAP two ways

  • Committed fixture captured from a live slapd 2.6.10 via tools/capture-subschema-fixture.ps1 (docker; 268 attribute types / 62 object classes, zero unparsed). The fixture's first line is the answering slapd's version string, written by the script (which fails closed if the image stops shipping 2.6) and asserted by the fixture test — the filename's "2.6" cannot silently drift.
  • New differential test starts slapd on an ldapi socket and reads cn=Subschema live. slapd's parent process exits only once the listener is ready, and the detached child releases inherited pipes — both verified empirically in a container before relying on them; the full differential suite was run in a Linux container (slapd 2.6.10, Ubuntu 24.04 base) and passed.

Review

Two adversarial review rounds ran (independent reviewer models). Round 1: 6 findings, 3 accepted and fixed in 0cb03d3 (fixture provenance witness, object-class floor in the live test, heuristic pinning test), 3 refuted with evidence. Round 2: 6 findings, 3 accepted and fixed in b557def (an overclaiming comment corrected, the blind-spot mode given a documenting test), 3 refuted. Public API changes reviewed deliberately and re-approved in PublicApi.Schema.approved.txt.

Groundwork for #61 (ldapSyntaxes values are already in the captured fixture; LdapSchemaDefinitionKind extends additively).

joshmakestuff and others added 3 commits August 3, 2026 11:20
Subschema subentries publish bare parenthesized definitions, and a live
server's schema cannot be fixed by the consumer — so alongside the strict
slapd.conf file parser there is now:

- LdapAttributeType.Parse / LdapObjectClass.Parse: strict single-definition
  parsing (RFC 4512 grammar; numeric OID, no unknown keywords, no trailing
  text). Bare definitions have no objectidentifier macro context.
- LdapSchema.ParseSubschema: lenient aggregate over attributeTypes /
  objectClasses values. A definition that fails to parse degrades into
  UnparsedDefinitions (raw text + error + kind) instead of blanking the
  schema; unknown non-X- keywords inside a definition are skipped (a bare
  word is left in place — it is more likely the next keyword than a value).
  X- extensions are captured, as in file mode.

The strict file parser is unchanged: unknown keywords still throw, matching
slapd's own rejection of them.

Proven against real OpenLDAP 2.6 subschema output two ways: a committed
fixture captured from a live slapd 2.6.10 (tools/capture-subschema-fixture.ps1,
docker-based; 268 attribute types / 62 object classes, zero unparsed), and a
new differential test that starts slapd on an ldapi socket and reads
cn=Subschema live. slapd's parent process exits only once the listener is
ready and the detached child releases inherited pipes — both verified
empirically in a container before relying on them.

Closes #60.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… floor

From adversarial review of the previous commit (2 of 6 findings confirmed,
1 partially; the rest refuted with evidence):

- The fixture's "2.6" was a filename claim with no runtime witness — the
  exact mislabeled-version class this repo has been burned by. The capture
  script now writes the answering slapd's version line as the fixture's
  first-line comment and fails closed if it is not 2.6; the fixture test
  asserts that witness, so a drifted recapture cannot silently relabel
  another version as 2.6. Fixture regenerated by the updated script
  (slapd 2.6.10, same 268/62/33 definitions).
- The unknown-keyword skip heuristic's bare-word case was untested; a new
  test pins the deliberate tradeoff (bare word = next keyword) and its
  rationale.
- The live differential test asserted a floor on attribute types but not
  object classes; now both, matching the fixture test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit's comment claimed a vendor bare value colliding with a
standard keyword "mis-parses under either heuristic" — false: the
consume-one-token alternative parses that exact shape correctly. Each
heuristic has exactly one of the two blind spots. Rewritten to say so, with
a test documenting the chosen heuristic's blind spot (NAME capturing the
word SYNTAX) instead of leaving that mode untested, and the mechanism-
distinguishing role correctly attributed to the flag-before-keyword test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joshmakestuff
joshmakestuff merged commit 9164854 into main Aug 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema: per-definition parse API for subschema-subentry input, with lenient mode

1 participant