Skip to content

fix(lint)!: a type mismatch in a record-triggered flow condition or an advisory validation rule is served a verdict that says what really happens - #29953

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-ddyc2k
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-ddyc2k

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #29926

维护者速读

  • 改了什么:文本或布尔字段和数字比大小(如 name > 5)写在「记录触发流程」的开始条件或连线条件里,现在发布时直接拒绝(error),文案说明真实后果:流程运行在该条件处失败,触发它的写入照常落库。写在建议性校验规则(severity: 'warning' | 'info')里仍被拒,但两条判词(类型不匹配和可空比较)都改成如实说明:规则被跳过、写入照常进行、建议永远不会出现。
  • 为什么改:fix(lint)!: publish refuses a text-vs-number comparison in a validation rule (or another fail-closed predicate) instead of warning, then refusing every write #29923 之后这两个角色拿到的判词是假的:流程条件说「静默得到 null」,实际是运行失败;建议性规则说「写入会被拒」,实际写入照常进行(objectql(validation): an unevaluable severity:'warning' rule logs "write rejected: … fails closed" twice while the write is accepted 201 and persists #24782)。另外 type-mismatch 一个代码对应两句话,违反「一个代码一句模板」的契约,现在每种结局各有自己的代码。
  • 风险与回滚:收窄发布门:已存在的记录触发流程若条件里有这种比较,下次发布会被拒(它们本来每次运行都会失败);type-mismatch 带 outcome: 'fail-closed' 的那一支改名为 type-mismatch-fail-closed,并去掉 outcome 参数。这一支已随 18.3.0 发出(@objectstack/formula@18.3.0 的类型和 @objectstack/mcp@18.3.0 的 validate_expression 都在用,但 18.3.0 的 changelog 里没有记录),所以按 18.3.0 的 type-mismatch + outcome 识别它的使用方需要改认新代码。回滚即 revert 本 PR,无数据迁移。
  • 先看:packages/lint/src/validate-expressions.ts(faultOutcomeOf 账本,两条判词共用)、packages/formula/src/validate.ts(TYPE_MISMATCH_FAULT_ARMS:每种结局的代码和文案)。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/publish-type-mismatch-fail-closed.test.ts,预期 5 条全过:校验规则被拒、公式仍发布带 warning、可空孪生照旧被拒、建议性规则被拒但文案不说写入被拒、记录触发流程条件被拒且文案说运行失败。
  • 需要你做的:无,席位落地(收窄,席位在落地前做 contract review)。

