Skip to content

Commit 2d6402c

Browse files
fix(spec): name timeRelative.object in the time-relative arm of the flow type-mismatch upgrade entry (#30637)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuV5cRafodM2YQHxeDkdiWJM)_ domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P Fixes #30279 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:只改升级指南条目 `flow-record-read-and-decision-type-mismatch-refused` 的两段文字(`surface` 与 `reason` 中对应的一句),写明时间相对流程的条件按被扫描对象 `config.timeRelative.object` 做类型检查:开始节点不写 `objectName` 时按它,开始节点的 `objectName` 就是被扫描对象时按 `objectName`。 - 为什么改:发布门(PR #30276 之后)已经这样检查并拒收,条目原文只说“扫描开始节点的 objectName”,比实际拒收范围窄,`os migrate meta` 与升级指南会把作者引向不完整的说明。 - 风险与回滚:纯文字更正,条目 id、kind、replacement、acceptanceCriteria 与任何判决都不变;回滚即撤 PR。 - 先看:`packages/spec/src/migrations/entries/semantic/18.flow-record-read-and-decision-type-mismatch-refused.ts`(`surface` 末段与 `reason` 中 time-relative 一句);`packages/spec/src/migrations/migrations.test.ts` 末尾新增的断言。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/migrations/migrations.test.ts -t "names the object a time-relative flow"`,预期 1 个用例通过。 - 需要你做的:无,席位落地。 ## What changes The entry's `surface` and one `reason` sentence, written from the gate code (`packages/lint/src/validate-expressions.ts`, the flow walk: `objectName = startObject ?? sweepObject`, where `sweepObject` is `config.timeRelative.object` on a time-relative flow; the `run-fails` outcome is declared when `sweepsTypedObject: sweepObject !== undefined && sweepObject === objectName`). Every other field (id, kind, replacement, acceptanceCriteria) is byte-identical. **`surface`, the time-relative tail** - FROM: `…; and the same conditions, bare name included, of a time-relative flow (start config.timeRelative) sweeping the start node's objectName` - TO: `…; and the same conditions, bare name included, of a time-relative flow (start config.timeRelative) sweeping config.timeRelative.object, typed against that object when the start node names no objectName and against the start node's objectName when that is the swept object` **`reason`, the time-relative sentence** - FROM: `A time-relative sweep launches each run with the swept row as its record, the same total row of the same object, so its conditions get the same refusal where 18.3.0 warned.` - TO: `A time-relative sweep launches each run with a row of the object it sweeps (config.timeRelative.object) as its record, the same total row of one object, so its conditions are typed against that object when the start node names no objectName, and against the start node's objectName when that is the swept object, and get the same refusal: where 18.3.0 warned on a sweep of the start node's objectName, and where a start that names no objectName got no verdict at all.` **Test.** No test pinned the entry's text; a new top-level `describe` in `packages/spec/src/migrations/migrations.test.ts` asserts the surface matches `sweeping config.timeRelative.object`. **Changeset.** `.changeset/30279-time-relative-entry-surface.md`, `@objectstack/spec` `patch`, with both declaration lines. ## Verification All readings on head `bc0dd484` unless named. <details><summary>Build, tests, typecheck</summary> - `pnpm --filter @objectstack/spec build` (under the verify lock): VERDICT command-exit 0. - `pnpm --filter @objectstack/spec check:generated`: exit 0, "All 14 generated artifacts are up to date"; no artifact carries the entry text, nothing regenerated. - `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/migrations/`: 5 files, 198/198. - The whole `@objectstack/spec` `pnpm test`: NOT MEASURED locally, reason: the shared verify lock answered queue-timeout (exit 99) twice behind a sibling's dogfood run (held 1094 s at the second try). Declared narrowing: the migrations directory above is the suite that reads the entry; CI `Test Core` runs the whole package. - `pnpm --filter @objectstack/spec typecheck` (under the lock): VERDICT command-exit 0. - `eslint --no-inline-config` over the entry file and the test file: exit 0. </details> <details><summary>Ablation (scripts/ablation-replace.mjs; restored blob == HEAD, git diff HEAD empty)</summary> - Mutation: the surface's new three-line tail set back to the FROM text (`sweeping the start node's objectName`). On disk: FROM phrase count 0 → 1, `sweeping config.timeRelative.object` 1 → 0, blob `f0920514` → `01fd0df2`; `gen:migration-registry` rerun, FROM phrase present in the generated `registry.ts` (1). - Result: 1 red, `the surface names config.timeRelative.object as the swept object: AssertionError: expected 'automation.Flow of a record-triggered…' to match /sweeping config\.timeRelative\.object/` (Tests 1 failed, 157 skipped). - Restore: blob after restore == HEAD (`f0920514`), `git diff HEAD` empty; registry regenerated; `migrations.test.ts` 158/158. </details> <details><summary>Derived gates</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on `bc0dd484`: 80 families; all 80 run, exit codes captured per command before any pipe; 77 exit 0 on the first pass. Three answered exit 3 (PREREQUISITE NOT MET: an unbuilt `@objectstack/formula`, unbuilt workspace `dist/`, unbuilt `objectql` `dist/`) on the first pass: `check:doc-formula-expressions`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`; re-run after the closure build `check:type-check-debt` performed, each exit 0. - `--ran` reconciliation with coded exit lines: "80 derived famil(ies) accounted for — 80 run, 0 NOT-MEASURED (a DERIVED zero — all 80 recorded an exit code and none of them is 3)". - Named in the dispatch and inside the derived set: `pnpm check:adr-0087-registration` exit 0 ("this PR adds no declared-breaking changeset"), `pnpm check:doc-authoring` exit 0. </details> ## Acceptance notes - **The code agrees with the card.** The gate types the conditions against `config.timeRelative.object` when the start names no `objectName`, and declares the `run-fails` outcome only when `timeRelative.object` equals the typed object, so a start naming another object keeps the warning; the TO sentences say exactly that. - **A start that names another object.** Since the `flow-time-relative-object-mismatch` readiness rule landed, a time-relative start whose `objectName` is not `timeRelative.object` is refused by that rule; this entry's `reason` still says "Any other flow keeps a warning", which stays true of the expression verdict itself. Noted, not changed. - **No tracker id in a runtime string**: the entry's `surface` and `reason` carry none; the test comment carries none either. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P Co-authored-by: Claude <noreply@anthropic.com>
1 parent f3a52d3 commit 2d6402c

3 files changed

Lines changed: 33 additions & 3 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): the `flow-record-read-and-decision-type-mismatch-refused` upgrade-guide entry names the object a time-relative flow's conditions are typed against: the swept `config.timeRelative.object`, when the start node names no `objectName`, and the start node's `objectName` when that is the swept object (#30279)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Text only: the entry's `surface` and the matching sentence of its `reason`, which `os migrate meta` and the upgrade guide show, said the refusal covers a time-relative flow "sweeping the start node's objectName". The author-time gate also types such a flow against `config.timeRelative.object` when its start names no `objectName`, and refuses the same conditions there; the entry now says so. Its id, kind, replacement and acceptance criteria are unchanged, and no verdict moves.

‎packages/spec/src/migrations/entries/semantic/18.flow-record-read-and-decision-type-mismatch-refused.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ export const entry: SemanticMigration = {
1010
+ 'config.condition, edges[].condition or a decision branch, where FIELD is a text or boolean field of '
1111
+ 'the trigger object ordered or arithmetic\'d against a number (record.name > 5, title > 5 in a '
1212
+ 'decision branch, record.active > 0); and the same conditions, bare name included, of a time-relative '
13-
+ 'flow (start config.timeRelative) sweeping the start node\'s objectName',
13+
+ 'flow (start config.timeRelative) sweeping config.timeRelative.object, typed against that object when '
14+
+ 'the start node names no objectName and against the start node\'s objectName when that is the swept '
15+
+ 'object',
1416
replacement:
1517
'Compare like with like. size(record.name) > 5 for the text\'s length; a number field '
1618
+ '(record.score > 5) for a value; double(record.name) > 5 only when every value is numeric text, '
@@ -23,8 +25,12 @@ export const entry: SemanticMigration = {
2325
+ 'run fails at that condition while the record-change trigger isolates the write that fired it. The '
2426
+ 'publish gate refused the bare name in a start or edge condition already, but passed the record.FIELD '
2527
+ 'read and every decision branch with no verdict at all. It now refuses them with the same run-fails '
26-
+ 'wording. A time-relative sweep launches each run with the swept row as its record, the same total '
27-
+ 'row of the same object, so its conditions get the same refusal where 18.3.0 warned. A bare name a '
28+
+ 'wording. A time-relative sweep launches each run with a row of the object it sweeps '
29+
+ '(config.timeRelative.object) as its record, the same total row of one object, so its conditions are '
30+
+ 'typed against that object when the start node names no objectName, and against the start node\'s '
31+
+ 'objectName when that is the swept object, and get the same refusal: where 18.3.0 warned on a sweep of '
32+
+ 'the start node\'s objectName, and where a start that names no objectName got no verdict at all. '
33+
+ 'A bare name a '
2834
+ 'declared flow variable or node id holds is the variable, not the field (the run seeds those before '
2935
+ 'it flattens the record), so it is not typed as the field. Any other flow keeps a warning, now worded for the failed run instead '
3036
+ 'of a null. There is no mechanical rewrite: whether the author meant the length, a number stored as '

‎packages/spec/src/migrations/migrations.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,3 +802,17 @@ describe('per-release section (ADR-0087 D4, package-version resolution)', () =>
802802
expect(Object.keys(release.added[0])).not.toContain('since');
803803
});
804804
});
805+
806+
describe('flow-record-read-and-decision-type-mismatch-refused names the object a time-relative flow is typed against', () => {
807+
// The author-time gate types a time-relative flow's conditions against
808+
// config.timeRelative.object when the start node names no objectName
809+
// (packages/lint validate-expressions.ts, the flow walk), so the entry's
810+
// surface must name that key, not only the start node's objectName.
811+
const entry = () =>
812+
MIGRATIONS_BY_MAJOR[18]!.semantic.find((s) => s.id === 'flow-record-read-and-decision-type-mismatch-refused');
813+
814+
it('the surface names config.timeRelative.object as the swept object', () => {
815+
expect(entry()).toBeDefined();
816+
expect(entry()!.surface).toMatch(/sweeping config\.timeRelative\.object/);
817+
});
818+
});

0 commit comments

Comments
 (0)