feat(spec)!: FlowEdgeSchema.condition is an evaluated slot — composes EvaluatedExpressionInputSchema; structuralConditionRefusal drops the ast-only admission (#15807) - #17267
Conversation
…Schema; structuralConditionRefusal drops the ast-only admission (#15807) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
#15807) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
… json-schema manifest and docs for EvaluatedExpressionInputSchema Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…ow-edge-condition-evaluated
📓 Docs Drift CheckThis PR changes 3 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 134 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7cf62ea8d8a2256f322211291075cdf7b642b015 && git checkout 7cf62ea8d8a2256f322211291075cdf7b642b015
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a256f18962970878a0215109ddb1c4f0357d105a 496cdfa47347b7bc7ed21ad3c17a0dbcbae0041f && git checkout -B drift-repro a256f18962970878a0215109ddb1c4f0357d105a && git merge --no-ff 496cdfa47347b7bc7ed21ad3c17a0dbcbae0041f
node scripts/docs-audit/affected-docs.mjs --json a256f18962970878a0215109ddb1c4f0357d105a
|
…surface discovery roster so FlowEdgeSchema.condition stays discovered (#15807) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Contract review at
|
| before this diff | after | |
|---|---|---|
| that edge | answers a silent false |
— |
| the rest of the flow | trigger armed, everything else runs | ⛔ the whole flow is skipped at boot, trigger never armed |
| what the operator sees | nothing | one warn line |
The changeset's remedy sentence — "author a source, which the refusal itself prescribes" — is true for an author at objectstack validate / POST /flows, and false for a stored row, whose only refusal is that warn. Fix: one paragraph saying so.
F2 — must-fix — no-migration-prescription is the wrong disposition, by ADR-0087's own words and by this lane's own precedent
docs/adr/0087…md:596—no-migration-prescription"is refused by a body that carries a migration prescription". The changeset's "What an author does with a refused edge condition" paragraph is a prescription in everything but theFROM → TOlabel.⚠️ check-adr-0087-registrationpasses (exit 0 locally and in CI) because it detects a FROM/TO block, not the substance — and feat(spec)!: an evaluated expression slot requires a non-blanksource—EvaluatedExpressionSchema, composed by the assignment value envelope (#15430) #15810 on this same lane once had to reword a label to get past it, which is the tell that the gate's letter and the ADR's meaning have separated.- D2 is genuinely unavailable (no lossless mapping:
ast-only has nosource; dropping a blank condition flips the edge from never-fires to always-fires). That is an argument for a D3semanticentry, which exists for exactly "non-lossless change; structured TODO" — ⛔ not for the exemption. - ⭐ The precedent is three days old and on this lane:
233222e2e(driver-memoryanalytics silently accepts an unparseabledateRangeand matches EVERY row — and the platform's own documented spelling'Last 7 days'is one of them #16041, 2026-09-07) closedtimeDimensions[].dateRange's string arm — an accept-set narrowing with no D2 conversion possible — and registered asemanticentry anyway, itsreasonsaying in as many words "⚠️ No D2 conversion and no stored-metadata rewrite". That PR's value was query-time, explicitly "not asys_metadatashape". This one is asys_metadatashape. The weaker case registered; the stronger one is claiming the exemption. - The in-repo census (0/0, control lit) is a repo reading. The changeset uses it to conclude
migrate meta"has nothing to rewrite" — conflating nothing to rewrite mechanically (true, D2 is impossible) with nothing to notify (false, exactly the F1 case).
Fix: one semantic entry in packages/spec/src/migrations/registry.ts in the #16041 shape, marker flipped to adr-0087: registered <id>, projections regenerated.
⛔ This seat does not take the alternative on offer. The reviewer noted the seat could instead rule that launch-window narrowings with a zero repo census keep no-migration-prescription. That ruling would contradict #16041 and would be a decision about how ADR-0087's categories apply — ⛔ not a seat's call, and ⛔ not one to make implicitly inside a changeset. Registering the entry is the conforming action and the one the precedent already took.
⚠️ One claim inside the verdict that does NOT check out — corrected rather than propagated
F2 says packages/spec/src/migrations/registry.ts "was named a held single-writer path on this lane (#15430 claim 5549282927)". Measured, both legs fail:
scripts/check-single-claim-paths.mjsSINGLE_CLAIM_PATHSdeclares exactly one path —.objectui-sha— and the file's own self-test pins the list at ≤ 5 entries precisely so it stays a declaration.migrations/registry.tsis not on it, which is why theNo other open PR may claim the same single-writer pathcheck is green on every PR here.- Claim
5549282927's file face namespackages/spec/src/automation/builtin-node-config.zod.tsand a siblingEvaluatedExpressionSchema— ⛔ not the registry. And spec/formula:ExpressionSchemaaccepts anast-only envelope that no engine can evaluate — it validates, it registers, it faults at run time #15430 itself ispm:blocked, unassigned, untouched since 2026-09-05, so nothing is actively holding anything.
⭐ The action the finding prescribes is still right, and I have the contention measured independently: PR #17146 (awaiting the maintainer's hand) and PR #17257 (green, held) both touch packages/spec/src/migrations/registry.ts. It is generated and ⛔ not routed to merge=os-regen, so a clash is an ordinary visible conflict, not a silent drop. The rework order carries the sequencing; the seat owns landing order.
I adopt verdicts verbatim — but a factual claim inside one that I can falsify gets corrected here, not carried forward.
Non-blocking, folded into the same round where cheap
- F3 —
packages/spec/src/shared/expression.zod.ts:147-152still promises "exactly one issue, atsource, for every refused shape". True for direct callers, false once composed through the new union, whereast-only and blank-bare-string surface asinvalid_unionat the slot. The new docblock records it; the old one promises the opposite. One sentence. In the round. - F4 —
objectstack validaterefuses anast-only edge withEVALUATED_EXPRESSION_SOURCE_REQUIRED(CLI schema step) and withSTRUCTURAL_CONDITION_SHAPE_REFUSAL(lint's raw-input path). Both prescribesource. Recorded, no fix now. - F5 / F6 — see the ACCEPT list.
What the review confirmed, so it is on the record
Ruling executed and the three doors agree; the removal of #15792's rec.ast !== undefined admission is in scope by the card's own text ("Revisit #15792's admission in the same PR") — the dispatch's fence was on typing the open record, which the diff does not do, so ⛔ the fence is not crossed. Clause ② yes reproduced independently (7 tells read one by one: T3 ×4 are the two new published rows, T2 ×3 are the new union's own members, ⛔ not members added to an existing set). Level and banner coherent with the launch-window convention. ADR grep across the naming ADRs: no accepted ADR reversed — ADR-0032 :95/:102 writes the IR as {dialect, source, ast?}, which this diff is consistent with, and the "M9.2 makes ast required" promise lives only in a docblock, in no ADR (grep -l "M9\.[12]" docs/adr → 0). Scope exact at 23 files, nothing unauthorised. The dogfood roster re-registration is real, with its own red-then-green as the control. Both NOT-MEASURED gates are owned by green CI jobs — ⛔ not gaps.
#17121 "the known turso flake" in briefs. The reviewer is right that this overstates a ledger status it does not have — #17121 is an open p1 with its real-defect hypothesis unresolved and no queue-flake-anchor. The causation reading here still holds (this diff touches 0 files under packages/drivers/, the failing signature is byte-identical, the rerun is green), but ⛔ "known flake" is not a status I get to assert.
File at ACCEPT (⛔ not folded into this PR)
| what | 承接者 |
|---|---|
config.condition's whitespace-only string is still a silent false on both doors while the edge now refuses the same blank at parse — one shared structuralConditionRefusal, two rulings. Needs a ruling, ⛔ not a lane's call |
domain:services seat (the #15662/#15792 lineage) |
objectui FlowDesignerEdge.condition imports ExpressionInput so it cannot describe a spec-rejected condition — after this PR the mirror is wider than the server again, the exact drift its docblock claims to close |
objectui / domain:ui seat, at pin-bump time, with Blocked-by: |
The "M9.2 — ast becomes required in build output" promise lives only in a docblock and is now load-bearing for two repos' pins. Decide it in an ADR (an 0032 amendment) or drop it |
domain:spec seat |
| #17121 — a second cross-PR hit of the signature (already dispatched, ⛔ not re-filed) | domain:engine |
⛔ needs:contract-review stays on the PR and the card. ⛔ The PR stays draft. The rework round follows.
Generated by Claude Code
…ow-edge-condition-evaluated
…edge condition, and state the stored-flow blast radius (#15807) Contract-review REWORK on this branch. No code change: the schema, the three doors, the pins and the roster are untouched. F1 — the changeset's remedy sentence was true for an AUTHOR and false for a row already in `sys_metadata`. `applyConversionsToStoredItem` is deliberately not applied to `flow` (conversions/stored.ts; same skip in the database loader's `rowToData`), so the read path never re-validates a stored flow; the door is `registerFlow` -> `canonicalizeStoredFlow` -> `FlowSchema.parse`, and all three boot paths in service-automation's plugin wrap it in try/catch, warn and continue. A stored flow carrying a refused edge condition therefore stops being registered ENTIRELY -- trigger never armed, whole flow dark, announced by one warn line. Stated as its own paragraph. F2 — `no-migration-prescription` is refused by a body carrying a migration prescription (ADR-0087), and the "What an author does" paragraph is one. D2 is genuinely impossible (an `ast`-only envelope has no `source` to derive; dropping a blank condition flips the edge from never-fires to always-fires), which is the argument FOR a D3 semantic TODO rather than for the exemption. Registered in the shape of the three-day-old precedent on this lane, #16041's `analytics-time-dimension-date-range-vocabulary-closed`, which also had no D2 available and registered anyway. Marker flipped to `registered`. The entry is authored as a FILE under migrations/entries/semantic/ and the registry regions regenerated -- those regions are generated (#7297/#6957) and a hand edit between the markers is reverted by the next gen run. Both projections correctly show NO diff: they fold only up to PROTOCOL_MAJOR (17) and this is a major-18 entry -- controlled by the precedent entry being absent from them too, against a lit control of a major-17 id that is present. F3 — `EvaluatedExpressionSchema`'s "exactly one issue, at `source`, for every refused shape" is true only for a direct caller; composed through `EvaluatedExpressionInputSchema` the `ast`-only and blank-bare-string shapes surface as one `invalid_union` at the slot. One sentence scoping the promise. Level unchanged and re-derived, not inherited: `@objectstack/spec` `minor` plus the **BREAKING** banner. The disposition marker records HOW a break reaches the upgrade channels; it is not an input to the semver level. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
Rework round landed —
|
Delta review at
|
| check | reading | |
|---|---|---|
| ① | at-tier verdict for THIS head | this document — but the F1 round moves the head, so a short delta re-check follows |
| ② | --pair 17267 |
exit 0 |
| ③ | every check green, newest-run-per-name | ⛔ not yet — 5–7 still running at 03:13Z (two independent reads), 0 red |
⛔ needs:contract-review stays on the PR and the card. ⛔ PR stays draft. #17121 is an open p1 with an unresolved hypothesis and no queue-flake-anchor — ⛔ not a "known flake"; if Test Core (4/6) reds on that signature it is a causation question for this seat, ⛔ not a content finding.
At ACCEPT — unchanged, nothing new
config.condition's whitespace-only string (承接者 domain:services); objectui's FlowDesignerEdge.condition mirror, now wider than the server (承接者 objectui / domain:ui, at pin-bump, with Blocked-by:); the "M9.2 — ast required in build output" promise that lives only in a docblock (承接者 domain:spec); #17121 (already dispatched). Plus the PR body's doubled attribution footer — a platform append, ⛔ not to be "fixed" by re-sending a body that already carries one.
Generated by Claude Code
….path`, not a CLI verb that does not exist (#15807) The stored-flow paragraph is written for the operator whose row exists only in `sys_metadata` — a Studio-authored flow with no config file. It told that reader to "Run `objectstack validate` on the exported stack". There is no export verb in the CLI: oclif discovers commands from `dist/commands/**`, which holds no `export`; `packages/cli/src/commands/meta/` offers `get|list|register|delete| resync`; and `validate` consumes a config file (`validate.ts` `loadConfig`), not a database. `git grep 'objectstack export|meta export|exported stack'` over `packages/cli/src` and `content/docs` returns only the module-export sense at `utils/config.ts:352`, against a lit control (`loadConfig` = 4 in `validate.ts`, `objectstack validate` = 8 doc files, `objectstack export` = 0). The route that does exist is the warn line the paragraph already names: all three boot sites spread `describeThrownForLog(e)` into the log meta, which reshapes the ZodError into `issues[].path` and renders it through `formatIssuePath` — numeric segments become `[i]`, so the offending edge reads `edges[N].condition`. `objectstack validate` is kept only where it is true: for a stack authored in config files. Folds in the path-spelling nit at the same time. Each spelling now matches the door it describes: the CLI prints `path.join('.')` over an `ObjectStackDefinitionSchema` parse, so its reading is `flows.N.edges.N.condition` (pinned, `flow-edge-condition-evaluated.test.ts`), while the warn renders `edges[N].condition`. Both carriers move — the changeset and the D3 entry's `acceptanceCriteria` — because leaving the false locator in the ledger keeps it in front of the reader who most needs it. `gen:migration-registry` re-run; the generated region is the entry's text modulo indent. No code, schema, door, pin or roster changes, and the not-interchangeable warning is untouched. Level re-derived and unchanged: `@objectstack/spec` `minor` + **BREAKING** + `adr-0087: registered flow-edge-condition-evaluated-slot-source-required`. Editing prose inside a `reason`/`acceptanceCriteria` string and a changeset paragraph moves nothing published and changes no break. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
Delta review at
|
| # | pre-check | reading |
|---|---|---|
| ① | at-tier verdict for THIS head | PASS WITH FINDINGS, no must-fix |
| ② | --pair 17267 |
exit 0, re-derived by the reviewer at 03:43Z |
| ③ | every check green, newest-run-per-name | 31 success / 2 skipped / 0 red, 1 still running (Lint & Repo Gates) at 03:46Z |
⇒ Landable the moment ③ converges. Test Core (4/6) — the shard carrying #17121's signature — is green on this head; #17121 remains an open p1 with fix PR #17282 unmerged, so it is a queue-ejection risk on the merge-queue full-suite run, ⛔ not a finding here.
⛔ But it is not being enqueued yet, and the reason has just doubled in cost
This PR touches packages/spec/src/migrations/registry.ts too (measured: it is in the 25-path diff). So it falls under the same hold as PR #17257: PR #17146 is a governed PR sitting with the maintainer for a hand merge and shares that file.
⇒ Two PASSed, green, landable PRs are now held on one human action, where an hour ago it was one. That changes the arithmetic, and the seat's recommendation with it — stated on #17146 rather than decided quietly here.
Generated by Claude Code
Fixes #15807
Clause-②: yes
What this does
FlowEdgeSchema.conditionwas typedExpressionInputSchema.optional()— the persistence contract,sourceORast.AutomationEngine.evaluateConditionreads it asexpression.source ?? '', so anast-only envelope authored on an edge parsed, registered, passedobjectstack validate, and then landed in the empty-source arm and answered a silentfalse: a branch that quietly never fired (measured on #15430, comment 5550509137). A whitespace-onlysourcewas the same seam through the other key. The silence is the defect; typing the key is the fix.packages/spec/src/shared/expression.zod.ts— newEvaluatedExpressionInputSchema(+ typeEvaluatedExpressionInput), the sibling ofExpressionInputSchemafor an evaluated slot: the bare-string shorthand still normalizes to{ dialect: 'cel', source }but must be non-blank after trimming; the envelope arm composesEvaluatedExpressionSchema(sourcerequired, non-blank) instead ofExpressionSchema. The union's error map answersEVALUATED_EXPRESSION_SOURCE_REQUIREDexactly when the refusal is the evaluated-slot rule (a blank string, an object with no stringsource) and yields to zod's default otherwise (a number, a dialect outside the enum) — measured, not assumed; the docblock records why both spellings land asinvalid_unionat the slot while a blanksourceinside an envelope lands ascustomatsource.packages/spec/src/automation/flow.zod.ts—condition: EvaluatedExpressionInputSchema.optional(); the.describe()names the rule.packages/spec/src/automation/flow-node-expression-paths.ts— the service-automation: refuse a structural flow condition that is neither CEL text nor an expression #15792 admission, revisited (below).packages/spec/src/index.ts— the two new exports. Regenerated:api-surface/,export-origins/,declaration-map/,json-schema.manifest/shared.json,content/docs/references/**(the reference table for the edge now readssource: string, required).ExpressionSchema/ExpressionInputSchemaare NOT narrowed. ⛔start.config.conditionis not typed here (no schema stands in front of the start node's openconfig).How the #15792 admission was disposed of: removed
structuralConditionRefusaladmitted an envelope throughtypeof rec.source === 'string' || rec.ast !== undefined. Theastclause existed only because the spec still admitted the shape atedge.condition(#15430 comment 5550509137 says so in as many words). With the edge schema closed, keeping it would have left the refusal deliberately holed for a shape the schema no longer admits on one surface and the engine cannot run on either —cel-engine.tsrefuses AST-only evaluation, andevaluateConditionnever readsast.So the clause is gone: an authorable envelope is one carrying a string
source(anastbeside it is fine).STRUCTURAL_CONDITION_SHAPE_REFUSALnow reads "an expression envelope carrying a stringsource" and says why; theFound …detail names theast-only case specifically. Consequence onconfig.condition(a start node's trigger gate, a decision node's predicate — an open record, so the structural pass is the only producer-side gate there): anast-only envelope is refused atregisterFlow, reported as a locatederrorbyobjectstack validate, and refused byevaluateConditionwith the same sentence, instead of answering a silentfalse. This is the one function both slots share, so the card's "revisit in the same PR" necessarily reaches that slot; the change is stated in the changeset. The whitespace-only STRING ruling onconfig.condition(#15662: consistentfalseon both sides) is untouched.packages/services/service-automation/src/engine.tsandpackages/lint/src/validate-expressions.tschanged in comments only — the behaviour arrives through the spec dependency.Premise re-derived on origin/main at
ae19f5edb(BASE), before editingControls read, not counted:
ExpressionInputSchemainflow.zod.ts= the import line plus:567, the liveconditionkey;EvaluatedExpressionSchemainexpression.zod.ts= the export, its two type aliases and its docblock, i.e. the live schema, not a tombstone.Consumer readings, pinned
ast-only edgesourceedge (envelope / bare string)ast-onlyconfig.conditionFlowEdgeSchema/FlowSchema(spec)invalid_unionatedges.0.condition, the published sentencecustomatcondition.source/invalid_unionatconditionregisterFlow(service-automation)FlowSchema.parse, sentence + pathSTRUCTURAL_CONDITION_SHAPE_REFUSAL+ "Found an object carrying anastbut no stringsource"evaluateCondition(service-automation)falseobjectstack validate(lint,runAuthoringRuleson the raw path)erroratedge 'e1'erroratnode 'start'/node 'branch'objectstack validate(cli, step 2 =ObjectStackDefinitionSchema.safeParse)invalid_unionatflows.0.edges.0.conditionThe CLI reading is pinned at the spec level on the very call
packages/cli/src/commands/validate.tsmakes (ObjectStackDefinitionSchema.safeParse, thenformatZodErrors); the CLI e2e layer is declared to CI.Tests and gates
Pre-merge on
451c15e03(branch baseae19f5edb), then again on the final head64769f0a3(afterbash scripts/pm/os-regen-merge.shbrought 6 commits fromorigin/main;check:generated"All 15 generated artifacts are up to date"), all underscripts/pm/os-verify-lock.sh, verdicts read from itsVERDICT command-exitline:pnpm --filter @objectstack/spec test—Test Files 471 passed (471) · Tests 13236 passed (13236)on both heads.pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2—127 passed · 1504 passedon both heads.pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2—103 passed · 3681 passedpre-merge,103 passed · 3707 passedon the final head (main brought lint tests).pnpm --filter … typecheckfor spec, service-automation, lint on64769f0a3— exit 0,check:test-typecheck: OKfor all three.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 107 families on64769f0a3; all 107 run with exit codes written to disk first;--ranreconciles107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN. 104 exit 0.check:skill-exampleswas exit 1 for a missingclient-reactdist, re-run green after building the client closures. NOT MEASURED (PREREQUISITE NOT MET, exit 3, both need every package'sdist/):check:dual-build-cjs-loads,check:type-check-debt— declared to CI'sLint & Repo Gates.check-adr-0087-registration✓ ([BREAKING+bang] not-required (no-migration-prescription)),check-changeset-no-major✓,check-empty-changeset✓.After the first CI run — one red that was mine, one that was not
cccf75fcb.Dogfood Regression Gate (3/3)on64769f0a3failed inpackages/qa/dogfood/test/expression-conformance.test.ts—STALE covers — surface no longer in source: automation/flow.zod.ts:FlowEdgeSchema.condition. The ADR-0058 D7 ledger discovers expression surfaces by a roster of input-schema names, and its own docblock says a new narrowed alias belongs in that roster on the commit that introduces it (Narrow the settings-manifestvisibledeclaration to the grammar the save-time evaluator actually implements (#7169 alignment half, measured 1-vs-93) #7327's shape).EvaluatedExpressionInputSchemais now listed; the ledger test is green locally (Tests 5 passed (5), after building@objectstack/verify) and on CI. That CI failure is the red control for the roster row.Test Core (4/6)oncccf75fcbfailed once in@objectstack/driver-tursosrc/turso-driver-timeout.test.ts:122("a stalled remote fails the operation within the configured window, as TIMEOUT / 504" —expected 0 to be greater than 0onremote.requests()). No file in this PR is underpackages/drivers/, the same shard passed on64769f0a3with the identical tree outsidepackages/qa/dogfood/test/, and a singlererun-failed-jobspassed. Reported on the card, not "fixed" here.cccf75fcb: gates re-derived (same 107 families), all 107 re-run with exit codes to disk,--ranreconciles107 run, 0 NOT-MEASURED, 0 UNRUN; same two PREREQUISITE NOT MET rows (check:dual-build-cjs-loads,check:type-check-debt) as above. CI, newest run per check name: every required context green —Lint & Repo Gates,TypeScript Type Check,Test Core,Dogfood Regression Gate,Build Core,Temporal Conformance (live PG + MySQL)allsuccess.Clause ② — measured on the finished diff
node scripts/pm/check-widening-tells.mjs --declaration no --diff pr.diff→ exit 4, 7 tells: T3 ×4 (two new rows on therootandsharedpublished entry points:EvaluatedExpressionInputSchema,EvaluatedExpressionInput) and T2 ×3 (the new union itself, read as a closed set gaining members). With--declaration yes→ exit 0. So the measured direction isyes— the public surface grows by one schema and one type. It is NOTyesfor the reason the claim feared: nothing becomes newly acceptable anywhere; the edge slot's accept set only shrinks, and the T2 hits are the new schema's own two arms, not a member added to an existing set. The claim's provisionalyesstands; the seat need not move it.Changeset
.changeset/flow-edge-condition-evaluated-slot.md—@objectstack/spec: minor(the launch-window convention;majoris refused bycheck-changeset-no-major), a**BREAKING**banner in the accept-set sense, and an ADR-0087not-required (no-migration-prescription)disposition: no key is renamed, retired or re-typed; both refused spellings never evaluated on any release; the census re-run for edges overexamples/ packages/ content/ skills/atae19f5edbfound 0ast-only and 0 blank-string edge conditions against a lit control of 1 (therecord.amount < 500example inflow.zod.ts).验收备注
config.conditionis still admitted bystructuralConditionRefusal(a consistentfalseon both sides, service-automation:evaluateConditionanswers a silentfalsefor a non-string predicate, and a non-stringconfig.conditionregisters clean #15662's ruling), while the edge now refuses it at parse. Two slots, two rulings, both stated in code; a one-rule flip is a ruling, not a lane's call. 承接者:无.EvaluatedExpressionSchema's docblock promises "exactly one issue, atsource" for every refused shape; through the input union theast-only and blank-string spellings surface asinvalid_unionat the slot (both arms abort). Recorded inEvaluatedExpressionInputSchema's docblock and pinned; the bare schema's promise still holds for direct callers. 承接者:无.packages/lint/src/validate-org-axis-red-lines.tsexpressionTextreadsrec.astforSharingRule.condition— that slot is the persistence contract and is scanned, not evaluated; correct as is. 承接者:无.check:dual-build-cjs-loadsandcheck:type-check-debtcannot be measured without a full package build; CI owns that run.Generated by Claude Code
Generated by Claude Code