feat(ssl): property-scoped assertion grammar#1404
Conversation
c3da0ff to
5ac71b8
Compare
Review: property-scoped assertion grammarVerdict: request changes. The core refactor is well-executed, but it misses a call site that ships the removed API to every new user via the recommended What's goodThe design is sound and the alignment with the API-check grammar is the right call. Reusing BlockerThe These aren't inert docs:
So the moment a release containing this PR is cut, The identical example inside Suggested fix: port both files to MajorThe
Reproduced: appending a deliberately-unused Either wire up The validation whitelists can drift from the builder unions silently. This contradicts the file's own source-level rule one block down ( Verified: adding Minor
Pre-existing, not introduced hereCodegen for an out-of-set target (e.g. a future |
Follow-up: blocker addressed in 789b7d1Pushed a fix for the blocker from my review above — both scaffolding templates are now on the property-scoped grammar: - SslAssertionBuilder.certExpiresInDays().greaterThan(30),
- SslAssertionBuilder.chainTrusted().equals(true),
- SslAssertionBuilder.hostnameVerified().equals(true),
- SslAssertionBuilder.tlsVersion().equals('TLS1.3'),
+ SslAssertionBuilder.certificate('daysUntilExpiry').greaterThan(30),
+ SslAssertionBuilder.connection('chainTrusted').equals(true),
+ SslAssertionBuilder.connection('hostnameVerified').equals(true),
+ SslAssertionBuilder.connection('tlsVersion').equals('TLS1.3'),Applied to both Since nothing in CI exercises these files, I verified the port three ways rather than trusting the edit:
A sweep across Still open from the review — I've left these for you, since they're judgement calls on your own design rather than mechanical fixes:
Also worth a separate look: the underlying gap that let this blocker exist. |
Replace the flat per-source SSL assertion builder with a property-scoped model mirroring the API check grammar and the monorepo redesign. Sources are now CERTIFICATE | CONNECTION | RESPONSE_TIME | JSON_RESPONSE | TEXT_RESPONSE. SslAssertionBuilder exposes certificate(property), connection(property), responseTime(), jsonResponse(jsonPath) and textResponse(regex?), reusing GeneralAssertionBuilder / NumericAssertionBuilder. The TlsVersion / SignatureAlgorithm / CipherSuite constant maps are kept as assertion targets, with JSDoc updated to the new API. Validation is rekeyed by (source, property): CERTIFICATE/CONNECTION whitelist comparisons per property and flag unknown properties; RESPONSE_TIME / JSON_RESPONSE / TEXT_RESPONSE whitelist per source. Boolean-target validation is retained for boolean properties. Both GREATER_THAN_OR_EQUAL and the regex (MATCHES) operator are removed for SSL; the now-unused SSL-only boolean/matches codegen helpers are dropped from the shared internal codegen. Codegen emits the five new sources and all specs (ssl-assertion, ssl-assertion-codegen, ssl-monitor, uptime-monitor-codegen) plus the ai-context example are migrated to the new grammar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013AYwkvoaANQTH5dGStbBj2
resolvedIp allowed EQUALS/NOT_EQUALS/CONTAINS but not NOT_CONTAINS, diverging from the webapp/backend string value type. Use the shared STRING set for parity. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013AYwkvoaANQTH5dGStbBj2
Convert SslAssertionBuilder.certificate/connection to typed overloads so
property names are narrowed to known unions (typos are compile errors +
autocomplete), and the two typed-constant properties narrow the builder
target type: connection('tlsVersion') -> TlsVersionValue,
certificate('signatureAlgorithm') -> SignatureAlgorithmValue. cipherSuite
stays unconstrained by prior decision.
Add type-level @ts-expect-error coverage proving the narrowing, plus
runtime validation coverage for the removed SSL-only operators
(MATCHES, GREATER_THAN_OR_EQUAL) and the per-property comparison
whitelist (sans/serialNumber/ocspStatus rejects; sans/resolvedIp accepts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AYwkvoaANQTH5dGStbBj2
Fixes the lint job (@stylistic/member-delimiter-style): single-line type literals use a comma, not a semicolon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013AYwkvoaANQTH5dGStbBj2
The advanced-project and advanced-project-js templates still called the flat SslAssertionBuilder methods (certExpiresInDays, chainTrusted, hostnameVerified, tlsVersion) removed by the property-scoped grammar refactor. These are not documentation-only: create-checkly downloads them as github:checkly/checkly-cli/examples/<template>#<version> and pins checkly to 'latest', so once the new grammar ships, scaffolding the recommended template and running checkly test/deploy would fail with "SslAssertionBuilder.certExpiresInDays is not a function". Semantics are unchanged; the synthesized wire payload is equivalent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The runtime whitelists in ssl-assertion-validation.ts were typed Record<string, PropertyRule>, so nothing tied them to the property unions in ssl-assertion.ts that they mirror. A union member added without a matching rule compiled clean and autocompleted, then failed at the user's checkly deploy with "unknown property". Key them by the unions instead, matching traceroute-assertion-validation.ts and this file's own source-level rule. A missing or misspelled entry is now a compile error. The unions are exported to make that possible, which also lets users name them when building typed wrappers, as the traceroute analogue already allows. They are prefixed Ssl- to suit the flat public export namespace, following the IcmpLatencyProperty / TracerouteResponseTimeProperty convention; both names are new to the public API, so nothing breaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
certificate(p)/connection(p) returned a GeneralAssertionBuilder exposing
every operator with string|number|boolean targets, so
certificate('daysUntilExpiry').greaterThan('30 days') compiled, passed
deploy validation, and then silently never fired.
Give each property its own operator class exposing exactly the
comparisons the backend accepts for it, with its target typed. The
operator maps are the declaration point: the property unions derive from
their keys, and the value-type and comparison tables are keyed by those
unions, so a property cannot be added to one and forgotten in another.
An unknown property still resolves, and is reported by validation rather
than throwing.
Validation now reports a non-numeric target for numeric properties and
for RESPONSE_TIME, closing the same silent-never-fires hole on the
object-literal path that bypasses the builder. Targets are checked only
where the accepted set is universal; the enumerated sets stay with the
builder's types to avoid restating the value unions.
Codegen emits numeric and boolean targets as bare literals so the
generated call matches the operators, sharing its numeric predicate with
validation so nothing the CLI calls valid is emitted as code that does
not compile. Targets outside a property's contract keep their string
form rather than being coerced, which would rewrite the assertion on the
next deploy.
valueForNumericAssertion keeps parseInt for its other callers, whose
targets may carry units; SSL opts into Number via the new parse option.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backend Joi schema and the go-runner (EvaluateRegExp) read the TEXT_RESPONSE extraction pattern from `property`; the `regex` field is never consulted. textResponse(pattern) previously emitted the pattern into `regex`, so it validated fine but was silently ignored at evaluation — the comparison ran against the whole serialized response document. Emit it as `property` (builder + import codegen) and pin the wire shape in tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXP63MTUyPnuc48ZWGovhR
The certificate/connection grammar was stated in four hand-synced places — 19 operator classes, two operator maps, a value-type table, and a comparison whitelist. Review found three ways they could drift while compiling clean: an operator added to a class but not the whitelist, a value type changed without the class's target type, and a property literal copy-pasted onto the wrong class. TypeScript's Record keying caught a missing entry but never a mismatched one. Declare each property once, as data: the operators it exposes and its target type. The property unions, the typed builder methods, the validation whitelist, and codegen's literal kind are all derived from that row, so the three drifts become structurally impossible — a property cannot expose an operator its whitelist rejects, or a target type codegen renders wrong, and there are no repeated literals to mis-copy. The generic machinery (operator catalog, target specs, the mapped type that turns a row into a builder surface) lives in a monitor-agnostic internal/assertion-grammar.ts; the SSL grammar tables and the runtime lookups validation and codegen read live in ssl-assertion-grammar.ts. Both stay out of the public API. Public call shapes, the property unions, wire output, and per-property operator and target narrowing are all unchanged: the pre-existing SSL tests pass untouched. A new consistency spec iterates the grammar and asserts the builder, validation and codegen agree for every declared (property, operator), and that number and boolean properties declare only operators codegen renders as literals — guarding the one drift the type system cannot, using codegen's own predicates rather than restating them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7b06846 to
fe440d4
Compare
…ration
Migrate the traceroute assertion builder onto the single-declaration
grammar system SSL already uses. The three hand-rolled builder classes
and the hand-maintained validation tables (property list and per-source
comparison whitelists) are replaced by grammar tables in
traceroute-assertion-grammar.ts, from which the builder surface, the
property union, and the validation whitelists all derive.
Traceroute exercises both grammar shapes: RESPONSE_TIME is
property-scoped over avg/min/max/stdDev, built via operatorsForProperty;
HOP_COUNT and PACKET_LOSS are single scalar sources whose backend grammar
omits notEquals, built via a new operatorsForSource helper. RESPONSE_TIME
comparisons are derived per statistical property, so a property that later
diverges is validated against its own row rather than a sibling's.
The builder call shapes, the property union, wire output, and per-source
operator narrowing are unchanged; the pre-existing traceroute tests pass
untouched, and the diagnostic messages ("has an invalid property", "must
not specify a property", "unknown source") are preserved. A consistency
spec iterates the grammar and asserts the builder and validation agree
for every declared (source, property, operator).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Redesigns the SSL monitor assertion grammar in the CLI constructs to a property-scoped model matching the backend's assertion grammar, and reworks the assertion builders behind it into a single-declaration grammar system that SSL and traceroute now share.
Public API (SSL)
SslAssertionBuilderis nowcertificate(property),connection(property),responseTime(),jsonResponse(jsonPath),textResponse(regex?)— replacing the flat per-source methods (certExpiresInDays(),tlsVersion(),sanContains(), …).number, booleans aboolean, andtlsVersion/signatureAlgorithmtheir constant-map unions (TlsVersion/SignatureAlgorithm/CipherSuiteare kept as targets). Socertificate('daysUntilExpiry').greaterThan('30 days')andhopCount().notEquals(5)are compile errors rather than assertions that silently never fire.GREATER_THAN_OR_EQUALandMATCHESoperators are dropped (along with the now-unusedvalueForBooleanAssertion/hasMatchescodegen path — SSL was its sole caller; other check types verified unaffected).textResponse(pattern)now carries the pattern in the assertion'spropertyslot — the slot the backend reads the extraction pattern from. It previously went intoregex, which is never consulted, so the pattern was silently ignored and the comparison ran against the whole response document.Breaking:
SslMonitorshipped in 8.15/8.16 with the flat methods; this removes them. The bundledcreate-checklytemplates are migrated in the same PR so scaffolding keeps working (see Other changes).Internal: single-declaration grammar system
Each assertion property is declared once, as data — its operators and its target type — in
internal/assertion-grammar.ts. The typed builder surface, the runtime validation whitelist, and codegen's literal kind are all derived from that one declaration, so a property cannot expose an operator its whitelist rejects, or a target type codegen renders wrong. This replaces four hand-synced declaration sites (per-property classes, an operator map, a value-type table, a comparison whitelist) that a prior review showed could drift while compiling clean.ssl-assertion-grammar.ts) and traceroute (traceroute-assertion-grammar.ts) are both built on it; traceroute's builder classes and hand-maintained validation tables are removed with no behavior change.(source, property, operator).Validation
Assertions written as plain object literals bypass the builder and are type-legal, so they are checked at deploy: unknown source / property / unsupported comparison, and — new — a non-numeric target on a numeric property or a non-
true/falsetarget on a boolean property, closing a silent-never-fires hole on that path.Other changes
create-checklyadvanced-project(TS) andadvanced-project-js(JS) templates ported to the new grammar — the recommended template pinscheckly: latest, so without this the scaffolded project would fail to load its SSL check on release.Tests
tsc --buildclean, eslint clean, full CLI unit suite green. New consistency specs for SSL and traceroute; existing SSL and traceroute tests pass unmodified (wire output byte-identical, operator/target narrowing verified via compile-time checks).Known follow-up
Spec files are not type-checked by the build (RED-739), so the
@ts-expect-errornarrowing assertions in the specs are inert until that lands; the grammar's guarantees hold attsconsrcand are additionally covered by the runtime consistency specs.🤖 Generated with Claude Code