Repository navigation
feat(spec)!: refuse the wait / connector_action loose config keys naming their sibling block; their D2 lifts leave the load path (F3 of #23543) - #23742
Conversation
…eir D2 lifts leave the load path (F3) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZyGVR3TdcTDLhuzyEbkBN
…ts and the shadowed-key delete Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZyGVR3TdcTDLhuzyEbkBN
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZyGVR3TdcTDLhuzyEbkBN
…d loose config location; docs: name the refusal Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZyGVR3TdcTDLhuzyEbkBN
…tire-wait-connector-config-lift
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 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 5f87b30caf58d0108de30ba747ee18f085a5972a && git checkout 5f87b30caf58d0108de30ba747ee18f085a5972a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3b46855fb97365902e1e81a6484cd41bd363e575 23bebd666bc6cc6617321a5a3090847f0760bdb9 && git checkout -B drift-repro 3b46855fb97365902e1e81a6484cd41bd363e575 && git merge --no-ff 23bebd666bc6cc6617321a5a3090847f0760bdb9
node scripts/docs-audit/affected-docs.mjs --json 3b46855fb97365902e1e81a6484cd41bd363e575
|
Contract reviewServed-tier: Family F3 of ruling 6182001882 (items 3 and 7) on card #23543, PR #23742, read at 2026-10-16T00:24Z. Inputs: the card body and its seventeen comments, the PR body and its 16-file list, the net diff against Gate verdicts on this head: 41 check-runs, 36 Governance: none of the 16 paths is in ① Derived judgmentsAccept-set changes (each narrows what an author may write):
② Semver level
③ Boundary flagsDev
Dev
Dev Carried forward from the F1 and F5-jsx records:
Report corrections, not flags: the dev report reads Premise re-read on the head: no Implemented-by: VERDICT: PASS |
席位核对 — ACCEPT · objectstack
|
Part of #23543
Clause-②: yes (narrowing)
This PR lands family F3 of ruling
6182001882(item 3) only. F2, F4 and F5-http stay on #23543, one PR each, in the ruled order. #23543 remains open.What
Ruling F3 A: the named loose keys under a
waitorconnector_actionnode'sconfigare refused with the prescription naming the sibling block; the lift deletes a loose key the block shadows at rehydration, so stored flows keep loading; both entries setretiredFromLoadPath.packages/spec/src/automation/flow-sibling-block-loose-keys.ts(not on the automation barrel, so no public export moves), declaresWAIT_EVENT_CONFIG_LOOSE_KEYSandCONNECTOR_CONFIG_LOOSE_KEYS. These are the tables the two lifts inconversions/registry.tsused to declare themselves, moved byte for byte. The registry now reads them from the leaf, so the keys refused at authoring and the keys lifted at rehydration are one set by construction.siblingBlockLooseConfigRefusalsruns in theFlowSchemasuperRefine'scollectFlowGraphswalk, besideconnectorActionConfigRefusals, so a node inside a region body is reached too. Each named loose key is onecustomissue atnodes.N.config.KEY: "Awaitnode'sconfig.durationwas removed in @objectstack/spec 18 (ADR-0087 D2) … Move it towaitEventConfig.timerDuration…", followed by the houseos migrate meta --from 16sentence.config.timeoutMsgets its own move: it goes towaitEventConfig.timerDurationas a string, becausewaitEventConfig.timeoutMsis itself a tombstone. The refusal applies only to these keys:eventType,timerDuration,duration,signalName,signalandtimeoutMsonwait;connectorId,actionIdandinputonconnector_action. Any otherconfigkey is judged as before. AtdefineStackthis surfaces asSTACK_SCHEMA_INVALID/ 422.null. None of these was ever read. Kept, the flow parse would now refuse them, and the stored row would stop loading. The connector completeness guard is unchanged: a node whose pair cannot complete is left untouched and refused, as before.flow-node-wait-event-config-liftandflow-node-connector-config-liftgetretiredFromLoadPath: trueandretiredAfter: '17.7.0', the stamp PR feat(spec)!: retire seven overdue ADR-0087 D2 conversions from the load path at 18; record why eight stay #23523, PR feat(spec)!: retire the datasource driver spelling mongo — refused naming mongodb, its D2 conversion leaves the load path (F1 of #23543) #23600 and PR feat(spec)!: retire the page kind jsx — refused naming html, its D2 conversion leaves the load path (F5-jsx of #23543) #23638 used. Each "stays on the load path — a recorded verdict" paragraph is rewritten as the retirement record. Both fixtures now show the deleted shadowed key, andexpectedNoticesgoes from 4 to 5 on each.18.flow-sibling-block-loose-config-keys-refused(semantic, step 18) carriesconversionIds: ['flow-node-wait-event-config-lift', 'flow-node-connector-config-lift'], both already members of step 17.conversions.test.tsmoves both entries to the retired list. The kept-without-ruling list now holds four entries:flow-node-http-callout-rename,page-component-visibility-to-visibleWhen,retry-policy-convergedandview-visibleOn-to-visibleWhen. The removal-version pin'sJOINgains one row per entry.Mechanism assumptions, measured
WAIT_EVENT_CONFIG_LIFTS(eventType;timerDuration/duration;signalName/signal;timeoutMs) andCONNECTOR_CONFIG_LIFTS(connectorId/actionId/input) inregistry.ts. They now live once, in the leaf, and both readers import it. The pinrefuses every named loose key and no otherasserts the six wait keys exactly. A non-lifted key (note) is not refused, and the same keys under another node type are not this rule's.registerFlowcanonicalizer (applyConversionsToFlow(…, { includeRetired: true }),engine.ts), stored-row rehydration, and the metadata save door's flow verdict (protocol.ts, full chain). So the delete lives in the replayed lift. Pinned inconversions.test.tsunderthe sibling-block lifts are retired (#23543 F3), for each node type:FlowSchemaas written.registerFlow,connector-nodes.test.tshas a new shadowed-inputcase: the node'sconfigcomes back{}and the action receives the block's input. The case inwait-node.test.tsthat reads the declared value asserts the deleted loose key the same way.wait-node.tsreadswaitEventConfigandconnector-nodes.tsreadsconnectorConfig. Neither has a loose-key read to delete. Each had a comment saying the shape is "rewritten at load", which now says the authoring parse refuses it and the rehydration seam rewrites a stored row.connector-action-config-required.test.tshad one case naming the lift. It now asserts three issues: the missing block, plus one at each loose key, each naming itsconnectorConfigkey.os migrate metaload-path suites. None has F3 as its subject. The climigrate-meta-load-conversions,migrate-meta-composed-load-conversionsandmigrate-meta-composed(load-path case), and the spec'sstack-authored-input, all driveretry-policy-converged(a flow-levelerrorHandling.retryDelayMs). PR feat(spec)!: retire the datasource driver spelling mongo — refused naming mongodb, its D2 conversion leaves the load path (F1 of #23543) #23600 re-pointed them to that entry. They stay unchanged, and they pass on this tree (cli unit tier, 3 files / 18 tests). F2 retires that entry, so F2 is the family that inherits the "no live subject left" question PR feat(spec)!: retire the datasource driver spelling mongo — refused naming mongodb, its D2 conversion leaves the load path (F1 of #23543) #23600 recorded.check:generatedread "All 14 generated artifacts are up to date" before and after themainmerge. No export,.describe()or authorable key moved: the refusal is a superRefine issue.Fixture triage (pin sweep:
type: 'wait'/'connector_action'with aconfigcarrying a named key, whole repo, single- and multi-line)validate-expressions.flow-cel-root.test.ts(two rows), lintlint-flow-patterns.test.ts(legacyWaitFlow, and the loop-body connector row, whoseactionkey becameactionId), and service-automationrun-summary.test.ts(three connector rows).conversions.test.tsnow replayincludeRetired(the stored-row seam). The two cases that pinned "the shadowed key is left in place" now pin its deletion. New: a connector describe and the retirement describe.connector-nodes.test.tsandwait-node.test.tsstill register the loose shape throughregisterFlow, which is the stored-row seam. Their comments now say so.lint-flow-patterns.test.ts'sconfig: { durationMs }wait row uses no lifted key.content/docs/automation/flows.mdx: theconnectorConfigandwaitEventConfigrows now say the same keys underconfigare refused.Changeset
.changeset/23543-f3-retire-wait-connector-config-lift.md:@objectstack/specminor;@objectstack/service-automationpatch (comment-only executor changes).Clause-②: yes (narrowing). It names each refused spelling and the block key it moves to, thetimeoutMsspecial case, and how stored rows keep loading (the shadowed key deleted at rehydration).registered flow-sibling-block-loose-config-keys-refused.Verification
Head
23bebd66mergesorigin/main3b46855f(it carries #23418'sregistry.ts.templatechange) throughscripts/pm/os-regen-merge.sh. There were no conflicts and the template equalsmain's. Afterpnpm install --frozen-lockfileand a spec rebuild,check:generatedread "All 14 generated artifacts are up to date". The branch's delta againstorigin/mainis exactly this PR's 16 files.23bebd66: specsrc/conversions,src/automation,src/migrations,src/sharedandsrc/stack*: 113 files / 3883 passed.cb5a4f9f:@objectstack/spec: fulltest, 679 files / 20770 passed (1 todo).typecheckexit 0.@objectstack/service-automation: fulltest, 186 files / 2487 passed.typecheckexit 0.@objectstack/lint: the two edited files, 2 / 305 passed.@objectstack/cli: unit tier, the three migrate-meta load-path suites, 3 / 18 passed. The full unit and integration tiers are declared to CI, since this diff touches no cli file.dispatch-gates.mjs --commandsderived 111 commands. Each was run one by one, with its exit code captured before any pipe.--ranreports "111 derived famil(ies) accounted for — 107 run, 4 NOT-MEASURED (3 DERIVED from a recorded exit 3, 1 claimed)".check-plugin-teardown-shape --self-test(shallow clone),check:skill-examples(client dist absent) andcheck:dual-build-cjs-loads(full build absent).check:dts-closureexited 1 because my local dependency closure was built withOS_SKIP_DTS=1, so no package except spec carries a.d.tshere. That is an environment state, not a finding, and CI builds the full set.check-adr-0087-registration --base origin/main,check-changeset-no-major,check-empty-changeset,check:nul-bytes.23bebd66; the report comment on spec(conversions): eight ADR-0087 D2 conversions stay on the 18 load path after #23487 — five families each need a ruling before they can retire #23543 carries that reading.scripts/ablation-replace.mjs). The walk's callfor (const refusal of siblingBlockLooseConfigRefusals(node))was replaced by an empty array.f69316ecce7b→95cf82982a02.conversions.test.ts,retired-key-removal-version.pin.test.tswith bothJOINrows, andconnector-action-config-required.test.ts). Direction: red.git diff HEADempty.940997a39a4e→9450deebd49e.git diff HEADempty.src, so neither ablation has a dist leg.Acceptance notes
waitandconnector_actionthrough the declared blocks, and the connector descriptor has not published aconfigschema since the lift landed. No objectui reader was re-measured in this PR.connector_actionnode with onlyconfig.connectorId/config.actionIdnow gets three issues: the missingconnectorConfigblock, plus one at each loose key. Each issue is true and located.Generated by Claude Code