Commit 0a88a80
docs(spec): name the node slot in the structural-condition ruling and its ADR-0087 entry (#17761)
Part of #17493
Items ① and ② of the card. ⛔ Item ③ is a **ruling, not a refactor** — it
is measured and reported here, and deliberately **not implemented**; the
card stays open on it, and this PR therefore carries no closing keyword.
Nothing in this diff changes behaviour. The refusal already shipped
(#17322 at `registerFlow`, #17495 at `objectstack validate`); what
shipped with it was the notification, and this is that half.
- **Clause-②: no** — this PR puts no new key on any published payload.
---
## ① The on-site ruling that had become false
`packages/spec/src/automation/flow-node-expression-paths.ts` — the
docblock of `structuralConditionRefusal`.
It admitted a whitespace-only string on this ground, verbatim as it
stood:
> every **string**, including a whitespace-only one. What a non-empty
string *says* stays `validateExpression('predicate', …)`'s verdict, and
**a whitespace-only condition meaning `false` is consistent on both
sides and is ruled correct, not a defect.**
#15807 removed that ground (`FlowEdgeSchema.condition` composes
`EvaluatedExpressionInputSchema`), #17322 ruled on the disagreement that
left, and #17495 finished it at the third door. ⛔ Not deleted — it
records a real decision, so the edit says **what changed it**:
- The admission itself is **unchanged and still correct**. This function
answers the SHAPE question; a string is a well-shaped structural
condition. The blank is refused **beside** it, by the imported
evaluated-slot rule, answering `EVALUATED_EXPRESSION_SOURCE_REQUIRED`
and not `STRUCTURAL_CONDITION_SHAPE_REFUSAL`.
- A new section records **why item ③'s question is open**, without
answering it.
- The same stale ground sat three lines from the symbol, in
`flow-node-expression-paths.test.ts`'s own case comment ("not authored
on both sides and stays so"). Same defect class, same file face,
mechanical: the comment is corrected, both assertions untouched. ⭐
Declared as a bounded on-site fix beyond the dispatched file face.
## ② The ADR-0087 entry named only the edge slot
`packages/spec/src/migrations/entries/semantic/18.flow-edge-condition-evaluated-slot-source-required.ts`
— `surface` and `acceptanceCriteria`.
Both named only `edges[].condition`, so a consumer replaying the chain
was told to sweep the edge key alone and a deployment carrying a blank
`config.condition` on a node was **never told to look**. Both now name
both structural slots, plus the node key's own locator phrase (the one
the structural pass builds, e.g. `node 'gate' (start) condition`) beside
the edge's `flows.N.edges.N.condition`, and the sweep carries the
warning that deleting a `condition` from a `start` node **opens the
trigger gate** rather than preserving it.
- `id`, `replacement` and `reason` are untouched, per the card.
- No backticks in `surface` — measured, 0 (`build-upgrade-guide.ts`
renders it inside a code span).
- **No new entry.** This is one decision reaching its second slot, which
is what both follow-up changesets
(`blank-node-condition-refused-at-registration.md`,
`validate-refuses-blank-structural-condition.md`) already declared by
taking the `not-required (already-registered …)` disposition against
this very entry.
- `packages/spec/src/migrations/registry.ts` is **regenerated by the
repo's own generator**, `pnpm --filter @objectstack/spec
gen:migration-registry` — ⛔ never hand-edited.
`check:migration-registry` is green.
## ⛔ ③ Measured, reported, NOT implemented
`registerFlow` still **ACCEPTS** a whitespace-only string at both
sibling ledger `predicate` slots, on this head, against controls that
prove the probe reaches them:
| slot | valid CEL (positive control) | envelope with a blank source
(refusal control) | `' '` | `''` |
|:--|:--|:--|:--|:--|
| `config.conditions[].expression` | ACCEPTED | REFUSED —
`PREDICATE_SLOT_STRING_REFUSAL` at `config.conditions[0].expression` |
**ACCEPTED** | **ACCEPTED** |
| `screen.fields[].visibleWhen` | ACCEPTED | REFUSED —
`PREDICATE_SLOT_STRING_REFUSAL` at `config.fields[0].visibleWhen` |
**ACCEPTED** | **ACCEPTED** |
| `config.condition` (lit control — the slot #17322 DID narrow) |
ACCEPTED | REFUSED | **REFUSED** | **REFUSED** |
`evaluateCondition(' ')` and `evaluateCondition('')` both answer `false`
— the same silent dead branch. The current behaviour is pinned as
correct by #15572 at
`packages/services/service-automation/src/decision-predicate-envelope.test.ts`
(the `it` block at `:110`–`:114` on this head; the card cited
`:113`–`:117` from `aefbb07b2`, a 3-line drift, the pin itself intact).
⛔ Narrowing those slots re-judges that pin and moves a published
accept-set: **a ruling, not a refactor**. `packages/services/**` is
untouched by this diff — 0 files.
## 验收备注
- `packages/lint`'s `predicateSlotRefusal`-side prose and
`flow-node-expression-paths.test.ts:189` also rest on "consistent on
both sides" — but for the **ledger predicate slots** that statement is
still **true** (resolver skips the blank, evaluator answers `false`, and
neither has been rebound). Noted, not filed: nothing to correct until
item ③ is ruled on.
- `packages/spec/src/migrations/registry.ts` is a hot shared file: PR
#17638 and PR #17635 both regenerate it from different entry files. No
conflict at this head; `origin/main` merged at `5a77c75b29` and the
registry regenerated and re-verified after.
## Verification
Every run below is on the final head `5a77c75b29` (`origin/main` merged
at that commit), with the gate's own verdict line, never a bare exit
status read through a pipe.
- `pnpm --filter @objectstack/spec build && check:generated && typecheck
&& test` — VERDICT `command-exit 0`; **473 test files / 13436 tests
passed**.
- The one test-layer file this diff touches, re-run alone: `pnpm
--filter @objectstack/spec exec vitest run --maxWorkers=2
src/automation/flow-node-expression-paths.test.ts` — 1 file / **26 tests
passed**.
- `node scripts/pm/dispatch-gates.mjs --commands` derived **84** gate
families for this change set; **84 run, 82 green, 0 unrun** — reconciled
with `--ran`, exit codes captured before any pipe. The two non-green
exited **3 = PREREQUISITE NOT MET, i.e. NOT MEASURED and not a
finding**: `check:dual-build-cjs-loads` and `check:type-check-debt` both
refuse without a repo-wide build closure, and both are declared to CI,
which builds one.
- Item ②'s own gate, `check:adr-0087-registration` — exit 0, verdict:
"this PR adds no declared-breaking changeset (1 non-breaking
changeset(s) seen)". `check:migration-registry` exit 0, verdict:
"src/migrations/registry.ts is current (202 semantic, 167 retired-key,
178 retired-def)". `check:spec-changes` and `check:upgrade-guide` both
report their artifact up to date; `check:docs` reports 222 generated
files in sync.
- `eslint . --no-inline-config --format json` over the **whole repo**,
eslint's own population read from its JSON output: **6638 files, 0
errors, 0 warnings**. No narrowing claimed and none needed.
- `check:nul-bytes` exit 0 (8441 text files), plus a control-character
self-scan over every changed file: 0 hits.
Changeset: `.changeset/17493-node-door-refusal-residues.md`,
`@objectstack/spec: patch`, card-scoped filename in the tree's
prevailing spelling.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 305e7fc commit 0a88a80
5 files changed
Lines changed: 147 additions & 44 deletions
File tree
- .changeset
- packages/spec/src
- automation
- migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
Lines changed: 7 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
270 | | - | |
271 | | - | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
272 | 277 | | |
273 | 278 | | |
274 | 279 | | |
| |||
Lines changed: 41 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
421 | 421 | | |
422 | 422 | | |
423 | 423 | | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
428 | 448 | | |
429 | 449 | | |
430 | 450 | | |
| |||
451 | 471 | | |
452 | 472 | | |
453 | 473 | | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
454 | 491 | | |
455 | 492 | | |
456 | 493 | | |
| |||
Lines changed: 30 additions & 19 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
18 | 22 | | |
19 | 23 | | |
20 | 24 | | |
| |||
54 | 58 | | |
55 | 59 | | |
56 | 60 | | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
62 | 70 | | |
63 | | - | |
64 | | - | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
65 | 75 | | |
66 | | - | |
| 76 | + | |
| 77 | + | |
67 | 78 | | |
68 | 79 | | |
69 | 80 | | |
70 | 81 | | |
71 | 82 | | |
72 | | - | |
73 | | - | |
74 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
75 | 86 | | |
76 | 87 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7842 | 7842 | | |
7843 | 7843 | | |
7844 | 7844 | | |
7845 | | - | |
7846 | | - | |
7847 | | - | |
7848 | | - | |
7849 | | - | |
7850 | | - | |
7851 | | - | |
7852 | | - | |
| 7845 | + | |
| 7846 | + | |
| 7847 | + | |
| 7848 | + | |
| 7849 | + | |
| 7850 | + | |
| 7851 | + | |
| 7852 | + | |
| 7853 | + | |
| 7854 | + | |
| 7855 | + | |
| 7856 | + | |
7853 | 7857 | | |
7854 | 7858 | | |
7855 | 7859 | | |
| |||
7889 | 7893 | | |
7890 | 7894 | | |
7891 | 7895 | | |
7892 | | - | |
7893 | | - | |
7894 | | - | |
7895 | | - | |
7896 | | - | |
| 7896 | + | |
| 7897 | + | |
| 7898 | + | |
| 7899 | + | |
| 7900 | + | |
| 7901 | + | |
| 7902 | + | |
| 7903 | + | |
| 7904 | + | |
7897 | 7905 | | |
7898 | | - | |
7899 | | - | |
| 7906 | + | |
| 7907 | + | |
| 7908 | + | |
| 7909 | + | |
7900 | 7910 | | |
7901 | | - | |
| 7911 | + | |
| 7912 | + | |
7902 | 7913 | | |
7903 | 7914 | | |
7904 | 7915 | | |
7905 | 7916 | | |
7906 | 7917 | | |
7907 | | - | |
7908 | | - | |
7909 | | - | |
| 7918 | + | |
| 7919 | + | |
| 7920 | + | |
7910 | 7921 | | |
7911 | 7922 | | |
7912 | 7923 | | |
| |||
0 commit comments