Repository navigation
feat(spec,lint): resolve action → flow and widget → dataset references against the release artifact - #25617
Conversation
…efineStack artifactFlows Claude-Session: https://claude.ai/code/session_011SU6BLnw3XLU8nKfnWMYVG Co-authored-by: Claude <noreply@anthropic.com>
… the artifact's packages[] Claude-Session: https://claude.ai/code/session_011SU6BLnw3XLU8nKfnWMYVG Co-authored-by: Claude <noreply@anthropic.com>
…dataset references Claude-Session: https://claude.ai/code/session_011SU6BLnw3XLU8nKfnWMYVG Co-authored-by: Claude <noreply@anthropic.com>
…tifact-flow-dataset-refs
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 144 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 56d41b252e302762e1cb39ec610fd5e779e8036f && git checkout 56d41b252e302762e1cb39ec610fd5e779e8036f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 10b8250ad5af331beed721694dca6e43df6d16b8 45b786764e97c05431a870693d2345b247833163 && git checkout -B drift-repro 10b8250ad5af331beed721694dca6e43df6d16b8 && git merge --no-ff 45b786764e97c05431a870693d2345b247833163
node scripts/docs-audit/affected-docs.mjs --json 10b8250ad5af331beed721694dca6e43df6d16b8
|
席位核对ACCEPT · #25514 ·
Generated by Claude Code |
Contract reviewServed-tier: Inputs, and nothing else: card #25514 (body and every comment; ruling of record 6377677280; dev report 6382768493), PR #25617 (body, file list, net diff against ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each named right or wrong:
No derived judgment in the diff is wrong. ② Semver level
③ Boundary flagsDev report 6382768493 — one
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #25514
Clause-②: yes (widening)
Metadata-protocol: added
Ruling-ref: 6377677280
What
One release artifact carries several packages (ADR-0130 D4). Two per-package checks judged a reference against one package, so they refused a target that a sibling package of the same artifact provides:
packages/spec, defect 1.validateCrossReferencesbuilt its flow scope from the stack's ownflowsalone. Atype: 'flow'action whosetargetnames a sibling package's flow was refused with "which is not defined in flows." This applied in all three positions: top-levelactions[],objects[].actions[], and inline page actions.packages/lint, defect 2.widget-dataset-unknown(validateWidgetBindings) built itsdatasetsmap fromstack.datasetsalone. It ignored thepackages[]resolution context that the CLI's per-package pass already supplies.Following ruling 6377677280 (option A), both reference classes now resolve against the artifact. A flow or dataset that no package of the artifact defines stays refused, exactly as before. Scope is same-artifact only. Cross-artifact resolution is #25403's ADR and is not touched here.
Contract (public-surface widening)
DefineStackOptions.artifactFlows?: readonly string[](packages/spec/src/stack.zod.ts). It lists the flow names the other packages of the artifact provide. A flow action's target resolves against ownflows∪ these names. It mirrorsartifactObjects.flowNames.size === 0), unchanged.defineStackreturns. The record is a non-enumerableSymbol.for('objectstack.stack.artifact-flow-claims')property beside the provenance mark, so it is not a schema key and not indist/objectstack.json.composeStacksstep 3b (collectArtifactFlowClaimErrors) refuses the composition when no input defines a recorded target. The refusal uses the sameSTACK_CROSS_REFERENCE_INVALID/422 envelope and the byte-identical per-finding message. A flow is "defined by an input" when it is in that input's top-levelflowsor, for a nested multi-package artifact, in apackages[].manifest.flowsbody.artifactObjectsre-run. Re-running the flow rule over every input at composition would also newly refuse a dangling target in an input built withstrict: false. That is a narrowing this card does not declare. The record checks exactly the promise the option made. An input that did not opt in cannot newly fail at composition, strict or not. That case is pinned.artifactObjects: a one-package composition, or a stack that is never composed, leaves the claim unverified.widget-dataset-unknownreadspackagesOf(stack)(the existingpackages/lintreader) and adds each body'sdatasetsto the map, the wayvalidate-object-references.tswidens rung ① withartifactProvidedObjectNames. This is resolution context only. Dataset-level checks (measure-aggregate-incoherent) still walkstack.datasetsalone, and the stack's own declaration wins a shared name.packages/cliis untouched:packageBodyAsStackalready passespackages[].packages/metadata-protocol/src/runtime-authoring-gate.ts): neither widened nor broken. Its judged stack never carriespackages(nopackageskey inruntime-gate.ts/runtime-authoring-gate.ts), sopackagesOfanswers[]and the rule behaves exactly as before. Its tests are green (below).Booted measurement (ruling note 2)
I boot-tested a temporary (uncommitted) extension of
examples/app-multi-package:orders(type: module) ships the flowcrm_order_stamp(start →create_recordoncrm_order→ end) and the datasetcrm_order_metrics.core(type: app) has its own flowcrm_core_noop, so the flow check runs at all. It hascrm_account.actions[stamp_orders] → type 'flow', target 'crm_order_stamp', a dashboard widgetorder_totalsbound tocrm_order_metrics, anddefineStack(..., { artifactFlows: ['crm_order_stamp'] }). Itsrequiresis['automation'].So the app package's action starts the module package's flow, and the app package's widget's dataset (owned by the module) returns its rows. The server was stopped by its recorded PID, and the probe edits were restored (
git statusclean).Tests
packages/spec/src/stack-artifact-flow-crossref.test.ts(9): both directions per class across all three positions. A sibling flow is accepted atdefineStackand atcomposeStacks. An unlisted flow is refused atdefineStack. A claimed flow that no input defines is refused atcomposeStackswith the per-stack message, envelope and header. A nested multi-package input resolves. Fences: no own flows means no check, a non-optedstrict: falseinput cannot newly fail, and no new enumerable key is added.packages/lint/src/validate-widget-bindings.artifact-datasets.test.ts(6): a sibling dataset is accepted, a dataset no package declares is refused, the single-package answer is unchanged, a resolved sibling dataset's measures are judged, a sibling's dataset-level findings are not judged on this package's pass, and the rule table reaches the per-package pass.ablation-replace.mjs, blob == HEAD,git diff HEADempty):artifactFlows?.has(target)→ never matches: 5/9 red....collectArtifactFlowClaimErrors(stacks),deleted: 1/9 red (the compose-time refusal).ablation-dist-preflightmarker present in 4 dist files): 3/6 red. The real doorobjectstack validateon the probe exits 1 with HotCRM's exact defect-2 line,package 'com.example.multi.core' — dashboard "crm_overview" › widget "order_totals": dataset "crm_order_metrics" does not resolve to a declared dataset.(rulewidget-dataset-unknown). After the restore and rebuild (marker absent from all 20 dist files), the door exits 0 and 6/6 pass.5cf84d58(pre-merge): specpnpm test715 files / 21696 passed,pnpm typecheck0; lintpnpm test159 files / 7243 passed,pnpm typecheck0. The CLI unit tier (vitest run --project unit) ran 327/327 files: 325 on the first run, pluspublished-subpath-console/published-subpath-hook-body, which neededpackages/cliitself built and then passed, 29/29. metadata-protocolprotocol.dashboard-dataset-publish-gate,protocol.runtime-gate-stored-universeandprotocol-publish-drafts-closureran 3 files / 24 passed. The CLI integration tier is left to CI; the diff touches no integration-tier file.origin/main(e649c2d91e, the Version Packages commit) at head45b786764e: spec build pluscheck:generatedshow all 14 generated artifacts up to date. Spec flow and artifact cross-ref tests: 39 passed. Lint build plus both widget-binding test files: 184 passed.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 113 commands, with an identical derivation on45b786764e. All 113 exit 0. Four first returned PREREQUISITE NOT MET (exit 3) or hit my own runner timeout, and passed once the prerequisite was met:check-plugin-teardown-shape --self-test: shallow clone, fixed withgit fetch --unshallow.spec check:skill-examplesandcheck:dual-build-cjs-loads: unbuilt siblingdist/, fixed by building those packages.check:type-check-debt: my 420 s cap; rerun uncapped, it exits 0.--ranreconciliation: 113 derived, 113 run, 0 NOT-MEASURED (a derived zero: every line carries its exit code). Re-run on45b786764e: the--base origin/mainchangeset gates,check:type-check-coverage,check:nul-bytes,check:doc-authoring,check:issue-citationsand speccheck:api-surfaceall exit 0. Local-only families also exit 0:check:dev-prereqsandcheck:skill-frame-freshness. Dogfood battery: n/a (no!in the title, no(narrowing)arm).Acceptance notes
top_productsreturning 4 rows) need a released@objectstack/*carrying this. They are measured downstream, not here. HotCRM must addartifactFlowsto its app package'sdefineStackcall. The dataset half needs no HotCRM change.content/docs/getting-started/examples.mdxgains one bullet beside the existingartifactObjectsparagraph.Generated by Claude Code