You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 0a88a80
Browse filesBrowse the repository at this point in the historyBrowse files
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>
0 commit comments