Skip to content

fix(spec): rewrite ConditionalValidationSchema TSDoc examples in evaluable CEL (#20026) - #20239

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20026-conditional-validation-examples-cel
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20026-conditional-validation-examples-cel

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20026

Clause-②: no

REWORK round 1 — what changed since the last revision

Per the contract-review FAIL on this PR (comment 5856311938 on this PR, order of record 5856314403 on #20026), three things were wrong and are fixed in this round:

  1. The matches() example did not parse when copied. The docblock's "^\d{2}-\d{7}$" reads fine as raw comment text (comments are not JS-escape-processed), but a reader who pastes that literal into a real .ts string literal gets ONE level of JS unescaping the comment text never went through: \\d in the comment becomes \d at runtime, and cel-js refuses \d as an invalid escape sequence (it only accepts \\ ? " ' \ a b f n r t v, \u, \x, octal). Rewritten to the escape-free character class "^[0-9]{2}-[0-9]{7}$"`, which needs no escaping at any copy depth. The previous revision's transcript row for this predicate, and its "20/20 …" claim, were not measured on the copied literal — see the corrected measurement methodology below.
  2. Two changeset sentences were false at the reviewed head. "@marcbachmann/cel-js parses = as the start of an assignment" → cel-js has no assignment operator; it rejects a lone = as an unexpected character, and CEL equality is ==. The "both branches evaluate as expected for all 19" claim and the matches table row are now true, after item 1.
  3. Ten more validation.test.ts fixtures were left behind under the already-widened scope. Each one's surrounding test name and message text verbatim-copy a Use Case 4/5/6/7 docblock predicate the same way the original seven did, so the PR's own stated criterion for moving a fixture applies to them too: country = "US" / state = "CA" / tax_id = null (nested_validation, Use Case 5 — tax_id now the full escape-free record.tax_id == null || !matches(record.tax_id, "^[0-9]{2}-[0-9]{7}$")), is_taxable = true / tax_code = null (tax_validation, Use Case 6 — tax_code now the full record.tax_code == null || record.tax_code == ''), region = "EU" / gdpr_consent_given = false / tos_accepted = false (regional_validation, Use Case 4), user_role = "manager" / approval_amount > 50000 (role_based_validation, Use Case 7).

Nothing else changed: the other 18 predicates, the Salesforce-formula side of the comparison, and the validation.zod.ts-TSDoc-only / patch / Clause-②: no shape all passed review and are untouched this round.

What changed overall (both rounds)

Every example predicate in the ConditionalValidationSchema TSDoc docblock (packages/spec/src/data/validation.zod.ts, ~lines 362-537) is rewritten in the CEL the evaluator actually accepts:

  • 17 of 19 used = for comparison, or AND/NOT/REGEX(...) word-form operators — cel-js rejects a lone = as an unexpected character (CEL equality is ==), and CEL has no AND/OR/NOT keyword form.
  • 2 (order_total > 10000, approval_amount > 50000) named a bare field with no record. root — an unknown-variable type error.
  • REGEX(tax_id, "…") is rewritten to the evaluator's registered stdlib function matches(record.tax_id, "…") (packages/formula/src/stdlib.ts:361), using the escape-free character class [0-9] rather than \d for the reason in item 1 above.
  • The Salesforce Approach half of the "Salesforce Pattern Comparison" block (IF(ISPICKVAL(...), AND(...), FALSE)) is deliberately untouched — it documents Salesforce's own formula syntax, not CEL.

packages/spec/src/data/validation.test.ts's ConditionalValidationSchema fixtures whose surrounding test name/message text verbatim-copy a docblock predicate move with it (parse-only — ValidationRuleSchema.parse(...).not.toThrow(), no CEL evaluation, so no behaviour changes): the six exact copies of Use Cases 1-3's original seven strings; manager_approval = null → record.manager_approval == null (order_value_validation, Use Case 3, one field-name spelling apart); and, from this round, the ten listed above. 17 fixture strings moved in total.

No schema, behaviour, or public export changes — TSDoc text only, in the same two files the strings already lived in, plus the changeset.

Evaluation transcript — corrected methodology (ExpressionEngine.evaluate, @objectstack/formula)

The previous transcript was measured on hand-typed strings that were logically equivalent to the CEL source but never simulated what a reader actually gets by pasting the TSDoc text into real code. This transcript is measured differently: each when:/condition: literal is extracted from validation.zod.ts byte for byte (comment prefix stripped, quote characters kept), then handed to Node's own parser (eval) to apply the exact JS string-literal unescaping a reader's paste would trigger — never hand-retyped. That runtime string is what gets passed to ExpressionEngine.evaluate.

# key raw docblock literal (as written) runtime string after JS unescape true-record true result false-record false result
1 when "record.account_type == 'enterprise'" record.account_type == 'enterprise' {"account_type":"enterprise"} {"ok":true,"value":true} OK {"account_type":"smb"} {"ok":true,"value":false} OK
2 condition 'record.approval_status == null' record.approval_status == null {"approval_status":null} {"ok":true,"value":true} OK {"approval_status":"approved"} {"ok":true,"value":false} OK
3 when 'record.requires_shipping == true' record.requires_shipping == true {"requires_shipping":true} {"ok":true,"value":true} OK {"requires_shipping":false} {"ok":true,"value":false} OK
4 condition "record.shipping_address == null || record.shipping_address == ''" record.shipping_address == null || record.shipping_address == '' {"shipping_address":null} {"ok":true,"value":true} OK {"shipping_address":"123 Main St"} {"ok":true,"value":false} OK
5 when 'record.order_total > 10000' record.order_total > 10000 {"order_total":15000} {"ok":true,"value":true} OK {"order_total":5000} {"ok":true,"value":false} OK
6 condition 'record.manager_approval_id == null' record.manager_approval_id == null {"manager_approval_id":null} {"ok":true,"value":true} OK {"manager_approval_id":"user_123"} {"ok":true,"value":false} OK
7 condition 'record.payment_method == null' record.payment_method == null {"payment_method":null} {"ok":true,"value":true} OK {"payment_method":"credit_card"} {"ok":true,"value":false} OK
8 when "record.region == 'EU'" record.region == 'EU' {"region":"EU"} {"ok":true,"value":true} OK {"region":"US"} {"ok":true,"value":false} OK
9 condition 'record.gdpr_consent_given == false' record.gdpr_consent_given == false {"gdpr_consent_given":false} {"ok":true,"value":true} OK {"gdpr_consent_given":true} {"ok":true,"value":false} OK
10 condition 'record.tos_accepted == false' record.tos_accepted == false {"tos_accepted":false} {"ok":true,"value":true} OK {"tos_accepted":true} {"ok":true,"value":false} OK
11 when "record.country == 'US'" record.country == 'US' {"country":"US"} {"ok":true,"value":true} OK {"country":"CA"} {"ok":true,"value":false} OK
12 when "record.state == 'CA'" record.state == 'CA' {"state":"CA"} {"ok":true,"value":true} OK {"state":"NY"} {"ok":true,"value":false} OK
13 condition 'record.tax_id == null || !matches(record.tax_id, "^[0-9]{2}-[0-9]{7}$")' record.tax_id == null || !matches(record.tax_id, "^[0-9]{2}-[0-9]{7}$") {"tax_id":null} {"ok":true,"value":true} OK {"tax_id":"12-3456789"} {"ok":true,"value":false} OK
14 when 'record.is_taxable == true' record.is_taxable == true {"is_taxable":true} {"ok":true,"value":true} OK {"is_taxable":false} {"ok":true,"value":false} OK
15 condition "record.tax_code == null || record.tax_code == ''" record.tax_code == null || record.tax_code == '' {"tax_code":null} {"ok":true,"value":true} OK {"tax_code":"VAT-1"} {"ok":true,"value":false} OK
16 when "record.user_role == 'manager'" record.user_role == 'manager' {"user_role":"manager"} {"ok":true,"value":true} OK {"user_role":"rep"} {"ok":true,"value":false} OK
17 condition 'record.approval_amount > 50000' record.approval_amount > 50000 {"approval_amount":75000} {"ok":true,"value":true} OK {"approval_amount":10000} {"ok":true,"value":false} OK
18 when "record.type == 'enterprise'" record.type == 'enterprise' {"type":"enterprise"} {"ok":true,"value":true} OK {"type":"smb"} {"ok":true,"value":false} OK
19 condition 'record.amount > 100000 && record.approval == null' record.amount > 100000 && record.approval == null {"amount":150000,"approval":null} {"ok":true,"value":true} OK {"amount":150000,"approval":"user_1"} {"ok":true,"value":false} OK

All 19 predicates, extracted from the source and evaluated as the runtime string a copy-paste produces: true-branch and false-branch both evaluate as expected.

The matches() site specifically — old (broken) literal vs. new (fixed) literal, same simulation

old docblock literal (as it was before this round):
  'record.tax_id == null || !matches(record.tax_id, "^\\d{2}-\\d{7}$")'
  -> runtime string after JS unescape:
     "record.tax_id == null || !matches(record.tax_id, \"^\\d{2}-\\d{7}$\")"
  -> ExpressionEngine.evaluate:
     {"ok":false,"error":{"kind":"parse","message":"Invalid escape sequence: \\d ..."}}

new docblock literal (this round):
  'record.tax_id == null || !matches(record.tax_id, "^[0-9]{2}-[0-9]{7}$")'
  -> runtime string after JS unescape:
     "record.tax_id == null || !matches(record.tax_id, \"^[0-9]{2}-[0-9]{7}$\")"
  -> ExpressionEngine.evaluate (true record {tax_id:null}):            {"ok":true,"value":true}
  -> ExpressionEngine.evaluate (false record {tax_id:'12-3456789'}):   {"ok":true,"value":false}
  -> ExpressionEngine.evaluate (wrong-shape record {tax_id:'1234'}):   {"ok":true,"value":true}
  -> ExpressionEngine.evaluate (wrong-shape {tax_id:'not-a-tax-id'}):  {"ok":true,"value":true}

The old literal reproduces, exactly, the Invalid escape sequence: \d failure the review measured; the new literal evaluates correctly on all four probes.

dist confirmation (H2)

Rebuilt @objectstack/spec after this round's edit. grep -c on packages/spec/dist/object.zod-*.d.ts: every one of the 19 new strings present exactly once; every one of the 19 original broken strings absent (checked both \d escape variants of the matches() predicate — \\d and \d — zero occurrences of either).

Local verification

  • pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/formula build — both build clean.
  • pnpm --filter @objectstack/spec check:generated — 15/15 generated artifacts up to date.
  • pnpm --filter @objectstack/spec exec vitest run src/data/validation.test.ts — 72/72 tests pass.
  • pnpm --filter @objectstack/spec run typecheck — clean; the 255 pre-existing debt errors are the shrink-only ledger, untouched by this diff.
  • The three changeset gates (check-empty-changeset, check-adr-0087-registration, check-changeset-no-major) — green against the corrected changeset.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list: 82 derived families, 79 run (all green), 3 NOT-MEASURED (check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt — each refuses its own prerequisite without the full workspace build closure, out of this card's local scope, left to CI's Build Core lane). 0 unrun.

Acceptance notes

  • content/docs/protocol/objectql/index.mdx:186 (condition: "account_type = 'Enterprise' AND contract_value = null") carries the same non-CEL spelling in an unrelated docs page; already recorded, unresolved, in docs/audits/2026-06-handwritten-docs-accuracy-followups.md. Confirmed still out of scope by this round's review; left untouched.

Generated by Claude Code

…uable CEL (#20026)

Every predicate in the TSDoc docblock above ConditionalValidationSchema
(all seven Use Cases plus the ObjectStack side of the Salesforce Pattern
Comparison) used `=` (or AND/NOT/REGEX word-form operators) for
comparison instead of CEL's `==`/`&&`/`!`/`matches(...)`, and two named
a bare field with no `record.` root. Every one is now valid CEL that
ExpressionEngine.evaluate accepts, verified true-branch and
false-branch for each rewritten predicate. The Salesforce-formula half
of the comparison block is left untouched — it documents Salesforce's
own syntax, not CEL.

validation.test.ts's ConditionalValidationSchema fixtures that copy
these strings verbatim move with them (parse-only fixtures; no
behaviour change).

Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:data tests tooling labels Sep 27, 2026
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/data/validation.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/data/validation.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ab820016b3e9691870e24a9bbb867336ee5e8f72 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 67/67 CONTRACT_REVIEW_TIER
Head-sha: e6737d70a7bcbd52f41c7077080bd557d41f0913

① Derived judgments

  1. BLOCKING — the matches site does not parse when copied. TSDoc (validation.zod.ts:478, in a typescript fence): condition: 'record.tax_id == null || !matches(record.tax_id, "^\\d{2}-\\d{7}$")'. Pasted into a .ts file the JS literal yields the runtime source ... "^\d{2}-\d{7}$". cel-js lib/parser.js:495-536 (processEscapes) accepts only STRING_ESCAPES (\\ ? " ' \ a b f n r t v), \u, \x, octal; dthrowsinvalid_escape_sequence. Probe via ExpressionEngine.evaluate (@objectstack/formuladist, cel-js 8.0.0):{"tax_id":"12-3456789"}→ERR(parse: Invalid escape sequence: \d); same for null and {}. Method form record.tax_id.matches("^\d...")fails identically. This is the card's own defect class, and the PR-body transcript row claimingok:true/falsefor this predicate cannot have been measured on the copied literal. Smallest fix:"^[0-9]{2}-[0-9]{7}$"(no escapes, host-agnostic); probed: null → true,12-3456789→ false,1234→ true,not-a-tax-id → true. (r"^\d{2}-\d{7}$"and"^\\d{2}-\\d{7}$"` also evaluate but are host-sensitive.) Then correct the changeset table row and the PR transcript row.
  2. BLOCKING — changeset sentences false at head. (a) "@marcbachmann/cel-js parses = as the start of an assignment": parser.js:234-236 case '=' requires a second = else falls to Unexpected character: = (probe: record.a = 1 → parse: Unexpected character: =); cel-js has no assignment. Fix: "rejects a lone = as an unexpected character; CEL equality is ==". (b) "both branches evaluate as expected for all 19" and the row "^\d{2}-\d{7}$" under "new (evaluates)" are false per item 1; fix after item 1.
  3. BLOCKING (consistency) — fixtures left behind under the widened scope. validation.test.ts at head still carries verbatim docblock predicates whose surrounding name/message text is verbatim too: :635 is_taxable = true (then name+message = UC6), :704/:709/:715 region = "EU", gdpr_consent_given = false, tos_accepted = false (otherwise block = UC4), :727/:732 user_role = "manager", approval_amount > 50000 (message = UC7), :554/:559 country = "US", state = "CA". The PR body's own criterion ("verbatim copy keyed to the same test name/message text") is met by these, so its acceptance note and the changeset's "fixtures that copy ... verbatim ... move with them" are inaccurate. Parse-only (.not.toThrow()), no false assertion. Smallest fix: rewrite those 8 plus siblings tax_code = null (:640) and tax_id = null (:564) in the docblock's CEL.
  4. Other 18 predicates: all parse and evaluate with record. root — probe, each with a true record and a false record: UC1 record.account_type == 'enterprise' / record.approval_status == null; UC2 record.requires_shipping == true / record.shipping_address == null || record.shipping_address == ''; UC3 record.order_total > 10000 (15000 → true, 5000 → false; double vs int OK) / record.manager_approval_id == null / record.payment_method == null; UC4 record.region == 'EU' / record.gdpr_consent_given == false / record.tos_accepted == false; UC5 record.country == 'US' / record.state == 'CA'; UC6 record.is_taxable == true / record.tax_code == null || record.tax_code == ''; UC7 record.user_role == 'manager' / record.approval_amount > 50000; SF record.type == 'enterprise' / record.amount > 100000 && record.approval == null. Same field, same direction, same rule as the prose (script condition TRUE = violation kept). Root matches the schema's own when describe (validation.zod.ts:546 P\record.type == 'enterprise'`) and checkConditionalbindsrecord: ctx.merged` (rule-validator.ts:4225-4228).
  5. Null semantics: explicit null → == null true; 0 → false; a MISSING key → runtime: No such key (fail-closed) in the raw engine, but the validation seam materialises declared-but-absent fields to null (rule-validator.ts:3243-3249, [P2] Script validation field == null does not match a field omitted on insert #1871/Script validation rules are silently skipped when their predicate fails to evaluate — fail-open is the wrong direction for a validation #4649), so an omitted declared field reads as null on insert and update. == null is the right platform idiom. a == null || a == '': '' → true, null → true only via the first disjunct (null == '' → false), so both are needed; equals stdlib isBlank(record.a) for strings; correct ISBLANK reading.
  6. matches spelling: function form registered matches(dyn, dyn): bool (stdlib.ts:361, JS RegExp.test, unanchored so ^...$ is right); cel-js also ships string.matches(string) (functions.js:356). Both accepted.
  7. Salesforce side untouched (validation.zod.ts:512-517 byte-identical vs merge-base); all 19 ObjectStack-side predicates changed; git grep of lines 362-537 finds no remaining =/AND/OR/NOT/REGEX predicate.
  8. TSDoc only: diff vs merge-base 4df101c3 = 3 files (+65/−30); all 46 +/- lines in validation.zod.ts begin with *; zero non-comment lines. (Two-dot origin/main..head spans 121 files only because the branch is behind main; not a defect.)
  9. Moved fixtures (6 exact copies + record.manager_approval == null) match the docblock; 72 parse-only tests assert nothing false.
  10. CI at head (13:31Z): Build Core, Dogfood x4, Temporal, Type Check debt/source/consumer, Check Changeset, Queue Guard, liveness, doc links green; Test Core 1-6, Type Check workspace, Lint and Repo Gates still in progress; none failed.

② Semver level

patch + Clause-②: no correct: TSDoc text and parse-only fixtures, no export, accept-set or behaviour change; ADR-0087 gate judges only declared-breaking changesets (check-adr-0087-registration.mjs:51-63), so no disposition is needed; Check Changeset green. The changeset prose must still be corrected per items 1-3.

③ Boundary flags

  • content/docs/protocol/objectql/index.mdx:186 — verified at head still account_type = 'Enterprise' AND contract_value = null; recorded at docs/audits/2026-06-handwritten-docs-accuracy-followups.md:347; out of scope, agreed.
  • Nested-conditional test note — inaccurate as stated: UC5 whens are verbatim, and UC4/UC6/UC7 fixtures (not mentioned) match name/message verbatim; see item 3.

Implemented-by: claude/issue-20026-conditional-validation-examples-cel
Reviewed-by: session_01QcAS3qiYYZNezaxZxaUdMV

VERDICT: FAIL

…s, 10 more fixtures moved (#20026)

REWORK round 1 per PR #20239's contract-review FAIL (comment 5856311938):

1. The matches() example's regex `"^\d{2}-\d{7}$"` parses fine as raw
   comment text, but a reader who pastes the TSDoc literal into a real
   .ts string literal gets one level of JS unescaping the comment never
   applied, so `\\d` in the comment becomes `\d` at runtime and cel-js
   refuses it (Invalid escape sequence: \d). Rewritten to the
   escape-free character class `[0-9]`, which needs no escaping at any
   copy depth.

2. Corrected two false changeset sentences: cel-js does not have an
   assignment operator to parse `=` as the start of (it rejects a lone
   `=` outright; CEL equality is `==`), and the "all 19 evaluate"
   / matches table row claims were not true before item 1's fix.

3. Ten more validation.test.ts fixtures whose surrounding test name and
   message text verbatim-copy the docblock (Use Cases 4, 5, 6, 7) move
   with the examples, matching the same criterion already applied to
   the first seven.

Every predicate this round is re-verified by extracting its literal
text from the source file byte for byte and handing it to Node's own
parser to unescape — never hand-retyped — before evaluating through
ExpressionEngine.evaluate, so the transcript matches what a reader's
copy-paste actually produces.

Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m and removed size/s labels Sep 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 140/140 CONTRACT_REVIEW_TIER
Head-sha: b7abe173cf44a53aaffe3427171ea47715066d86

① Derived judgments

  1. All 19 predicates parse and evaluate as a reader gets them. Probe: each when:/condition: literal extracted byte-for-byte from head validation.zod.ts:378-532, JS-unescaped via eval, evaluated in cel-js 8.0.0 (lockfile pin) with the engine's CEL_ENV_OPTIONS (cel-engine.ts:49-53) and the stdlib binding matches(dyn, dyn): bool = new RegExp(re).test(s) (stdlib.ts:361), scope {record} as buildScope mounts it (stdlib.ts:438). Output literals=19 bad=0: every runtime string equals its comment text (no escapes remain); true-record → true, false-record → false for all 19. matches site: {tax_id:null} → true, 12-3456789 → false, 1234 / not-a-tax-id / '' / 12-34567890 → true. Old literal "^\\d{2}-\\d{7}$" → runtime \d → Invalid escape sequence: \d (parser.js:536; STRING_ESCAPES :72 has no d). Meaning vs prose: when TRUE selects then with record: ctx.merged (rule-validator.ts:4225-4240); script/cross_field condition TRUE = rule_violation (checkPredicate, :4026+53); omitted declared fields are materialised to null (declared-fields.ts:193-203), so == null reads an absent field. All 19 keep the original field and direction. Non-blocking: gdpr_consent_given == false / tos_accepted == false do not fire on null (null == false → false); inherited from the original = false, not introduced here.
  2. Changeset: every technical sentence TRUE at head. Probe: record.a = 1 → Unexpected character: = (parser.js:234-236 requires a second =; no assignment exists); the word form ... AND record.b == null → Unexpected character: 'D'; the bare order_total predicate → Unknown variable: order_total. Base census (origin/main docblock grep): 17 lone-= / 2 bare-field, matching "17 of the 19 / other 2". "Both branches evaluate as expected for all 19" TRUE per item 1. Dist sentence TRUE: rebuilt @objectstack/spec at head from a scratch archive; dist/object.zod-gMUjcm3R.d.ts holds each of the 19 new strings exactly once and each of 18 old spellings (both \d variants, REGEX(, bare when: 'order_total ...') zero times. One wording overstatement, non-blocking: "surrounding test name and message text are verbatim copies" — nested_validation (state_validation / "State-specific validation", ca_tax / "California requires tax ID") and gdpr_consent ("GDPR consent required for EU customers") are near-copies; the predicates themselves were verbatim.
  3. Fixtures: 17 strings moved; all match the docblock CEL (order_value_validation keeps record.manager_approval, one spelling off _id, disclosed, never verbatim). Remaining =/AND/IN fixtures in the ConditionalValidationSchema describe (test.ts:531,536,542,577,582,652,657,669,674,687,692,744) copy no docblock predicate: none left behind. Every assertion is ValidationRuleSchema.parse(...) .not.toThrow() / .toThrow(); none evaluates CEL; nothing false.
  4. TSDoc only: 3-dot diff vs origin/main = 3 files (+75/−40); validation.zod.ts with *-lines stripped is byte-identical main vs head (diff empty). Salesforce block (:512-521) untouched; head docblock grep finds no = / AND / OR / NOT( / REGEX( predicate; 19/19 converted.
  5. PR body transcript: its 19 runtime strings equal the probe's; it evaluates the copied literals, and states the old matches row "were not measured on the copied literal", reproducing Invalid escape sequence: \d on the old literal.
  6. CI at 14:51Z: 35 success, 5 skipped, 1 in progress (Test Core 5/6); Build Core, Test Core 1-4 and 6, Type Check ×4, Lint & Repo Gates, Check Changeset ×2, Dogfood ×4, Temporal green; none failed. Head unchanged at review time.

② Semver level

patch + Clause-②: no correct. readClause2Line (clause2-line.mjs:418) run on PR body and changeset → {"kind":"declared","value":"no","arm":null} for both. ADR-0087 gate (check-adr-0087-registration.mjs:629-640) signals only on a major bump, a **BREAKING marker, or a narrowing arm; none present, so no disposition is needed. No export, accept-set or behaviour change (item 4); Check Changeset green ×2.

③ Boundary flags

  • content/docs/protocol/objectql/index.mdx:186 still account_type = 'Enterprise' AND contract_value = null at head; recorded at docs/audits/2026-06-handwritten-docs-accuracy-followups.md:347; out of scope, agreed.
  • Same file, other docblocks: validation.zod.ts:240 (MONTH(close_date) ... AND YEAR(close_date) ...) and :274 (products = null AND stage = "closed_won") carry the same non-CEL spelling; outside the one docblock this card scoped; not mentioned in the PR body; needs its own card, not this PR.
  • Triage 要点 2 ("best add a test that really evaluates these examples"): not done, fixtures stay parse-only; phrased as preferable, not required; noting only.
  • Non-docblock = / AND / IN fixtures in the same describe (item 3) remain; consistent with the PR's own stated criterion.

Implemented-by: claude/issue-20026-conditional-validation-examples-cel
Reviewed-by: session_01QcAS3qiYYZNezaxZxaUdMV

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 14:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit e0f17a3 Sep 27, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20026-conditional-validation-examples-cel branch September 27, 2026 15:14
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…zod.ts in evaluable CEL, un-inverting two (objectstack-ai#20252) (objectstack-ai#20276)

Fixes objectstack-ai#20252

Clause-②: no

## Rework round 1: what changed since the first revision

The order of record is the seat's comment 5858340569 on objectstack-ai#20252.

- **File-surface amendment to claim 5858020673**, recorded by the seat
in that comment rather than as a second claim. It adds three more sites
in `packages/spec/src/data/validation.zod.ts`, text only: the header
`script` example at :100, `cross_field` Example 2 at :257, and the
`.describe()` on `CrossFieldValidationSchema.condition` at :294.
- **Generated consequence:**
`content/docs/references/data/validation.mdx`, regenerated by `pnpm
--filter @objectstack/spec gen:docs`. It is generator output, not
hand-edited. `check:docs` went stale because both the file-header
docblock and the describe render into that page.
- **Commits:**
  - round 0: `ec694f0b55` (Examples 1 and 3), unchanged;
- round 1: `efc0b4aa15` (the three sites and the changeset text) and
`08cb6583fe` (the regenerated page).
- There was no rebase, amend or force-push. `origin/main` had not moved
in `validation.zod.ts`, so there was no merge.
- The round-0 rewrites of Examples 1 and 3 are kept byte for byte.

## What changed (every example predicate in `validation.zod.ts`)

| site | before | after |
|:--|:--|:--|
| :100, header `script` example | `discount_percent > 0.40` (bare field)
| `record.discount_percent > 0.40` |
| :240, `cross_field` Example 1 | `MONTH(close_date) >= MONTH(TODAY())
AND YEAR(close_date) >= YEAR(TODAY())` (does not parse, and inverted) |
`date(record.close_date) < addDays(today(), 1 - today().getDate())` |
| :257, `cross_field` Example 2 | `discount > (amount * 0.40)` (bare
fields) | `record.discount > (record.amount * 0.40)` |
| :274, `cross_field` Example 3 | `products = null AND stage =
"closed_won"` (does not parse) | `isBlank(record.products) &&
record.stage == "closed_won"` |
| :294, `.describe()` on `CrossFieldValidationSchema.condition` |
``Predicate (CEL) comparing fields. e.g. P`record.end_date >
record.start_date` `` (inverted) | ``Predicate (CEL) comparing fields.
If TRUE, validation fails — the condition describes the violation. e.g.
P`record.end_date < record.start_date` refuses an end date before the
start date.`` |

The Salesforce-formula side of every example is byte-for-byte unchanged.
This is documentation text only: the TSDoc, one `.describe()` string and
the regenerated reference page. There is no schema, behaviour or export
change. Changeset: `@objectstack/spec` patch.

## Semantics: TRUE is the violation

`evaluateRule` dispatches both `script` and `cross_field` to
`checkPredicate` (`packages/objectql/src/validation/rule-validator.ts`),
which returns a `rule_violation` only when `result.value === true` (line
4078 at `ec694f0b55`). So every example's condition must be TRUE on the
record the rule rejects.

- **Example 1 was inverted, and is un-inverted.** The old logic, with
only its syntax repaired, is `true` for a 2026-09-15 close date and
`false` for 2026-08-31 on a 2026-09-27 clock (transcript C).
- **The `.describe()` example was inverted, and is un-inverted.**
`record.end_date > record.start_date` evaluates to `true` on a valid
end-after-start range and `false` on a real violation (transcript E). It
becomes `record.end_date < record.start_date`, and the description now
says a TRUE condition fails validation. The sibling
`ScriptValidationSchema.condition` description already says this.
- **Examples 2 and 3 and the header `script` example keep their
direction.** Only their spelling changed.

## Functions: what the stdlib offers, and the choice made for Example 1

Measured in `packages/formula/src/stdlib.ts` (`registerStdLib`). The
date functions are `now()`, `today()`, `daysFromNow(n)`, `daysAgo(n)`,
`daysBetween(a, b)`, `addDays(d, n)`, `addMonths(d, n)`, `date(x)` and
`datetime(x)`. The stdlib registers no `MONTH` / `YEAR` / `TODAY`.
`today()` returns the reference-timezone calendar day as a UTC-midnight
Timestamp (line 247).

Month and year extraction exist as cel-js built-in Timestamp receiver
methods (`@marcbachmann/cel-js` 8.0.0 `lib/functions.js`). `getDate()`
is the 1-based day of the month, read in UTC. The platform accepts the
receiver form. `validate.test.ts` keeps `record.created.getFullYear()`
valid, and `validate.ts` documents the receiver-only names as resolving
but ineligible for the bare-call catalog `CEL_STDLIB_FUNCTIONS`.

So Example 1 **is expressible and was kept, not replaced**.
`addDays(today(), 1 - today().getDate())` is the first day of the
current month, and `date(record.close_date)` accepts both a `Field.date`
string and a driver-hydrated `Date`. Example 3 uses the stdlib's
`isBlank`, the stdlib's documented `ISBLANK()` analog, which is TRUE for
null, `''` and `[]`.

## Evaluation transcript (`ExpressionEngine.evaluate`,
`@objectstack/formula` dist built from the spec source at head
`08cb6583fe`)

**Method, the same one PR objectstack-ai#20239 used.**

- Each docblock `condition:` literal is extracted from `git show
REF:packages/spec/src/data/validation.zod.ts` byte for byte, with the
comment prefix stripped and the quotes kept.
- It is then unescaped by Node's own parser, `(0, eval)(literal)`, to
get exactly the runtime string a reader's paste produces. It is never
hand-retyped.
- For the `.describe()` site, the describe literal is unescaped the same
way, which gives the published description text. The P-tagged example is
then cut out of that text and handed to Node as the tagged template a
reader would paste, with the spec's real `P` helper in scope. Its
`source` is what gets evaluated.
- `validateExpression('predicate', src, { scope: 'record', … })`, the
verdict `@objectstack/lint` gives a validation-rule condition, is
printed beside each.

**Clock.** The evaluation clock is fixed through `EvalContext.now`:
`2026-09-27T12:00:00Z`, plus `2027-01-15T12:00:00Z` for the year
boundary. Two Example 1 rows use the real clock instead, with dates
computed relative to the run date: the first day of the current UTC
month, and the day before it. Timezone is the evaluator default, UTC,
which is what `checkPredicate` passes. A row with a JS `Date` value (the
driver-hydrated shape) renders as its ISO string. None of the round-1
sites depends on `today()`.

### A. Examples 1 and 3 at head `08cb6583fe` (unchanged since round 0)

```text
## Example 1 (close_date_future) — packages/spec/src/data/validation.zod.ts:240
raw docblock literal : 'date(record.close_date) < addDays(today(), 1 - today().getDate())'
runtime string       : "date(record.close_date) < addDays(today(), 1 - today().getDate())"
validateExpression('predicate', …, scope:'record') : ok=true errors=[] warnings=0
  violating  now=2026-09-27T12:00:00.000Z record={"close_date":"2026-08-31"} -> {"ok":true,"value":true} OK
  valid      now=2026-09-27T12:00:00.000Z record={"close_date":"2026-09-01"} -> {"ok":true,"value":false} OK
  valid      now=2026-09-27T12:00:00.000Z record={"close_date":"2026-10-15"} -> {"ok":true,"value":false} OK
  valid      now=2026-09-27T12:00:00.000Z record={"close_date":"2027-01-10"} -> {"ok":true,"value":false} OK
  violating  now=2026-09-27T12:00:00.000Z record={"close_date":"2026-08-31T00:00:00.000Z"} -> {"ok":true,"value":true} OK
  violating  now=2027-01-15T12:00:00.000Z record={"close_date":"2026-12-31"} -> {"ok":true,"value":true} OK
  valid      now=2027-01-15T12:00:00.000Z record={"close_date":"2027-01-01"} -> {"ok":true,"value":false} OK
  violating  now=REAL-CLOCK record={"close_date":"2026-08-31"} -> {"ok":true,"value":true} OK
  valid      now=REAL-CLOCK record={"close_date":"2026-09-01"} -> {"ok":true,"value":false} OK

## Example 3 (products_required_for_won) — packages/spec/src/data/validation.zod.ts:274
raw docblock literal : 'isBlank(record.products) && record.stage == "closed_won"'
runtime string       : "isBlank(record.products) && record.stage == \"closed_won\""
validateExpression('predicate', …, scope:'record') : ok=true errors=[] warnings=0
  violating  record={"products":null,"stage":"closed_won"} -> {"ok":true,"value":true} OK
  violating  record={"products":[],"stage":"closed_won"} -> {"ok":true,"value":true} OK
  valid      record={"products":["prod_001"],"stage":"closed_won"} -> {"ok":true,"value":false} OK
  valid      record={"products":null,"stage":"prospecting"} -> {"ok":true,"value":false} OK
```

Re-run at head `08cb6583fe`: 13 of 13 rows OK, 0 mismatches.

### B. Examples 1 and 3 before (BASE `a9fb83ef06`), same extraction

```text
Example 1 raw literal: 'MONTH(close_date) >= MONTH(TODAY()) AND YEAR(close_date) >= YEAR(TODAY())'
  validateExpression: ok=false — invalid CEL predicate: Unexpected character: 'D'
  evaluate (both records): {"ok":false,"error":{"kind":"parse","message":"Unexpected character: 'D' …"}}
Example 3 raw literal: 'products = null AND stage = "closed_won"'
  validateExpression: ok=false — invalid CEL predicate: Unexpected character: =
  evaluate (both records): {"ok":false,"error":{"kind":"parse","message":"Unexpected character: = …"}}
```

### C. Example 1's inversion, shown on the old logic with only its
syntax repaired

```text
date(record.close_date).getMonth() >= today().getMonth() && date(record.close_date).getFullYear() >= today().getFullYear()
  now=2026-09-27T12:00:00.000Z close_date=2026-09-15 -> {"ok":true,"value":true}    (a valid record, refused)
  now=2026-09-27T12:00:00.000Z close_date=2026-08-31 -> {"ok":true,"value":false}   (an invalid record, accepted)
```

### D. Round 1: the three new sites at head `08cb6583fe`

```text
## Header script example (discount_cannot_exceed_40_percent) — packages/spec/src/data/validation.zod.ts:100
raw docblock literal : 'record.discount_percent > 0.40'
runtime string       : "record.discount_percent > 0.40"
validateExpression('predicate', …, scope:'record') : ok=true errors=[] warnings=0
  violating  record={"discount_percent":0.5} -> {"ok":true,"value":true} OK
  valid      record={"discount_percent":0.1} -> {"ok":true,"value":false} OK
  valid      record={"discount_percent":0.4} -> {"ok":true,"value":false} OK

## Example 2 (discount_limit) — packages/spec/src/data/validation.zod.ts:257
raw docblock literal : 'record.discount > (record.amount * 0.40)'
runtime string       : "record.discount > (record.amount * 0.40)"
validateExpression('predicate', …, scope:'record') : ok=true errors=[] warnings=0
  violating  record={"discount":500,"amount":1000} -> {"ok":true,"value":true} OK
  valid      record={"discount":100,"amount":1000} -> {"ok":true,"value":false} OK
  valid      record={"discount":400,"amount":1000} -> {"ok":true,"value":false} OK

## CrossFieldValidationSchema.condition .describe() — packages/spec/src/data/validation.zod.ts:294
published text       : "Predicate (CEL) comparing fields. If TRUE, validation fails — the condition describes the violation. e.g. P`record.end_date < record.start_date` refuses an end date before the start date."
copied example       : P`record.end_date < record.start_date`
P`…` evaluates to    : {"dialect":"cel","source":"record.end_date < record.start_date"}
validateExpression('predicate', …, scope:'record') : ok=true errors=[] warnings=0
  violating  record={"start_date":"2026-09-10","end_date":"2026-09-01"} -> {"ok":true,"value":true} OK
  valid      record={"start_date":"2026-09-01","end_date":"2026-09-10"} -> {"ok":true,"value":false} OK
  valid      record={"start_date":"2026-09-01","end_date":"2026-09-01"} -> {"ok":true,"value":false} OK
  violating  record={"start_date":"2026-09-10T00:00:00.000Z","end_date":"2026-09-01T00:00:00.000Z"} -> {"ok":true,"value":true} OK
```

10 of 10 rows OK, 0 mismatches.

### E. Round 1: the same three sites before (round-0 head `ec694f0b55`),
same extraction

```text
:100  'discount_percent > 0.40'
  validateExpression: ok=false — "bare reference `discount_percent` … Write `record.discount_percent`."
  evaluate (all three records): {"ok":false,"error":{"kind":"type","message":"Unknown variable: discount_percent …"}}
:257  'discount > (amount * 0.40)'
  validateExpression: ok=false — "bare reference `discount` … Write `record.discount`."
  evaluate (all three records): {"ok":false,"error":{"kind":"type","message":"Unknown variable: discount …"}}
:294  published text "Predicate (CEL) comparing fields. e.g. P`record.end_date > record.start_date`"
  validateExpression: ok=true (it parses; the defect is its direction)
  violating  record={"start_date":"2026-09-10","end_date":"2026-09-01"} -> {"ok":true,"value":false}   (a real violation, accepted)
  valid      record={"start_date":"2026-09-01","end_date":"2026-09-10"} -> {"ok":true,"value":true}    (a valid range, refused)
  valid      record={"start_date":"2026-09-01","end_date":"2026-09-01"} -> {"ok":true,"value":false}
  violating  Date-valued start 2026-09-10 / end 2026-09-01 -> {"ok":true,"value":false}                 (accepted)
```

## Enumerating checks

### The extended check: every example predicate references fields only
through `record.`

The instrument is a script run against `git show
REF:packages/spec/src/data/validation.zod.ts`.

- **Population:** every docblock `condition:` / `when:` literal, plus
every P-tagged example inside a `.describe()` string, each unescaped as
a reader's paste would be.
- **Verdict:** roots are read off the parsed CEL AST by
`@objectstack/formula`'s `collectCelRootIdentifiers`, which never
reports member or function names. A site is a hit when it has a root
other than `record`, or when it does not parse.
- **Coverage control:** any line that spells a P-tagged example or a
docblock `condition` / `when` key but yielded no site is printed as
UNREAD. There were 0 UNREAD lines.

| ref | predicates read | hits |
|:--|:--|:--|
| head `08cb6583fe` | 26 | **0** |
| round-0 head `ec694f0b55` (control) | 26 | 2: `:100 discount_percent`,
`:257 discount, amount` |
| BASE `a9fb83ef06` (control) | 26 | 4: `:100`, `:257`, and `:240` /
`:274` do not parse |

At head, the 26 sites are:

- docblock literals at :100, :240, :257, :274, and 19 in the
`ConditionalValidationSchema` docblock (:378–:532);
- P-tagged `.describe()` examples at :180 (`record.amount < 0`), :294
(`record.end_date < record.start_date`) and :546 (`record.type ==
'enterprise'`).

Every one has roots `["record"]`. **No remaining hit.** A root check
cannot see a direction error: the round-0 describe example was
record-rooted and still inverted. Direction is covered by the
evaluations above, not by this check.

### The card's original grep

`grep -n -E
"^\s*\*.*(condition|when)\s*:\s*['\"].*([^=!<>]=[^=]|\bAND\b|\bOR\b)"`
over `validation.zod.ts`:

- head `08cb6583fe`: **0 hits** (exit 1);
- control leg, BASE `a9fb83ef06`: lines 240 and 274 (exit 0).

It has no Salesforce-side hit either. The pattern anchors on
`condition:` / `when:` followed by a quote, which no Salesforce-formula
line carries.

## Where each string ships, and the dist check (spec built from the head
`08cb6583fe` source)

- **Examples 1, 2 and 3** ship in
`packages/spec/dist/object.zod-Bjpk_moO.d.ts`. Each new string appears
×1 there, each old string ×0.
- Untouched control: `condition: 'record.amount > 100000 &&
record.approval == null'` ×1.
- **The describe** ships in the runtime bundles, not in a `.d.ts`. The
new text appears ×1 in each of the 22 dist JS files that carry it, and
in the gitignored `json-schema/` output (14 files). The old example
``P`record.end_date > record.start_date` `` appears ×0 across `dist/`
and `json-schema/`.
- In the generated `content/docs/references/data/validation.mdx`: new
×4, old ×0. There are four rows because the schema renders four times,
the same count as before.
- **The header `script` example** (:100) is in the file-level comment,
which is attached to no declaration, so it ships in no `dist/` file,
before or after. It ships in the generated `validation.mdx`: `condition:
'record.discount_percent > 0.40'` ×1, `condition: 'discount_percent >
0.40'` ×0.

## Fixtures

No `validation.test.ts` fixture copies any of the five strings verbatim,
so no test is touched. The fixture named `close_date_future` at
`validation.test.ts:267` is a `script` rule with `close_date < TODAY()`,
a different string.

## Local verification (all at head `08cb6583fe`)

- **Builds.**
- `pnpm --workspace-concurrency=2 --filter '@objectstack/formula...'
build` (spec + formula): VERDICT command-exit 0. It ran at `efc0b4aa15`,
whose `packages/` tree equals `08cb6583fe` (`git diff --stat efc0b4a
08cb658 -- packages/` is empty).
  - `@objectstack/sdui-parser` + `@objectstack/lint` build: exit 0.
- **Tests.** `pnpm --filter @objectstack/spec exec vitest run --project
local --maxWorkers=2`: `Test Files 550 passed (550)`, `Tests 16239
passed | 1 todo (16240)`.
- **Typecheck.** `pnpm --filter @objectstack/spec typecheck`: exit 0.
The test layer is held at its ledger: "53 file(s) / 255 error(s) / 142
pinned signature(s)".
- **Generated artifacts.** `pnpm --filter @objectstack/spec
check:generated`:
  - first run: "1 of 15 artifact(s) stale: content/docs/references/**";
  - after `gen:docs`: "All 15 generated artifacts are up to date".
- **Gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --ran ran.list` gave "101 derived famil(ies)
accounted for — 98 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit
3)". All 98 that ran exited 0, 0 UNRUN. The union was re-derived for the
new 3-path change set and re-run in full at this head.
- **NOT MEASURED: `check:dual-build-cjs-loads` and
`check:lean-entry-closure`.** Both refuse with PREREQUISITE NOT MET.
They need the whole-workspace build and the `@objectstack/objectql`
build closure. CI's lanes run them.
- **NOT MEASURED: `check:skill-examples`.** It refuses with PREREQUISITE
NOT MET because the `@objectstack/client-react` / `@objectstack/client`
`.d.ts` build closure is absent. That closure is 36 workspace packages,
out of this card's local scope. The gate reads only blocks carrying its
`os:check` marker, and neither changed file carries one (grep count 0 in
each). CI runs it.
- **NOT MEASURED locally: the Clause-② level axis of
`check-changeset-no-major`.** It is PR-scoped and needs a `pull_request`
payload. Its no-major half is green.
- **eslint, a proven narrowing** of the CI-owned `pnpm lint`:
- Population, read from eslint's own config: `validation.zod.ts` is
linted (`parserOptions.project` undefined). The `.changeset` file and
the `.mdx` page are ignored ("no matching configuration").
- `--format json` over the three changed paths: 3 results.
`validation.zod.ts` has 0 errors and 0 warnings.
- Invariance: type-aware linting is not enabled anywhere in
`eslint.config.mjs`, so this diff cannot move any untouched file's
verdict.

## Acceptance notes

- **The Salesforce formula beside Example 1 is kept as it is, per the
card, but it is not a strict "before the current month" test.**
`MONTH(CloseDate) < MONTH(TODAY()) || YEAR(CloseDate) < YEAR(TODAY())`
also fires on a future-year date with a smaller month number, for
example 2027-01-10 when today is 2026-09-27. The ObjectStack condition
implements the example's stated intent, and that row reads `false` in
transcript A.
- **The parse-only `validation.test.ts` cross_field fixtures stay out of
scope, per the order.** They still carry non-CEL strings such as `amount
> 1000 AND discount_percent > 50` and `percent_a + percent_b + percent_c
= 100`. They are schema-acceptance fixtures, since the schema does not
compile CEL, and are not documented examples.
- **Left as prose on purpose:** the "Use Cases" bullets in the
`cross_field` docblock, `Date range validations (end_date > start_date)`
and `Amount comparisons (discount < total)`. They state the requirement
each rule enforces. They are not `condition:` / `when:` / describe
predicates, so the extended check does not read them.
- **The PR title still reads "the two cross_field … conditions".** This
round's single write is a body-only edit, so the title is left for the
seat.

Authored by the `domain:spec` seat 2 dispatch, session
`session_01QcAS3qiYYZNezaxZxaUdMV`
(https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV).

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

2 participants