docs(spec): name the node slot in the structural-condition ruling and its ADR-0087 entry - #17761
Conversation
… its ADR-0087 entry The blank structural condition is refused at all three doors since #17322 and #17495; two places in `packages/spec` still described the world before that. `structuralConditionRefusal`'s docblock admitted a whitespace-only string on #15662's ground that it "is consistent on both sides and is ruled correct, not a defect". #15807 removed that ground at the edge door and #17322 ruled on the disagreement. The admission itself is unchanged and still correct — this function answers the SHAPE question and the blank is refused beside it by the imported evaluated-slot rule — so what changes is the recorded reason, plus a section recording, without answering, the open question at the sibling ledger `predicate` slots. The ADR-0087 D3 entry `flow-edge-condition-evaluated-slot-source-required` named only `edges[].condition` in its `surface` and `acceptanceCriteria`, so a consumer replaying the chain was told to sweep the edge key alone and a deployment carrying a blank `config.condition` was never told to look. Both fields now name both structural slots, with the node key's own locator phrase. `registry.ts` is regenerated by `pnpm --filter @objectstack/spec gen:migration-registry`. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
… its assertions The same stale ground as the docblock, three lines from the symbol: the case admitted a blank structural condition as "not authored on both sides", which #15807 and #17322 ended. The two assertions are unchanged and still correct — they pin that this function does NOT carry the blank rule — and the comment now says so instead of restating a ruling that no longer holds. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
📓 Docs Drift CheckThis PR changes 1 package(s): 14 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 — 136 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 70e3731293fd1ce6db29faee0cf157a329d95818 && git checkout 70e3731293fd1ce6db29faee0cf157a329d95818
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ec739e67192ce686f956cd490b7bc2b9d0d64cac 5a77c75b29637540fcb3acdc4a2078049767659d && git checkout -B drift-repro ec739e67192ce686f956cd490b7bc2b9d0d64cac && git merge --no-ff 5a77c75b29637540fcb3acdc4a2078049767659d
node scripts/docs-audit/affected-docs.mjs --json ec739e67192ce686f956cd490b7bc2b9d0d64cac
|
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 atobjectstack validate); what shipped with it was the notification, and this is that half.① The on-site ruling that had become false
packages/spec/src/automation/flow-node-expression-paths.ts— the docblock ofstructuralConditionRefusal.It admitted a whitespace-only string on this ground, verbatim as it stood:
#15807 removed that ground (
FlowEdgeSchema.conditioncomposesEvaluatedExpressionInputSchema), #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:EVALUATED_EXPRESSION_SOURCE_REQUIREDand notSTRUCTURAL_CONDITION_SHAPE_REFUSAL.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—surfaceandacceptanceCriteria.Both named only
edges[].condition, so a consumer replaying the chain was told to sweep the edge key alone and a deployment carrying a blankconfig.conditionon 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'sflows.N.edges.N.condition, and the sweep carries the warning that deleting aconditionfrom astartnode opens the trigger gate rather than preserving it.id,replacementandreasonare untouched, per the card.surface— measured, 0 (build-upgrade-guide.tsrenders it inside a code span).blank-node-condition-refused-at-registration.md,validate-refuses-blank-structural-condition.md) already declared by taking thenot-required (already-registered …)disposition against this very entry.packages/spec/src/migrations/registry.tsis regenerated by the repo's own generator,pnpm --filter @objectstack/spec gen:migration-registry— ⛔ never hand-edited.check:migration-registryis green.⛔ ③ Measured, reported, NOT implemented
registerFlowstill ACCEPTS a whitespace-only string at both sibling ledgerpredicateslots, on this head, against controls that prove the probe reaches them:' '''config.conditions[].expressionPREDICATE_SLOT_STRING_REFUSALatconfig.conditions[0].expressionscreen.fields[].visibleWhenPREDICATE_SLOT_STRING_REFUSALatconfig.fields[0].visibleWhenconfig.condition(lit control — the slot #17322 DID narrow)evaluateCondition(' ')andevaluateCondition('')both answerfalse— the same silent dead branch. The current behaviour is pinned as correct by #15572 atpackages/services/service-automation/src/decision-predicate-envelope.test.ts(theitblock at:110–:114on this head; the card cited:113–:117fromaefbb07b2, 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'spredicateSlotRefusal-side prose andflow-node-expression-paths.test.ts:189also rest on "consistent on both sides" — but for the ledger predicate slots that statement is still true (resolver skips the blank, evaluator answersfalse, and neither has been rebound). Noted, not filed: nothing to correct until item ③ is ruled on.packages/spec/src/migrations/registry.tsis a hot shared file: PR feat(spec)!: retire thescheduledcache-warmup strategy — the cron it selected left in this same major (ADR-0049) #17638 and PR feat(spec): refuse a duration key whose JSDoc names a unit its describe does not #17635 both regenerate it from different entry files. No conflict at this head;origin/mainmerged at5a77c75b29and the registry regenerated and re-verified after.Verification
Every run below is on the final head
5a77c75b29(origin/mainmerged 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— VERDICTcommand-exit 0; 473 test files / 13436 tests passed.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 --commandsderived 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-loadsandcheck:type-check-debtboth refuse without a repo-wide build closure, and both are declared to CI, which builds one.check:adr-0087-registration— exit 0, verdict: "this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)".check:migration-registryexit 0, verdict: "src/migrations/registry.ts is current (202 semantic, 167 retired-key, 178 retired-def)".check:spec-changesandcheck:upgrade-guideboth report their artifact up to date;check:docsreports 222 generated files in sync.eslint . --no-inline-config --format jsonover 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-bytesexit 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