Clause-②: no (narrowing) — the author-time gate refuses, in a record-triggered flow's start or edge condition, a type-mismatch it used to pass with a warning; and the fail-closed arm of the verdict is served under its own code type-mismatch-fail-closed instead of type-mismatch + params.outcome. Added beside them (not counted as the arm): the codes type-mismatch-run-fails and type-mismatch-fail-open, and the exported type ExprFaultOutcome from @objectstack/formula.
Metadata-protocol: narrowed
Skills-docs: updated here — content/docs/data-modeling/formulas.mdx (the type-soundness row names each outcome) and the MCP skill text in packages/mcp/src/skill-md.ts (flow_condition now errors too). skills/** teaches no text-vs-number comparison in a flow condition or an advisory rule (grep for type mismatch, evaluates to null and the flow condition examples), so no skill or eval moves.

What changes

One ledger for both verdicts (@objectstack/lint). FAIL_CLOSED becomes faultOutcomeOf(site), the one function every call site asks what a site's runtime does on a fault. Its docblock carries the ledger, read off each evaluator. The answer goes to the shared validator as ExprSchemaHint.faultOutcome (the type-mismatch verdict) and to checkNullGuards as its NullGuardOutcome (the nullable-comparison verdict). The tokens are the same three, so one rule cannot get two different stories.

site runtime on a fault outcome type-mismatch before → now
validation rule condition / when / nested branches, severity: 'error' write rejected (#4649) fail-closed error → error (code renamed)
the same, severity: 'warning' / 'info' (top-level rule's severity; branches inherit it) logged, "write proceeded: the rule is advisory" (#24782) fail-open error "the write is refused" → error, true wording
field requiredWhen / readonlyWhen, option visibleWhen, hook condition refuses the write / aborts the operation fail-closed unchanged
record-triggered flow: start node condition, edge condition evaluateCondition throws; the run fails, the write lands run-fails warning "evaluates to null" → error
any other flow; a non-start node's (inert) condition — none warning, unchanged
formula, display predicates reads null / never refuses none warning, unchanged

Item 1, flow conditions. The twin refuses there (checkNullGuards(..., 'run-fails'), severity error, #29026), so the type mismatch does too. Its message: "… here a flow condition that cannot be evaluated fails the run: the flow stops at this condition, the write that fired it still lands, and the work the flow exists to do is not done." It applies on exactly the twin's sites: the top-level start node and every edge of a record-* flow. The twin's decision branch row has no type-mismatch counterpart: the branch slot is validated without the object's field types (checkDeclaredPredicate), so no type mismatch reaches it, before or after.

Item 2, advisory validation rules. Measured on the twin: it refuses every validation rule regardless of severity, with "the write is rejected fail-closed". So the type mismatch keeps the refusal (error) and both verdicts now word it as fail-open. The twin's 'fail-open' outcome was unused (its clause still described the pre-ADR-0137 requiredWhen), so its clause is reworded for the surface that really skips: "so the rule is SKIPPED fail-open: it is advisory, so the write proceeds, the rule is never applied (its advisory never fires), and the only trace is a log line saying it could not be evaluated". The type-mismatch arm says the same. Neither claims a refused write.

Item 3, refusal codes (@objectstack/formula). I took the "own code per arm" option. type-mismatch (warning, reads null), type-mismatch-fail-closed, type-mismatch-run-fails and type-mismatch-fail-open (errors) each name one template, chosen from a table keyed by the outcome (TYPE_MISMATCH_FAULT_ARMS), so a new outcome without its own code does not compile. The outcome param is gone. The fail-closed sentence is byte-identical to #29923's (the new pin's message is that text). Pinned in PINS (four new cases), and SWEEP_SCHEMAS gains the deal schema once per outcome, so the sweep reaches every code and holds its params to its message.

Readers of the renamed code:

Item 4. 18.validation-type-mismatch-refused acceptanceCriteria: "answers 200" → "answers 201".

@objectstack/spec. New semantic entry 18.flow-condition-type-mismatch-refused, registered as the ADR-0087 disposition. The changeset says what 18.3.0 already serves (the type-mismatch + outcome arm, shipped without a changelog line) and what this release changes about it, and that the #29904 changeset's "flow conditions keep the warning" no longer holds.

Why

Measured on main at 21510bba (after #29923 4c2cd29a), through the real saveMetaItem (draft) + publishPackageDrafts door. This is the objectql test below, run with the ledger ablated back to #29923's behaviour (ablation A5):

record-triggered flow, start condition `name > 5` on a required text field
  → outcome "published", advisory expression-invalid severity=warning:
    "type mismatch `string > int` — `name` holds text … This faults at runtime, so the expression
     silently evaluates to null (unless the value happens to be numeric)…"
advisory rule (severity 'warning') `record.name > 5`
  → refused, "… here an expression that cannot be evaluated fails closed: the write (or the
     operation a hook guards) is refused, not skipped."

Both are false for that role. The runtime readings: evaluateCondition throws on a fault (ADR-0032 §1c), and the record-change trigger isolates the write. evaluateValidationRules runs an advisory rule with advisory: true and logs unevaluableAdvisoryMessage ("write proceeded: the rule is advisory, so it was not applied to this write").

Tests

  • packages/formula/src/validate.test.ts: fail-closed now asserts code type-mismatch-fail-closed with exact params (no outcome). Two new tests cover run-fails (flattened, bare name, conversions validate clean) and fail-open.
  • packages/formula/src/expression-refusal-codes.test.ts: PINS for the three codes (exact params and message, a boolean case on fail-closed). SWEEP_SCHEMAS adds one schema per outcome.
  • packages/lint/src/validate-expressions.test.ts:
  • packages/mcp/src/mcp-validate-expression.test.ts: validation → type-mismatch-fail-closed; flow_condition → type-mismatch-run-fails error (was asserted a warning).
  • packages/objectql/src/publish-type-mismatch-fail-closed.test.ts: two new door cases, the advisory rule and the record-triggered flow.
Suites (at the final head, under os-verify-lock, shared box)
formula test       Test Files 64 passed (64)    Tests 1634 passed (1634)
lint test          Test Files 192 passed (192)  Tests 7953 passed (7953)
mcp test           Test Files 55 passed (55)    Tests 660 passed (660)
spec test          Test Files 808 passed (808)  Tests 24277 passed | 1 todo (24278)
spec test:repo     Test Files 89 passed (89)    Tests 1524 passed (1524)
objectql publish-type-mismatch-fail-closed.test.ts  Tests 5 passed (5)
typecheck + check:test-typecheck: formula, lint, mcp, objectql — exit 0; spec typecheck — exit 0
  (spec typecheck OOM'd once at the default heap on the shared box; exit 0 with --max-old-space-size=8192.
   objectql's test typecheck caught 4 errors in the new flow case — `raw` untyped — fixed in the final head.)
Ablations (each restored: blob == HEAD, git diff HEAD empty)
A1  lint: record-triggered flag forced false (`recordTriggered: false /* ablated-29926-a1 */`)
    × refuses a type mismatch in a record-triggered flow condition with the run-fails wording (#29926)
A2  lint: validation rule outcome forced `fail-closed`
    × refuses a type mismatch in an advisory validation rule without claiming the write is refused (#29926)
    × gives the nullable twin the same outcome on one advisory rule, and keeps fail-closed on an error rule (#29926)
      AssertionError: expected 'type mismatch `string > int` — `recor…' to match /advisory/
A3  formula: every outcome arm served as `type-mismatch` again
    × type-mismatch-fail-closed · × type-mismatch-run-fails · × type-mismatch-fail-open (PINS)
    × every refusal validateExpression reports on the sweep carries a code from its set … (SWEEP_SCHEMAS)
    × 3 validate.test.ts outcome tests — Tests 7 failed | 160 passed (167)
