Repository navigation
feat(service-automation,lint,spec,verify)!: the single-brace resolver and the legacy condition path are deleted — every condition is CEL, a value-slot literal is handed on as written (#19939 pass 4, S7) - #23252
Conversation
… legacy condition path (#19939 S7) Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
…d docblocks follow the deleted resolver (#19939 S7) Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
…S7 changeset (#19939 S7) Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
…S7) Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
…tirement reached (#19939 S7) Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 5 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 142 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 239173ff7a08ef0c4dd7167b94c38343f3fa1026 && git checkout 239173ff7a08ef0c4dd7167b94c38343f3fa1026
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin afbf960c2649d87ff6503721b9c290ec679356c6 05539d4230bb5487498d664af0ea563f79950f76 && git checkout -B drift-repro afbf960c2649d87ff6503721b9c290ec679356c6 && git merge --no-ff 05539d4230bb5487498d664af0ea563f79950f76
node scripts/docs-audit/affected-docs.mjs --json afbf960c2649d87ff6503721b9c290ec679356c6
|
Contract reviewServed-tier: Read: card #19939 (body and all 76 comments; the dispatching seat's amendment ① Derived judgmentsA. What leaves
B. What leaves
C. D. Scope against the order. The stage plan E. The 15 in-place corrections, each judged on this head. Invariants measured on all 15 between
F. Keeping the corrected files under G. The red H. Check-runs on ② Semver level
③ Boundary flagsNewest report
No FAIL item. The one escalation is item 5, a carry on released text that this PR could not carry itself. Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)
改了什么: 这是 #19939 系列(退役流程里的单花括号模板写法
为什么改: ADR-0032 规定流程只保留一种模板分隔符 风险与代价(含回滚):
席位意见: 建议批准。
你要做的: 在本 PR 上点 Approve(请审已发给 Generated by Claude Code |
Fixes #19939
Clause-②: no (narrowing)
Pass 4, stage S7 — the close-out of #19939: the single-brace
{…}template dialect's resolver is deleted (ADR-0032 Sequencing 6: "delete the single-brace resolver"), with the engine's legacy condition path, the lint's mirror of the grammar, and the canon and pending release text that still described them. Every earlier stage (S1–S6) retired the dialect position by position; this one removes what was left reading it.维护者速读(草稿)
改了什么
{…}。负责解析这种写法的运行时代码被整段删除。{amount} > 100这类旧写法的条件会被明确拒绝,并告诉作者正确写法amount > 100。{},现在原样写入。os validate)不再按旧写法报告问题,也不再在旧写法的位置给出旧写法的修改建议。为什么改
{{ }},并要求删除单花括号解析器。S1–S6 已经逐个位置把旧写法退役,解析器只剩两处只会看到普通字面量的调用,以及一段已注册流程根本走不到的条件旧路径。风险与代价(含回滚)
@objectstack/verify的测试入口):旧写法从"按文本比较"变为报错。本仓库源码、示例和文档里没有这类调用。@objectstack/lint移除 4 个导出(旧语法的分类工具)。本仓库和 objectui 都没有引用。席位意见
你要做的(一个动作)
What changes
service-automation'sbuiltin/template.tslosesinterpolate,interpolateString,resolveToken, the value-expression function table andFlowExpressionFunctionError(none was a package export), with their four unit-test files (template-array-index,template-date-offset-dst,template-functions,template-object-token). The file keeps the text-slot renderer (renderTextSlot,textTemplateScope,FlowTextTemplateError).value-slot-map.tsresolveValueSlot(the literal arm every value slot shares) and theassignmentexecutor (logic-nodes.ts, all three shapes) saw literals only, since the value-slot judge refuses every token. They hand oncopyLiteral(value): arrays and plain objects copied, every other value as it is. That fixesinterpolate()writing aDateas{}(stage plan6096263424's S7 item), pinned throughregisterFlow+executeon the threeassignmentshapes and oncreate_record/update_record.AutomationEngine.evaluateConditionloses the template branch, its hole discriminator, and its text comparison (TEMPLATE_HOLE,templateHoles,refuseUnresolvedTemplateHole,refuseUnresolvedCelOperand,compareValues,unquoteLiteral). Every condition is CEL. A braced string is refused as the CEL brace trap, naming its source. Atemplateorflowenvelope is refused before evaluation. Measured first:registerFlowrefused all 12 condition spellings probed (start, edge, decision,visibleWhen,wait; bare braced strings andtemplate/flowenvelopes), so only a direct call reached the path.validateFlowTemplatePathsreads{{ }}holes only and skips a CRUD node'sfilteroperands (value slots since S6), so its filter-positionerrorand every finding on a single-brace token are gone. The value-slot and text-slot judges refuse those tokens asexpression-invalid. Its 14 single-brace filter fixtures and the reference-integrity suite's one are re-judged.flow-double-brace-interpolationandflow-bare-dollar-reference, at a string outside every slot and literal, say the value is read as written instead of prescribing a single-brace token.flow-template-grammar.ts, the lint's mirror of the deleted grammar (its test readtemplate.tsfrom disk), is deleted with its four exports.FlowSchemaexample'sconnector_actioninput holds a literal, since nothing reads a token there and no connector-input value slot is built. The step-18 registry text: the text-slot entry says no other flow string reads the dialect, and the value-slot entry (flow-value-slot-template-dialect-refused, order 88) names every value slot S1–S6 retired, where it named only pass 1's.flow-template-token.tsis now the judges' frozen reading of a retired grammar. Docblocks inflow-text-slot-template.ts,builtin-node-config.zod.tsanddata/context-tokens.zod.tsare updated, with their two reference pages regenerated. Also updated: the verify handle's doc, a formula stdlib comment, and the flows guide's paragraph that still taught a{var}condition.FROM → TO
engine.evaluateCondition('{amount} > 100', vars)engine.evaluateCondition('amount > 100', vars)"{status} == 'open'",'{status} == open'"status == 'open'"{ dialect: 'template', source: "{flag} == true" },dialect: 'flow'{ dialect: 'cel', source: 'flag == true' }classifyFlowTemplateToken(inner)(@objectstack/lint)valueSlotTemplateRefusals(value)/textSlotTemplateRefusal(text)(@objectstack/spec/automation), which refuse a token with the spelling that replaces itFLOW_TEMPLATE_DATE_FUNCTIONS,FLOW_TEMPLATE_VALUE_FUNCTIONSisoDate(today()),isoDatetime(now()),round,floor,ceil,abs,min,maxFlowTemplateTokenVerdict@objectstack/specThe retired
flow-filter-token-unknownrule (S6) gets its own FROM → TO in its changeset, corrected below: rule idflow-filter-token-unknown→expression-invalid(EXPRESSION_INVALID);validateFlowFilterTokens(stack)→validateStackExpressions(stack);FlowFilterTokenFinding→ExprIssue;FLOW_FILTER_TOKEN_UNKNOWN→EXPRESSION_INVALID.The new changeset is
.changeset/19939-flow-single-brace-resolver-deleted.md. Bumps:@objectstack/service-automationmajor,@objectstack/lintmajor,@objectstack/specpatch,@objectstack/verifypatch. ADR-0087 disposition: not required, already registered (flow-value-slot-template-dialect-refused,flow-text-slot-single-brace-refused), accepted bycheck-adr-0087-registration.check-empty-changesetis red on purpose: DELIBERATE CORRECTIONscripts/check-empty-changeset.mjsrefuses a diff that modifies a changeset present on the merge base. It names two classes with opposite remedies. COLLISION means restore theirs and rename yours. DELIBERATE CORRECTION means "do NOT restore it — say so on the PR and get it confirmed". This PR is the second class for all 15 files, exactly as #23126 was: each file is a release note that a later landing on thenexttrain made false (S6,615c696046) or that this PR makes false. Nothing is restored to turn the gate green. It stays red (exit 1, 15::errorlines, one per file) until the at-tier contract review on this head confirms the correction.What moved since #23126. The Version Packages PR #21988 merged as
d889448b9and18.0.0-next.0is on npm (nextdist-tag). That cut consumed every pending changeset into.changeset/pre/, which is where these 15 corrections now sit. They still decide published text. In@changesets/cli3.0.3,assemble-release-plandropspre/changesets only while pre mode is notexit. So the GA cut that exits pre mode reads them, and the18.0.0CHANGELOG entry is built from the corrected sentences. The18.0.0-next.0CHANGELOG entries already carry the S6-falsified sentences. Those are released entries, and AGENTS.md routes their amendment to a dedicated docs-only PR, never a rider here. That amendment is the seat's carry.Why each in-place-fix condition holds. (1) Same defect class: every file is pending or prerelease text describing the single-brace dialect, or the retired
flow-filter-token-unknown, as still live. (2) Mechanical, with its shape pinned by #23126, which edited the identical "keeps resolving where it still lives" sentences in place. (3) No other claim holds these files: #22586 is closed, and the next Version Packages refresh regenerates frommain. (4) The same gate family, with no new verification surface..changeset/pre/)19939-flow-value-slot-template-dialect-refused.md:21filtervalue keeps the template dialect; a text slot (…) reads{{ }}holes and refuses a single-brace token.{{ }}holes and refuses a single-brace token.615c69604622161-lint-slice-10-one-line.mdos explainlist, theos verifylist and the runtime-gate sentence nameflow-filter-token-unknownflow-filter-token-unknown, is retired in this same release with its rule: a flow filter operand is a value slot, and a token there is refused asexpression-invalidbyvalidateStackExpressions(itsExprIssue), with the CEL envelope that replaces it."22161-lint-slice-14-one-line-fix.mdflowwrite bullet, "the two vocabularies a flow filter token resolves in;"19939-flow-crud-filter-operands.md:27, :43filter: { id: { dialect: 'cel', source: 'current_user.id' } }"19939-flow-callee-input-value-slots.md:32,-collection-value-slots.md:38,-http-slots.md:44,-notify-value-slots.md:44,-screen-value-slots.md:41,-value-slot-date-macros-refused.md:47filtervalue."19939-flow-value-slot-run-user-refused.md:22, :30, :44{$User.*}keeps resolving where the single-brace dialect still lives, a CRUDfiltervalue."; the$User.Emailrow: "anassignmentofuid: …, aget_recordonsys_userwithfilter: { id: '{uid}' }"; "and every other position keeps the single brace"get_recordonsys_userwithfilter: { id: { dialect: 'cel', source: 'current_user.id' } }"; "and the hint at a node's literal, or at any other position, names the value itself"22110-flow-text-slot-double-brace.md:24filtervalue keeps the single-brace dialect, and the value slots are CEL's.22301-verify-evaluate-condition-door.md:12{var}hole (the legacy template dialect).{var}hole is refused as the brace trap, and atemplateorflowenvelope is refused too (the legacy template dialect is deleted in this release).22502-flow-binding-variable-dollar-name-refused.md:33,22572-flow-binding-name-dollar-refused.md:41The measured lengths slice 10 states (at most 194; 222 to 441) still hold for its nine remaining ids: PR #22828's table puts
flow-filter-token-unknownat 267–278 → 181–192.Acceptance notes
0f84c9c(read only): a TS-AST walk of 494 code files, judged with the spec's own classifiers (classifyFlowFilterOperandString, the frozen token grammar). Insrc, 294 strings carry a single-brace token at positions [v18] retire the{var}template dialect in flow assignment slots: refuse at registration with per-spelling remedies (the C half of #11182 ruling D, on the v18 train) #19939 retired, in 28 files:fields.*94, CRUD filter operands 71 (none a query placeholder), notifytemplateData.*35, httpbody34, notifyrecipients19, notifyactionUrl19, loopcollection10,assignments.*6, subflowinput.*5, screendescription1. Most are paths (about 260), with 26 date macros and 7$Userpaths. There are none at the positions S7 itself retires: 0 braced conditions, 0connector_actioninputs. Tests add 14 more. The seat records this on its ledger.inspectors/flow-node-config.ts:690 and :706 (collection placeholders,refMode: 'template') andjson-schema-to-fields.ts:259 and :295. The simulator re-implements the deleted interpolator (previews/simulator/flow-simulator.ts:455–581). The pinned console imports none of the removed lint exports (git grep exit 1; control exit 0), so the Console Pin Gate is not tripped.packages/formula/src/stdlib-timestamp-text.test.tsstill describes the deletedresolveToken/interpolateStringin the present tense; its pinned bytes stay valid as a frozen record of what they measured. Carrier: none.builtinValueJudged/carriesInterpolationToken). Aget_recordwithlimit: '{x}'registers, and every run refuses it (probe). The seat files it as this family's tail card.Verification
All at head
05539d423(origin/main01a3421f4merged throughscripts/pm/os-regen-merge.sh, plus one fold commit), unless stated. Measured size: 53 files, +867 / −2451 = 3318 changed lines againstmain, generated files included.Merge and fold.
b1f1c0ffcmergesorigin/main01a3421f4with a merge commit, throughscripts/pm/os-regen-merge.sh: no conflict, nothing regenerated by hand. That merge brought in the18.0.0-next.0cut, which is what moved the 15 corrected files under.changeset/pre/.05539d423folds S7's canon fix: the order-88 rationale fragment inregistry.ts.template, plus one sentence in the new changeset. The fragment's projections (registry.ts, the upgrade-guide pages,spec-changes.json) are gitignored build output, so nothing committed derives from it. The full build regenerates them, andcheck:generated,check:upgrade-guideandstep18-rationale-merge.test.tsread them green.Build.
pnpm turbo run build --filter=!@objectstack/docs --concurrency=2, under the verify lock: 74 successful, 74 total. The full dist closure is what makescheck:dual-build-cjs-loadsmeasured (exit 0) rather than exit 3.Derived gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths, on05539d423derives 114 commands. Each ran once, with its exit code captured before any pipe.check:authorable-surface,check:generated,check:upgrade-guide,check:skill-examples,check:dual-build-cjs-loads,check:engine-double-contract,check:nul-bytes,check-adr-0087-registrationandcheck-changeset-no-major.check-empty-changeset --base origin/main, with 15::errorlines. This is by design, as described above.--ranreports: "114 derived famil(ies) accounted for — 114 run, 0 NOT-MEASURED (a DERIVED zero — all 114 recorded an exit code and none of them is 3)".Suites, on the merged head, under the lock:
@objectstack/service-automation: 185 files, 2449 tests passed.@objectstack/lint: 137 files, 6662 tests.@objectstack/verify: 38 files, 301 tests.@objectstack/spec:src/automation src/migrations src/data/context-tokens41 files, 1806 tests;--project repo scripts/step18-rationale-merge.test.ts1 file, 9 tests.turbo run typecheckon service-automation, lint, verify, formula and spec: 53 successful, 53 total.Downstream consumer sweep. Prefix direction:
...@objectstack/service-automation,...@objectstack/lintand...@objectstack/verifytogether give 55 packages. Each package ran alone with--maxWorkers=2, all exit 0:@objectstack/cli --project unit: 285 files, 4283 tests.The cli integration tier is declared to CI, since the diff touches no integration-tier file and no spawn entry. The four removed lint exports have zero hits in the tree (
git grepexit 1; control exit 0), and the full build compiles every package's declarations.NOT MEASURED locally: the other 43 packages of the closure. The reason is the shared container: a first, single turbo run of the sweep was SIGKILLed (exit 137) inside metadata-protocol while the gate battery ran beside it. A
git grepover those 43 finds noevaluateConditioncall, no deleted-symbol orflow-template-*reference, and no interpolator call. The only hits are service-messaging's own{{ }}renderer and prose in the dogfood ledger. CI runs them.Ablations, at
56811ae93. The three mutated files and five test files are byte-identical at05539d423(git diff --quietexit 0); the suites above re-ran green on the merged head, and the ablations were not re-run there. Each usedscripts/ablation-replace.mjswrap mode under the lock: anchor hits 1 then 0, a blob change proven, then restore with blob == HEAD andgit diff HEADempty, plus a shell trap that restores HEAD by absolute path with ahash-objectcheck.copyLiteralwalking every object (value-slot-map.ts): 6 red (the threeassignmentDate shapes, thecopyLiteralunit, thecreate_record/update_recordDate).template/flowenvelope refusal removed (engine.ts): 2 red.src.ESLint, a measured narrowing at
05539d423:eslint --no-inline-config --format jsonover the 47 changed, non-deleted files, read off eslint's own JSON. 27 files are linted with 0 errors and 0 warnings. The other 20 (16 md, 3 mdx, 1.template) are ignored by the config.eslint.config.mjsenables no type-aware linting (noparserOptions.project), so the diff cannot move the verdict on an untouched file.Generated by Claude Code