Repository navigation
fix(spec,service-automation): the v18 stored preview lists page components on the retired API source and every refusal registerFlow makes after the parse - #23356
Conversation
…isterFlow refuses after the parse registerFlow's registration checks (undeclared plugin config keys, decision modes, expressions and template holes, the api trigger secret) now return their refusal with its sites instead of throwing. registerFlow throws the first refusal, byte-identical, and canonicalizeStoredFlow lists every site as a TODO under 'registerFlow', so the stored preview reads such a flow as refused rather than on protocol. A site a conversion TODO already names is not listed twice. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
… listing Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
…retired view-level API source The view-data-api-provider-retired detector now also walks every stored page's components and lists a component whose own props row refuses its data prop with the retirement's prescription (object-grid, object-map, object-gantt, object-tree), by page, component and position, with its fate. The D3 entry's scope names those components. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
…ing, declared where they are read The conversion TODO's conversionId may name 'registerFlow'; the contract's todos member says it carries registration's whole verdict; the CLI page's --stored table lists both new families; and the engine's TODO stamp is classified beside the conversion pass's own. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
…und to the retired API source Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
…ired API source and registerFlow's refusals Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
…ypes apart from the flow registration verdict Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
…ller that reads todos The save door, a package duplication and the sync's held-flow comparison read parsed or storable only, so they pay nothing for the stored preview's listing. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
…ored-preview-gaps
📓 Docs Drift CheckThis PR changes 4 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 147 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 bfb9b4451dd7af3fe7c4c9b9eda85bbe86fcdbce && git checkout bfb9b4451dd7af3fe7c4c9b9eda85bbe86fcdbce
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 228357ab69aaaaba6e306b7cce7bb3fdc757ccb7 ac768f1d54f1225d9cb6e64b581ca0fe8312774d && git checkout -B drift-repro 228357ab69aaaaba6e306b7cce7bb3fdc757ccb7 && git merge --no-ff ac768f1d54f1225d9cb6e64b581ca0fe8312774d
node scripts/docs-audit/affected-docs.mjs --json 228357ab69aaaaba6e306b7cce7bb3fdc757ccb7
|
Contract reviewServed-tier: Read: card #23317 with every comment (triage Check-runs on the head at 01:14Z: 39 runs, 31 success, 5 skipped, 0 failed, 4 ① Derived judgmentsEach accept-set and public-surface change the diff implies, named right or wrong.
② Semver levelThe changeset
③ Boundary flagsEvery deviation and every
Implemented-by: VERDICT: PASS |
|
Queue note ·
Generated by Claude Code |
Fixes #23317
Clause-②: yes (widening)
What this does
os migrate meta --storedlisted two families of stored rows as already on protocol while a v18 door refuses them. Both are now refused and listed, through the preview's existing TODO lines. Nothing is rewritten, on preview or on--apply, andstoredMigrationCleandoes not move. No door accepts or refuses anything it did not before.dataprop binds the retired view-level API source:object-grid,object-map,object-gantt,object-treeview-data-api-provider-retired(the existing D3 entry, its scope extended)registerFlowrefuses in a stored flow the parse accepts: a text slot's template hole, a decision'smode, a malformed node or edge predicate, a plugin node's undeclared config key, anapiflow with no secretregisterFlow(the refusing door: these sites have no step-18 entry of their own)Public surface: Clause ②, yes (widening)
The card's prediction was
Clause-②: no(the preview lists more, no door accepts more). That holds for every door. The diff also widens published outputs, the same class contract review record 6152721854 named for PR #23315, so this line isyes (widening), and the changeset isminorfor both packages:ConversionTodoNotice.conversionId(@objectstack/spec): may now nameregisterFlow, the refusing door, for a site no conversion and no D3 entry covers. Declared in its docblock.IAutomationService.canonicalizeStoredFlow(...).todos?(@objectstack/spec) andAutomationEngine.canonicalizeStoredFlow(...).todos(@objectstack/service-automation): now also carry each site flow registration refuses. Declared in the contract's docblock. No type changes.onTodosink ofapplyConversions(@objectstack/spec) now also receives a page component bound to the retired API source, under the existing D3 id.Nothing narrows. No export name changes:
check:api-surfaceis green and nothing was regenerated.Zone 2 assumptions, each measured
ComponentPropsMaprows whosedataisViewDataSchema:object-grid,object-map,object-gantt,object-tree.object-kanban,object-calendarandobject-timelinetake a row array underdataand refuse a provider object for its shape, never with this retirement's prescription.PageSchema.safeParseandgetMetadataTypeSchema('page')both accept the page, becausePageComponent.propertiesis an open record. The door that refuses the binding is the component-props gate (validateComponentPropsin@objectstack/lint). It is declaredsurfaces: CLI_ONLY, soos validate,os buildandos lintrefuse it in source, and the save door and the boot do not.surface/replacement/reason/acceptanceCriterianow name the four components and the component-props gate.ComponentPropsMaprow'sdatamember, the one the component-props gate parses. It is listed only when that row refuses the binding with the prescriptionViewDataSchemagives. No list of types is kept in step: a pin derives the four fromComponentPropsMapand holds the D3 entry's and the detector'ssurfacetext to them.canonicalizeStoredFlowwasregisterFlow's first half: convert, parse, cycle check, region validation. The four registration checks (validateNodeConfigKeys,validateDecisionModes,validateFlowExpressions,validateApiTriggerSecret) ran only insideregisterFlow, and each threw.688a39d77. Canonicalize passed, andregisterFlowrefused: a decisionmode: 'bogus', a malformed node predicate, a braced edge predicate, anapiflow with no secret, and a notifytitleon the single-brace dialect or with an unknown formatter. A builtin's undeclared key and spec(automation): the #21898 token carve-out (builtinValueJudged/carriesInterpolationToken) outlived every interpolation — a built-in config value carrying a{token}registers past its type, then fails every run #23260's value arm (get_recordlimit: '{x}') are refused insideFlowSchema.parse. SocanonicalizeStoredFlowthrows them, and the stored pass already reports the rowfailedwith the parse's words.configSchema), the trigger binding and its credential channel (resolveSecret), and the object-schema resolver. So the shared list is an instance method.registrationChecks()is the one ordered list.registerFlowiterates it and throws the first refusal, so the same flows are refused with byte-identical messages.canonicalizeStoredFlowruns the same list and lists every site. A check added to that list later is listed with no second change.todosis first read, once. So the save door, a package duplication and the sync's held-flow comparison (holdsFlowDefinitioninplugin.ts), which readparsed/storableonly, do no more work than before.{…}site is refused by both the spec detector (D3 id) andvalidateFlowExpressions. Registration sites carry stack paths spelled as the conversion walk spells them (flows[0].nodes[1].config.body.nodes[0].config.title), and a site a chain TODO already names is skipped. Ablation A4 below: without the skip, the existing v18 upgrade: stored flows whose get/update/delete_record filter operand is a {…} token are refused at boot with no conversion — every deployment's automations built on the old spelling stop running #23269 engine pin goes red.TODO registerFlow: SOURCE left as stored at PATH — No conversion rewrites it, and registerFlow refuses it: left as stored, the flow is not registered at boot and never runs. Flow NAME, WHERE: the check's message. HereSOURCE,PATH,NAMEandWHEREare placeholders.fromshows the source text, the key, or the declared value. For theapisecret, it shows only a blank string ornull, so no secret is printed.Where it lands, and why outside the claim's file surface
The claim's surface was taken whole:
refused-shapes.tsand its test, the D3 entry,engine.ts,stored-migration.ts(docblocks only) and one changeset. Outside it:packages/spec/src/conversions/types.tsandpackages/spec/src/contracts/automation-service.ts: docblocks only. They declare the widened domain and content above.packages/runtime/src/dispatcher-error-vocabulary.ts: oneforeign-vocabularyrow.check:dispatcher-error-vocabularyrefuses any unclassified site that stampsOS_METADATA_CONVERSION_TODO, and the engine now builds that TODO. The row sits beside theapply.tsrow and has the same reasoning. The file is read by the gate and by a test, and it is not in runtime'sdist.content/docs/deployment/cli.mdx: the--storedtable. Its row said each TODO names a step-18 entry and that the parse or the boot refuses the row, and both would have become false. It now lists both families, each with its fate.packages/services/service-automation/src/builtin/config-unknown-keys.test.ts: one pin called the privatevalidateNodeConfigKeysand expected a throw. It now reads the returned refusal.packages/metadata-protocol/src/protocol.stored-migration.test.ts: one preview pin for a page row.packages/metadata-protocol/src/protocol.ts(fenced for PR fix(metadata-protocol): an environment kernel that hydrates its own store writes a published body through to its registry, so the security catalog serves it at once #23345). The stored pass already readsflowResult.todos ?? [], so no edit there was needed.Pins
refused-shapes.test.ts, 14 new):pages[0].regions[0].components[0].properties.data, naming the page, the component and its position, with the fate and the prescription; the row is not rewritten;page:tabspanel, and one in a slot;objectandvalueproviders,object-kanban(a row-arraydata), a custom type with no props row, and the governeddataSourceelement binding;datarefuses the binding with the prescription;PageSchemaaccepts the page, and each row refuses the binding;metadata-protocol: the preview lists the page rowskippedwith its TODO, without the header. Anobjectprovider page counts ascanonical.--applywrites nothing.canonicalize-stored-flow.test.ts, 23 new):apiflow with no secret). Each is listed underregisterFlowat its site, in the check's words, andregisterFlowrefuses it with the same words;registerFlowrefuses it;registerFlow's order, andregisterFlowstill throws the first;registerFlowTODO;builtinValueJudged/carriesInterpolationToken) outlived every interpolation — a built-in config value carrying a{token}registers past its type, then fails every run #23260's value arm) throws fromcanonicalizeStoredFlowas fromregisterFlow;storableis the input reference;parsed/storable/conflictsruns no check, andtodosruns them once;registerFlowand not atcanonicalizeStoredFlow.Verification
At
HEADac768f1d5unless a line names another commit.origin/mainwas merged atac768f1d5: 5 commits, none underpackages/spec.service-automation'snotify-node.tschanged there, so that suite re-ran after the merge.@objectstack/service-automation^...,@objectstack/metadata-protocol^...and@objectstack/runtime^...(turbo, 30 of 30 tasks), then@objectstack/specagain; everyVERDICT command-exit 0.pnpm --filter @objectstack/spec check:generated: "All 14 generated artifacts are up to date". No tracked file moved.typecheck, exit 0 each:@objectstack/spec(test layer held),@objectstack/service-automation(after the merge),@objectstack/metadata-protocol,@objectstack/runtime.@objectstack/service-automation, the whole suite after the merge: 185 files, 2481 tests, exit 0.@objectstack/speclocalproject in four locked runs: 102 files / 2735 tests, 170 / 5918, 310 / 9524 (+1 todo), and 88 / 2245. That is 670 files, the whole project (729 test files minus the 59repoones). All passed, at706df2da0; no spec file changed after it.@objectstack/spectest:repo: 59 files, 1011 tests, exit 0, at706df2da0.@objectstack/metadata-protocol:protocol.stored-migration,protocol.flow-canonicalizerandprotocol.save-flow-canonicalization, 80 tests, exit 0. This is a declared narrowing: the package's code is unchanged (docblocks only), and CI runs the suite.@objectstack/runtime:error-envelope.conformance.test.ts(the test that imports the vocabulary file), 53 tests, exit 0. Also a narrowing: runtime's code is unchanged.scripts/ablation-replace.mjsin wrap mode under the lock. Every anchor went 1 to 0, and every restore was proven: blob equal toHEAD, emptygit diff HEAD. The spec and engine pins import their subject by relative path, so nodistleg was needed.object-kanban, the custom type and door agreement failed. This is the proof that the row is the judge.canonicalizeStoredFlow, re-run on the final lazy form: 13 failed, 21 passed. Every listing pin failed; the on-protocol controls, the parse-throw pin and the log pin stayed green.pnpm lint:eslint --no-inline-config --format jsonover the 13 changed files gave 13 entries. 11 were linted, with 0 errors and 0 rule warnings. eslint itself ignores the changeset andcli.mdx("File ignored").eslint.config.mjsenables no type-aware linting (noparserOptions.project), so this diff cannot move an untouched file's verdict.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 114 commands atac768f1d5, with no stale-tree warning. Each ran with its exit code recorded before any pipe.check:skill-examplesandcheck:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: an unbuiltclient-react, and 30 unbuilt packages).turbo run buildfilled their prerequisites (35 of 35 and 74 of 74 tasks, cached).check:dispatcher-error-vocabularycaught one real finding: the engine's TODO stamp was unclassified. The vocabulary row above fixes it.Serial note: #23316
#23316 (branch
claude/issue-23316-refused-flow-runtime-state, no PR yet) editsengine.ts,plugin.tsandcontracts/automation-service.ts.git merge-tree --write-tree HEAD origin/claude/issue-23316-refused-flow-runtime-stategave exit 0 and tree5069a5cc8(no conflict), at this head and that branch's2b84fe555.FlowRegistrationRefusalintoengine.ts, so this branch's internal types are namedRegistrationCheckRefusal/RegistrationCheckSite. None of the identifiers this diff adds appears on that branch.plugin.ts, the cold-boot bind,withdrawFlow, or whatregisterFlowdoes after its checks.mainand re-runs the automation suite and the contract gates. A joint typecheck of the merged tree was not measured here.Acceptance notes
surfaceReason("Gating rule held off the runtime door pending the crossing discipline … Crossing is its own rollout card"). The save door and the boot accept a page component whosedatacarriesprovider: 'api'withheaders. At objectuiorigin/main72594f29,resolveDataSourcestill builds anApiDataSourcefrom such a config (ObjectGantt.tsxroutes through it). So a stored page keeps sending the request as stored, headers included, until the row is rewritten. This PR lists those pages and does not change a door. It is reported as a finding.datasaysview.data.provider, because it isViewDataSchema's own text. This is wording only, and its carrier is the retirement's owner.todos, the stored pass runs registration's checks once per flow row. Nothing else pays for them.Generated by Claude Code