A4  mcp: `faultOutcome: 'run-fails'` removed from the flow_condition site
    × validates a flattened flow condition … — expected true to be false (ok)
A5  objectql through the built lint dist (marker `ablated-29926-a5` proven in dist/index.js, index.cjs,
    runtime.js, runtime.cjs): validation rules forced `fail-closed`, flow conditions no outcome
    × refuses the comparison in an advisory rule … — message says "is refused, not skipped"
    × refuses the comparison in a record-triggered flow condition … — outcome "published",
      advisory severity=warning "… silently evaluates to null …" (the card's premise, reproduced)
    Tests 2 failed | 3 passed (5); the #29904 cases stayed green (control)
Restore: each via ablation-replace (blob == HEAD, git diff HEAD empty); lint dist rebuilt clean by the
full build afterwards.

Acceptance notes

Verification

Rework r1 (head 06abf5ac, changeset text + one lint test): check-adr-0087-registration --base origin/main and --self-test, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main and --self-test, check:changeset-gate-self-tests: exit 0. validate-expressions.test.ts 459/459, lint check:test-typecheck and ESLint on the file: exit 0. dispatch-gates --commands derives the same 106-family list as before. Everything below was measured at 61a4cdbf, which differs only in those two files.

  • node scripts/pm/dispatch-gates.mjs --commands: 106 derived families, each run, all exit 0. --ran reconciliation: "106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN". Four exited 3 on the first pass for missing prerequisites (check-plugin-teardown-shape --self-test on a shallow clone; check:skill-examples, check:dual-build-cjs-loads and check:lean-entry-closure before a full build). They were run again after git fetch --unshallow and a full turbo run build (119/119 tasks): exit 0. The final head 61a4cdbf differs from the swept commit only in the new objectql test's type annotation (and the commit message). check:objectql-double-limit and check:engine-double-contract were run again on it: exit 0.
  • check-adr-0087-registration --base origin/main: "registered flow-condition-type-mismatch-refused (new here)".
  • pnpm lint (eslint . --no-inline-config): exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzfu5kYQW45XjsF79Rp

…n advisory validation rule is served a verdict that says what really happens

- Flow start / edge conditions of a record-triggered flow: refused with
  the run-fails wording (the run fails, the triggering write lands), the
  outcome and severity the null-guard twin already has there.
- Advisory (`warning` / `info`) validation rules: still refused, as the
  twin refuses them, but both verdicts now say the rule is skipped and
  the write proceeds instead of "the write is refused".
- One refusal code per outcome: type-mismatch (warning),
  type-mismatch-fail-closed, type-mismatch-run-fails,
  type-mismatch-fail-open; the role -> outcome decision lives in one
  ledger, `faultOutcomeOf`, for both verdicts.
- D3 entry flow-condition-type-mismatch-refused; the
  validation-type-mismatch-refused entry's create answers 201.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzfu5kYQW45XjsF79Rp
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 61a4cdbf1f30155ffa53e25239722c5791af0ad0
Local-runs: probe — node scripts/check-adr-0087-registration.mjs --base origin/main --head pr-29953 on the head: the gate refused to report a verdict (no tsx under this checkout, "an input it cannot produce is a red, never a skip"), so no verdict was taken from it; the dev's reported "registered flow-condition-type-mismatch-refused (new here)" is judged by hand below.
Scope: card #29926 and its two comments; PR #29953 body, files list, head check-runs; the full head diff; at pr-29953: .changeset/29926-type-mismatch-outcome-per-role.md, .changeset/29904-type-mismatch-fail-closed.md (still present), packages/formula/src/{validate.ts,expression-refusal.ts,index.ts,validate.test.ts,expression-refusal-codes.test.ts}, packages/lint/src/validate-expressions.ts (ledger 1283–1340, checkNullGuards wrapper 2382–2408, check 2409–2480, checkDeclaredPredicate 2557–2585, flow node/edge/decision sites 2744–3015, rule loop 3166–3240), packages/lint/src/validate-null-guards.ts (85–95, 280–300, 739–775), packages/lint/src/validate-expressions.test.ts (976–1090, 3330–3356, 3664, 3931), packages/mcp/src/{mcp-http-tools.ts,mcp-validate-expression.test.ts,skill-md.ts}, packages/objectql/src/publish-type-mismatch-fail-closed.test.ts, packages/objectql/src/validation/rule-validator.ts (36–37, 342–359, 3620–3695), packages/services/service-automation/src/dag-traversal.ts (1072–1135 evaluateCondition), packages/services/service-automation/src/engine.ts (6200–6250 start-condition gate), packages/triggers/trigger-record-change/src/record-change-trigger.ts (368–380), packages/spec/src/migrations/entries/semantic/18.{flow-condition,validation}-type-mismatch-refused.ts, packages/spec/src/migrations/types.ts (SemanticMigration), packages/spec/src/automation/flow-trigger-kind.ts; main at 21510bba: packages/lint/src/validate-expressions.ts:3204; scripts/check-adr-0087-registration.mjs header; AGENTS.md (Post-Task Checklist, Touched packages/spec); .claude/skills/pm-dispatch/references/contract-review.md; docs/adr/0087-*.md header. External inputs read as data: npm registry metadata and the published tarballs of @objectstack/formula@18.3.0, @objectstack/lint@18.3.0, @objectstack/mcp@18.3.0 (dist + CHANGELOG, extracted into the scratchpad); GitHub releases *@18.3.0 (target 83d7e4ae); the Release workflow runs on main today.

Face: narrowing (Clause-②: no (narrowing) on PR body and changeset) + published spec path. Run in-seat as an isolated reviewer at CONTRACT_REVIEW_TIER, fed the card, the PR and the head's check-runs only.

① Derived judgments

  • Accept-set narrowing at the author-time gate (record-triggered flow start / edge condition, type-mismatch warning → error). Correct and correctly derived. faultOutcomeOf({kind:'flow condition', recordTriggered}) returns 'run-fails' only when resolveFlowTriggerKind(flow) === 'record_change' (start config.triggerType starts with record-, flow-trigger-kind.ts:76), and is passed to check(...) only on the top-level start node (isTriggerGate) and on edges — exactly the twin's sites (checkFlowNullGuards, lint: the publish-time null-guard gate still excludes flow and edge conditions, so an unguarded ordering comparison on a nullable field ships and faults at run time #29026). Any other flow and any non-start node condition keep undefined → warning. Judged right.
  • Runtime outcome run-fails is what the engine does. Verified: dag-traversal.ts:1072+ evaluateCondition throws on a non-ok evaluation (ADR-0032 §1c, "NO silent fallback"); engine.ts:6229 calls it for the start condition inside execute()'s try; record-change-trigger.ts:371–379 catches the run failure and swallows it ("a flow failure must NEVER break the CRUD write that triggered it"). So the run fails at the condition and the write lands; the old "silently evaluates to null" text was false there. The new message is true.
  • Twin grading measured correctly. The twin's wrapper always pushes severity: 'error' regardless of outcome (validate-expressions.ts:2401), passed 'run-fails' on record-triggered start/edge/decision (validate-null-guards.ts:93, 284–286), and — on main 21510bba:3204 — called checkNullGuards(..., objectName) with the default 'fail-closed' on every validation rule regardless of severity. So "the twin refuses advisory rules, worded fail-closed" is a correct measurement; the PR keeps the refusal and re-words both verdicts. Judged right.
  • Advisory-rule handling is truthful about runtime. rule-validator.ts:3630–3672: severity !== 'error' → evaluateRule(..., advisory: true); an unevaluable advisory violation is neither pushed to errors nor to advisories, only logged via unevaluableAdvisoryMessage ("write proceeded: the rule is advisory, so it was not applied to this write"). A nested conditional branch is governed by the outer rule's severity (rule-validator.ts:36–37, 342–346), matching the ledger's "a nested branch inherits it" and ruleOutcome applied to every predicate of the rule. The new fail-open clause ("skipped, not refused: the write proceeds, the rule is never applied… the only trace is a log line") matches. The twin's previously unused 'fail-open' clause is re-worded for the surface that genuinely skips; the requiredWhen pins at test 3340–3353 assert its absence on fail-closed surfaces and still hold. Judged right.
  • Code split type-mismatch → type-mismatch (warning) + type-mismatch-fail-closed / -run-fails / -fail-open (errors), outcome param removed. The code is right: TYPE_MISMATCH_FAULT_ARMS is keyed by ExprFaultOutcome, so a new outcome without a code does not compile; CODE_TABLE gains the three; PINS is typed over ExpressionRefusalCode so each has a pin; SWEEP_SCHEMAS carries the deal schema once per outcome. The fail-closed sentence is byte-identical to fix(lint)!: publish refuses a text-vs-number comparison in a validation rule (or another fail-closed predicate) instead of warning, then refusing every write #29923's (checked against the shipped 18.3.0 dist string). But the claim that this arm "has not shipped in a release" is false. @objectstack/formula@18.3.0 (published 2026-11-03T15:09Z; the *@18.3.0 GitHub releases target 83d7e4ae, the version-packages merge, of which fix(lint)!: publish refuses a text-vs-number comparison in a validation rule (or another fail-closed predicate) instead of warning, then refusing every write #29923 4c2cd29a is an ancestor) carries outcome: "fail-closed" in dist/index.{js,mjs} and readonly outcome?: 'fail-closed' on ExpressionRefusalParams['type-mismatch'] in dist/index.d.ts; @objectstack/mcp@18.3.0 serves faultOutcome: "fail-closed" on the validation site, so an MCP consumer on 18.3.0 receives code type-mismatch with params.outcome; @objectstack/lint@18.3.0 ships the fail-closed grading. The publish gate: a validation rule comparing a text field with a number (record.name > 5) publishes with only a warning that says it "evaluates to null", then every write to the object is refused #29904 changeset is still in .changeset/ only because the version-packages PR chore: version packages #26517 branched before fix(lint)!: publish refuses a text-vs-number comparison in a validation rule (or another fail-closed predicate) instead of warning, then refusing every write #29923 merged, so 18.3.0's CHANGELOGs have no entry for it (0 mentions of 29904/29923 in the three shipped CHANGELOGs). Consequence: renaming the arm's code and deleting params.outcome is a narrowing of a served code and a served type, not an unreleased one. The PR does declare the rename inside its Clause-②: no (narrowing) line and the FROM → TO carries the consumer mapping (type-mismatch + params.outcome === 'fail-closed' → type-mismatch-fail-closed), so the declaration is structurally complete; what is wrong is the premise ("尚未发版" in the maintainer brief, "has not shipped in a release" in the body) and the changeset's "Supersedes, in the same release" framing, which will ship in the next CHANGELOG as the claim that the outcome param was introduced and replaced in one release, when 18.3.0 has been serving it. Judged wrong on the premise, right on the declaration — see ④ and REWORK-1.
  • New exported type ExprFaultOutcome and two new codes — widenings, correctly listed "beside" the narrowing and not counted as the arm. Right.
  • MCP validate_expression: validation → type-mismatch-fail-closed, flow_condition → type-mismatch-run-fails error. The code is right; the derived claim "answers what the gate will" is only approximately true: the tool has no severity input, so an advisory rule's author is told fail-closed where the gate serves fail-open, and flow_condition always declares run-fails where the gate warns on a non-record-triggered flow. Pre-existing shape of the site map; flagged in ③, not blocking.
  • D3 entry text 200 → 201 — correct; the data door answers 201.

② Semver level

minor on @objectstack/formula, @objectstack/lint, @objectstack/mcp, @objectstack/spec — consistent with Metadata-protocol: narrowed (a kind takes at least minor on a package the diff moves). Every moved published package is listed: packages/objectql moves only in a test file (publish-type-mismatch-fail-closed.test.ts, not shipped), content/docs is not a package. Both line-initial declarations (Clause-②: no (narrowing), Metadata-protocol: narrowed) are present in the PR body and the changeset and agree. The changeset carries the BREAKING banner, the FROM → TO, and exactly one ADR-0087 marker <!-- adr-0087: registered flow-condition-type-mismatch-refused -->. Check Changeset and Governed Surface Queue Guard are green on the head. Level judged right; the body's prose premise about the code rename is the defect (REWORK-1), not the level.

③ Boundary flags

  • Dev reading 1 — non-record-triggered flows keep the "evaluates to null" warning. Agree it is out of this card (the card scopes item 1 to record-triggered flows and the twin has no grading there). The text is still false for them (same evaluateCondition throws). File as a follow-up; not blocking. Pin note: the new lint test titled "keeps the warning on a flow that is not record-triggered, and on an inert node condition" exercises only the inert node (its flow is record-after-update); the non-record case is actually pinned by the pre-existing "warns on a flattened flow condition doing arithmetic on a bare text field" (start node without triggerType). Retitle or add a triggerType: 'scheduled' case (should-fix, non-blocking).
  • Dev reading 2 — record.x in a flow condition is never type-judged. Confirmed pre-existing (the flattened-scope soundness check types bare names only). File; not blocking.
  • Dev reading 3 — decision branches get no type verdict. Confirmed: checkDeclaredPredicate (validate-expressions.ts:2583) calls validateExpression('predicate', raw) with no schema hint, so no fieldTypes and no faultOutcome reach a decision branch; the twin alone refuses there. File (a decision branch name > 5 also fails the run and draws nothing); not blocking.
  • MCP site map cannot express severity or trigger kind (validation always fail-closed, flow_condition always run-fails); the tool description and skill text say "where it refuses the write / fails the run" without that caveat. File against packages/mcp; not blocking.
  • Release-lane defect surfaced by this review, not this PR's: 18.3.0 shipped fix(lint)!: publish refuses a text-vs-number comparison in a validation rule (or another fail-closed predicate) instead of warning, then refusing every write #29923's narrowing (formula / lint / mcp / spec) without its changelog entry because the version-packages PR chore: version packages #26517 branched before 4c2cd29a and was merged and released without regeneration. Needs a dedicated docs-only erratum for 18.3.0 and a check that a version-packages PR is regenerated against its merge-base before it lands. File (p2) to the release lane.
  • No open_questions block on the PR beyond the three readings above. PR author is objectstack-fleet[bot]; PR is a draft, mergeable_state: blocked. Check-runs at review time: 22 completed success, 0 failed, 35 in progress (Test Core 1–8, Test UI 1–16, Type Check lanes, Build Core/Docs, Build & E2E UI); landing rule ② (all green) still owed by the lane at landing.

④ Narrowing face

  • FROM → TO present and correct for the metadata narrowing: name > 5 on a text field → size(name) > 5 / score > 5 on a number field / double(name) > 5 only for numeric text; boolean active > 0 → active == true; validated clean in the formula tests. For the code narrowing: type-mismatch + params.outcome === 'fail-closed' → type-mismatch-fail-closed — present. Missing: the statement that the FROM side is what @objectstack/formula@18.3.0 / @objectstack/mcp@18.3.0 serve (the body and brief say the opposite).
  • Disposition: registered flow-condition-type-mismatch-refused; the id resolves to the new file packages/spec/src/migrations/entries/semantic/18.flow-condition-type-mismatch-refused.ts and is new in the diff, so the registered arm's two conditions hold by inspection (gate probe refused to report here; dev reports it green and Check Changeset is green). registry.ts is generated and gitignored, so nothing further to commit for packages/spec.
  • D3 entry shape: matches SemanticMigration (id, surface, replacement, reason, acceptanceCriteria, no conversionIds because nothing mechanical is rewritten) and the sibling 18.validation-type-mismatch-refused entry one for one; surface names the slots (automation.Flow start config.condition, edges[].condition, triggerType record-*), reason states the runtime fact that was verified above, acceptanceCriteria is a verify loop on os validate / os build / publish-drafts / a 201 create that runs past the condition. Judged right.
  • Migration path for authors: the error lands at the slot path (flows[0].nodes[0].config.condition, flows[0].edges[0].condition) with the conversion named in the message; the door test proves the refusal through saveMetaItem + publishPackageDrafts. Adequate.
  • Pins / ablations: adequate for the narrowing face — formula (PINS ×4 cases, SWEEP_SCHEMAS per outcome, validate.test.ts run-fails + fail-open), lint (record-triggered start+edge error, inert node warning, advisory warning/info incl. nested branch, twin agreement, #5017 receiver table gains rule.severity), mcp (both sites), objectql door (advisory rule, record-triggered flow). Ablations A1–A5 each name the test that fails and A5 reproduces the card's premise through the built lint dist. One pin-title gap (③ reading 1). No gap on the served-code rename beyond the prose premise.

Rework

  1. (blocking) The code rename is a served-code narrowing; "unreleased" is false. @objectstack/formula@18.3.0 (npm 2026-11-03T15:09Z) ships type-mismatch with outcome: 'fail-closed' (dist + index.d.ts, seat re-measured: 10 hits in index.d.ts); @objectstack/mcp@18.3.0 serves it on the validation site. The publish gate: a validation rule comparing a text field with a number (record.name > 5) publishes with only a warning that says it "evaluates to null", then every write to the object is refused #29904 changeset stayed pending only because version-packages chore: version packages #26517 branched before 4c2cd29a. Fix: (a) changeset: replace "Supersedes, in the same release" with the truth (the arm shipped in 18.3.0 without a changelog line; this release renames type-mismatch + params.outcome: 'fail-closed' → type-mismatch-fail-closed and removes outcome), keep FROM → TO; (b) PR body: strike 尚未发版 / "has not shipped in a release", state the 18.3.0 consumer impact in 风险与回滚. Declaration lines, level and ADR-0087 marker unchanged.
  2. (should-fix) Retitle or extend the lint test "keeps the warning on a flow that is not record-triggered, and on an inert node condition": its flow is record-after-update; add a scheduled / no-triggerType start+edge case.
  3. Filed by the seat, not this PR: the 18.3.0 changelog gap (release lane), the three dev readings, the MCP site-map severity/trigger-kind gap.

Implemented-by: claude/project-thread-ddyc2k
Reviewed-by: session_01WGufzibcN9oHa8kDbTTnL5

VERDICT: REWORK

…in 18.3.0; the non-record flow warning is pinned on a start and an edge

- Changeset: replace the "Supersedes, in the same release" paragraph.
  `@objectstack/formula@18.3.0` / `@objectstack/mcp@18.3.0` already serve
  `type-mismatch` + `params.outcome: 'fail-closed'` (shipped without a
  changelog line); this release renames that arm and removes `outcome`.
- Lint test: the non-record-triggered case now covers a flow with no
  `triggerType` and a `schedule` flow, start and edge condition each;
  the inert-node case is its own test.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzfu5kYQW45XjsF79Rp
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 06abf5acf76683083d0ef57c5a0d7af70053f4fa
Local-runs: none
Scope: delta review of rework r1 against the full review at 61a4cdbf1f30155ffa53e25239722c5791af0ad0 (one commit, git diff 61a4cdbf..06abf5ac: .changeset/29926-type-mismatch-outcome-per-role.md +1/−1, packages/lint/src/validate-expressions.test.ts +28/−1); the corrected PR body draft at /mnt/project-files/issue-29926/pr-body-r1.md (not yet live; the live head is 06abf5ac, draft PR); packages/spec/src/automation/flow-trigger-kind.ts:64–85 (re-read for the schedule arm); the new head's check-runs (2 success, 2 skipped, 5 in progress, 0 failed at review time). Everything else carries over from the review at 61a4cdbf, which read the whole diff, the runtime, the twin, the ADR-0087 gate, the D3 entries and the published 18.3.0 tarballs.

Face: narrowing (Clause-②: no (narrowing) on PR body and changeset) + published spec path. Run in-seat as an isolated reviewer at CONTRACT_REVIEW_TIER, fed the card, the PR and the head's check-runs only.

① Derived judgments

  • REWORK-1 (served-code rename premise). Fixed. The changeset's "Supersedes, in the same release" paragraph is replaced by "The renamed code shipped in 18.3.0": it states that @objectstack/formula@18.3.0 and @objectstack/mcp@18.3.0 already serve publish gate: a validation rule comparing a text field with a number (record.name > 5) publishes with only a warning that says it "evaluates to null", then every write to the object is refused #29904's refusal as type-mismatch with params.outcome: 'fail-closed', that the outcome param is in the 18.3.0 ExpressionRefusalParams type, that 18.3.0's changelog has no line for it because 29904-type-mismatch-fail-closed.md was not consumed by that release, and that this release renames the arm and removes the param so an 18.3.0 consumer moves to type-mismatch-fail-closed via the FROM → TO. Every one of those statements matches what I measured on the published tarballs and the *@18.3.0 release target 83d7e4ae. The "flow conditions keep the warning" supersession is kept as a separate, true sentence. The corrected body strikes the "has not shipped" bullet with a bracketed r1 edit stating the same facts, and 风险与回滚 now tells the maintainer that the arm shipped in 18.3.0 and that a consumer keyed to type-mismatch + outcome must move to the new code. Judged right.
  • REWORK-2 (mis-titled pin). Fixed. The old test is split: "keeps the warning on a flow that is not record-triggered: start and edge conditions" now runs two flows — a start with no triggerType and a start with schedule: '0 * * * *' — and asserts warning on both the start condition and the edge condition. Under resolveFlowTriggerKind neither is record_change (no record-* triggerType; config.schedule != null → 'schedule'), so nullGuardObject is undefined, faultOutcomeOf declares nothing, and the verdict stays the warning while objectName still binds title for the type check. "keeps the warning on an inert node condition of a record-triggered flow" keeps the original record-triggered inert-node body under a title that says what it exercises. The non-record branch of faultOutcomeOf is now pinned on both sites, so forcing recordTriggered: true would fail a named test. Judged right.
  • All other derived judgments (narrowing sites, run-fails runtime, twin grading, advisory fail-open wording, code split, MCP, D3 200 → 201) carry over unchanged: the delta touches no source file.

② Semver level

Unchanged: minor ×4 on @objectstack/formula / lint / mcp / spec, Clause-②: no (narrowing) and Metadata-protocol: narrowed line-initial and identical in the changeset and the corrected body, BREAKING banner, FROM → TO and the single marker <!-- adr-0087: registered flow-condition-type-mismatch-refused --> untouched by the delta. The dev reports check-adr-0087-registration, check-changeset-no-major and check-empty-changeset exit 0 on r1; the earlier head's Check Changeset was green and the changeset's frontmatter and declaration lines did not move. Carries over as right.

③ Boundary flags

  • Carry over, unchanged, as file items (none blocking): non-record-triggered flows keep a warning whose "evaluates to null" text is still false there (now at least pinned as the intended behaviour); record.x in a flow condition is never type-judged; decision branches receive no type verdict; the MCP site map cannot express a rule's severity or a flow's trigger kind; the release-lane defect that 18.3.0 shipped fix(lint)!: publish refuses a text-vs-number comparison in a validation rule (or another fail-closed predicate) instead of warning, then refusing every write #29923 without its changelog entry (version-packages chore: version packages #26517 branched before 4c2cd29a) — needs a docs-only 18.3.0 erratum and a regenerate-before-land guard, not this PR.
  • The corrected PR body is not live yet; the seat writes it through the relay. The record below is on the head; the body's declaration lines are unchanged from the live body, so the write is prose only.
  • Check-runs on 06abf5ac at review time: 2 success, 2 skipped, 5 in progress, 0 failed. Landing rule ② (all green) is still owed by the lane at landing. PR author objectstack-fleet[bot]; PR is a draft.

④ Narrowing face

  • FROM → TO: unchanged and complete for the metadata narrowing (size(name) > 5 / number field / double(name) > 5 / active == true) and for the served code (type-mismatch + params.outcome === 'fail-closed' → type-mismatch-fail-closed); the delta adds the missing fact that the FROM side is what 18.3.0 serves, so an upgrading agent grepping the CHANGELOG learns it is renaming a code it runs today. Judged right.
  • Disposition registered flow-condition-type-mismatch-refused, D3 entry shape, migration path and pins/ablations carry over from 61a4cdbf; the one pin gap named there is closed by the split test. No new gap introduced.

Implemented-by: claude/project-thread-ddyc2k
Reviewed-by: session_01WGufzibcN9oHa8kDbTTnL5

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

席位核对

Verdict: ACCEPT at head 06abf5acf76683083d0ef57c5a0d7af70053f4fa · 2026-11-03T17:39Z

  • Shape: 16 files: formula verdict and refusal codes, the lint faultOutcomeOf ledger, the MCP validate_expression site map, a new D3 semantic entry under packages/spec/src/migrations, docs, and one minor changeset. Narrowing face + published spec path ⇒ CONTRACT_REVIEW_TIER. The first review, 6610931370 at 61a4cdbf, was REWORK: the renamed code had shipped in 18.3.0. Rework r1 is PASS 6611149064 at this head.
  • Premise measured: the dev reproduced it through the built lint dist (ablation A5: the record-triggered flow published with the false "evaluates to null" warning). The runtime fact behind run-fails was verified by the reviewer: evaluateCondition throws, and the record-change trigger swallows the run failure while the write lands.
  • Gates: dispatch-gates 106/106 exit 0, suites green for formula 1634, lint 7953, mcp 660 and spec 24277 + 1524. pnpm lint exit 0. The changeset gates were rerun on r1 with exit 0. Landing waits for every check on this head to be green or an expected skip.
  • Accepted deviations: harness-pinned branch claude/project-thread-ddyc2k. The body was rewritten by the seat through the relay with the r1 text, plus the signed trailer.
  • Out of scope, where it goes: the seat files these follow-ups: the 18.3.0 changelog gap (release lane), non-record-triggered flow-condition wording, record.x never type-judged, decision branches with no type verdict, and the MCP site map's missing severity / trigger kind.
  • Landing: on all-green at this head, the seat sends pr_ready + automerge_enable through the relay.

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/formula, @objectstack/lint, @objectstack/mcp, @objectstack/spec, touching 21 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/formula/src/index.ts, packages/mcp/src/skill-md.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/automation/hook-bodies.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/data-modeling/field-types.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/data-modeling/fields.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/data-modeling/formulas.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/data-modeling/validation-rules.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/deployment/cli.mdx (via requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/deployment/validating-metadata.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/kernel/contracts/data-engine.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/permissions/system-context.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/protocol/objectql/security.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/protocol/objectui/concept.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/protocol/objectui/layout-dsl.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))

⛔ 9 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v15.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v16.mdx (via requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v17/17-0.mdx (via requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v17/17-1.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v17/17-4.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v17/17-5.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v18/18-1.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))
  • content/docs/releases/v18/18-3.mdx (via readonlyWhen (literal, a string literal in runStackExpressionPasses), requiredWhen (literal, a string literal in runStackExpressionPasses))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/formula/src/index.ts, packages/mcp/src/skill-md.ts) — pages documenting those are invisible to this run
  • 9 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 175 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 c92b88728cb533338759c3d700a683f29db1ee60 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f7c6d6773442690b53c23c3b02d1b8f1702b32d0 — the merge of head be72183fe8e6a02d5c699bbc52254fa93101700e into base c92b88728cb533338759c3d700a683f29db1ee60, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f7c6d6773442690b53c23c3b02d1b8f1702b32d0 && git checkout f7c6d6773442690b53c23c3b02d1b8f1702b32d0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c92b88728cb533338759c3d700a683f29db1ee60 be72183fe8e6a02d5c699bbc52254fa93101700e && git checkout -B drift-repro c92b88728cb533338759c3d700a683f29db1ee60 && git merge --no-ff be72183fe8e6a02d5c699bbc52254fa93101700e

node scripts/docs-audit/affected-docs.mjs --json c92b88728cb533338759c3d700a683f29db1ee60

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs c92b88728cb533338759c3d700a683f29db1ee60 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

Head moved · 2026-11-03T18:30Z · 06abf5ac → be6ffccb, moved by the seat through the relay pr_update_branch: a clean merge of main 3b8c724c, no conflict. The CI run on 06abf5ac (41685698809) concluded cancelled, because Test UI 13/16 hit its 30-minute limit with 1955 ✓ and no ✗. The relay's run_rerun_failed takes only a failure run, so the merge is what reruns CI.

  • The PR's own diff is identical: git range-diff shows both commits = (61a4cdb, 06abf5a), and the stat against the merge parent is unchanged at 16 files, +581/−102. The CONTRACT_REVIEW_TIER PASS 6611149064 and ACCEPT 6611189776 carry over to this head.
  • Landing: on all-green at be6ffccb, pr_ready + automerge_enable through the relay.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

Head moved · 2026-11-03T20:01Z · be6ffccb → 91cc0b22, moved by the seat through the relay pr_update_branch as a clean merge of main. On be6ffccb, lint.yml run 41694184578 failed only on an ESLint job checkout timeout (a 20-minute git fetch that never reached ESLint), and ci.yml run 41694184464 failed only on Test UI 16/16, cancelled at its 30-minute limit with no ✗. Both are runner slowness. The relay's run_rerun_failed takes ci.yml runs only, so the merge reruns both workflows.

  • The PR's own diff is identical: under git range-diff both commits (61a4cdb, 06abf5a) are =, and the stat against the merge parent is 16 files, +581/−102. The CONTRACT_REVIEW_TIER PASS 6611149064 and ACCEPT 6611189776 carry over to this head.
  • Landing: on all-green at 91cc0b22, pr_ready + automerge_enable through the relay.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

Head moved · 2026-11-03T22:29Z · 91cc0b22 → be72183f, moved by the seat through the relay pr_update_branch as a clean merge of main (c92b8872). On 91cc0b22 the only reds were runner timeouts (Test UI shards cancelled at their limit, the debt-ledger checkout timeout, Check Changeset cancelled), none of which the relay can rerun; main had not moved since 18112bfa until now.

  • The PR's own diff is identical: under git range-diff both commits (61a4cdb, 06abf5a) are =, and the stat against the merge parent is 16 files, +581/−102. The CONTRACT_REVIEW_TIER PASS 6611149064 and ACCEPT 6611189776 carry over to this head.
  • Landing: on all-green at be72183f, pr_ready + automerge_enable through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 22:57
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 22:57
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 9037b13 Nov 3, 2026
66 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ddyc2k branch November 3, 2026 23:27
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 size/l tests tooling

Projects

None yet

1 participant