Skip to content

fix(cli): the flow-region gate never passes a flow it could not judge, and the boot banner counts refused flows - #29243

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-l9edg2
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-l9edg2

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Fixes #29059

Clause-②: no
Metadata-protocol: none

domain:server#3 · os-steve · session_01ADHkmQBbn1k9FtQeh2nJSs

维护者速读

What changes

All five Wanted items of #29059.

  1. No silent pass on a parse failure (utils/flow-region-shape.ts). findFlowRegionRefusals used to return with no row when FlowSchema.safeParse refused a flow. It now pushes a VALIDATION_ERROR row at the flow's path, named by the flow's name (or its path when it has no string name), carrying the schema's issues. The door refuses on it like any other row. New unit test src/utils/flow-region-shape.test.ts forces the branch (own flows, nameless body, packages[] entry).
  2. The blindness pin is no longer vacuous (test/authoring-rule-command-parity.test.ts). Each OUTSIDE_THE_TABLE entry now names its gate, and the case first asserts that gate(stack) is non-empty and carries the marker, then that the registry run as os lint runs it reports nothing with the marker. The docblock now says it measures the registry, not the os lint command.
  3. packages[] subject tested end to end (test/validate-flow-region-shape.test.ts). A two-entry assembled artifact with the malformed flow in the second entry: os validate --json and os build --json both exit 1 with exactly one region row { path: 'packages[1].manifest.flows[0]', code: 'VALIDATION_ERROR', flow: 'rgn_ops_lead_time' }, and build writes no dist/.
  4. Stale prose: the parity file's test-budget header says "the ten rule cases" (nine CASES plus one OUTSIDE_THE_TABLE).
  5. Boot banner count (commands/serve.ts, utils/format.ts). collectAutomationSummary takes the declared flows' names and counts those for which the engine's getFlowRegistration(name).state is refused. That read already exists on the engine and on IAutomationService (optional, the metadata door uses it), and the record behind it is written by the plugin's kernel:ready cold-boot bind (recordFlowRefusal). So no new engine or plugin surface. The banner prints ⚠ Flows: N flow(s) declared, M refused at registration — … after the Flows count line, and alone when every declared flow was refused (the old early return on flowCount === 0 would have printed nothing). No line when nothing is refused. Whether boot continues is unchanged.

AutomationReadySummary gains refusedCount. It is not on any published entry of @objectstack/cli (exports is ., ./console, ./hook-body; index.ts re-exports only the ServeCommand default), and the banner is stderr text, so no JSON or published member changes.

Forced test edits: src/utils/format.boot-warning-classes.test.ts and src/utils/format.flow-contender-agreement.test.ts build AutomationReadySummary literals by hand; each gains refusedCount: 0 because the member is required.

Tests

Red first, by ablation (scripts/ablation-replace.mjs, each leg restored and proven by blob hash equal to HEAD and an empty git diff HEAD):

(1) parse failure: put the silent return back

On 03cc2f40, flow-region-shape.ts anchor if (!result.success) { replaced by if (result.success === false) { return; } if (false) {: src/utils/flow-region-shape.test.ts 3 failed / 3, each expected [] to deeply equal [ { path: 'flows[0]', … } ]. (A first attempt whose replacement contained the anchor was refused by the tool as a no-op and restored; it is not counted.)

(2) vacuous pin: a fixture typo

Fixture edge dy rewired d → y to x → y (the catch region becomes single-exit): with this PR's assertion, known gap case 1 failed, []: expected [] to not deeply equal []. Control leg, same typo with the new assertion deleted (the pin as on main): 1 passed. The old pin stayed green on a fixture nothing refuses.

(3) packages[] subject

const subjects = entries.length > 0 → false && … (walk the empty top level only): the assembled case failed, os validate exit +0, expected 1. Path spelling packages[N].manifest. → packages[N].: failed with path: "packages[1].flows[0]" against the expected packages[1].manifest.flows[0].

(5) boot banner count

serve.ts refusedCount++ → void 0: serve-automation-refused.test.ts 2 failed / 4 (refusedCount 0). On 48c3a64c, the all-refused branch's printRefused(); → void printRefused;: 1 failed / 4, expected [] to have a length of 1. (On 03cc2f40, the earlier line placement, gating the line on flowCount > 0 failed the same case.)

The test drives the REAL AutomationEngine (dist): registerFlow refuses a one-branch parallel, the test records it the way the host's sync does, and the collector and the real printServerReady read it.

Real boot, one-off measurement (not kept as a test): os serve on a two-flow config with one one-branch parallel, OS_DATABASE_URL=:memory:, printed

  ⚠ Flows:   2 flow(s) declared, 1 refused at registration — refused flows never run; each one's reason is under Boot diagnostics ('failed to register flow')
  Flows:   1 flow(s) 1 bound to triggers (record_change, schedule, time_relative, api)

with both [Automation] failed to register flow and [Automation] cold-boot flow bind: failed to register flow for bt_fan under Boot diagnostics, and boot continued to Press Ctrl+C to stop. (That run had the line before the count line; 48c3a64c moved it after.)

Verification

All on the final head fc24941128 (this branch merged with origin/main dedc350519; clean merge), after rebuilding @objectstack/cli and its dependency closure:

  • pnpm --filter @objectstack/cli typecheck (tsc plus check:test-typecheck): exit 0.
  • pnpm --filter @objectstack/cli exec vitest run --project unit: 374 files, 5252 tests passed.
  • test/authoring-rule-command-parity.test.ts (integration tier, edited here): 12 / 12 passed.
  • node scripts/pm/dispatch-gates.mjs --commands derived 63 commands for this change set; all 63 exit 0; --ran reconciliation: 63 run, 0 NOT-MEASURED, 0 UNRUN.
  • pnpm lint: exit 0.

On the pre-merge head 48c3a64c the same set was green too. Five gates first exited 3 (prerequisite not met: no full build, shallow clone) and were rerun green after turbo run build and an unshallow fetch: check-plugin-teardown-shape --self-test, check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity.

Dogfood battery: not owed (no ! in the title, Clause-②: no with no narrowing arm).

The 63 derived commands, each with its exit code
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-test-child-env :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-coverage :: exit 0
pnpm check:i18n-walk-parity :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:route-envelope :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Acceptance notes

  • Clause-② stays no. The parse-failure branch cannot be reached by an authored stack: every flow the walk reads was parsed by the stack schema's flows: z.array(FlowSchema) (and, under packages[], by AssembledPackageBodySchema, whose collections come from the same stack shape), and FlowSchema accepts its own output. All existing validate/build/parity cases and the CONTROL case stay green, so nothing that passes today is refused.
  • What the count does not see. It counts DECLARED flow names (the config, packages included). A runtime-authored sys_metadata flow the engine refuses is not declared, so it is not counted; its per-flow line under Boot diagnostics is unchanged. And the record is written by the kernel:ready bind; the boot pull's own refusals are deliberately not recorded (AGENTS.md "Startup registry reads"). A host with no protocol service runs no such bind, so its count reads 0 there while the warn still prints. Every os serve boot measured here has the bind.
  • Integration tier: test/authoring-rule-command-parity.test.ts is integration (it spawns the CLI) and was run locally in full because it is edited; the rest of the integration tier is declared to CI.

Generated by Claude Code

…, and the boot banner counts refused flows

- findFlowRegionRefusals: a FlowSchema parse failure is a located
  VALIDATION_ERROR row instead of a silent return.
- authoring-rule-command-parity: the OUTSIDE_THE_TABLE pin also asserts
  the doors' own gate refuses its fixture with the marker; docblock says
  it measures the registry; test-budget header counts ten rule cases.
- validate-flow-region-shape: an assembled-artifact packages[] fixture,
  asserting exit code, path and that build writes no dist/.
- os serve banner: declared flows the engine refused at registration are
  counted beside the declared count, from getFlowRegistration.

Claude-Session: https://claude.ai/code/session_01ADHkmQBbn1k9FtQeh2nJSs
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 45e9b2f14c91f76683296cd33bb1b477cbaef1bb.

⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os serve (command, 44 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 45e9b2f14c91f76683296cd33bb1b477cbaef1bb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8d42804642ca0314952a5aae364d3b9e8905228b — the merge of head fc2494112839c156ca8b947f1458a4e0e0ac506c into base 45e9b2f14c91f76683296cd33bb1b477cbaef1bb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8d42804642ca0314952a5aae364d3b9e8905228b && git checkout 8d42804642ca0314952a5aae364d3b9e8905228b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 45e9b2f14c91f76683296cd33bb1b477cbaef1bb fc2494112839c156ca8b947f1458a4e0e0ac506c && git checkout -B drift-repro 45e9b2f14c91f76683296cd33bb1b477cbaef1bb && git merge --no-ff fc2494112839c156ca8b947f1458a4e0e0ac506c

node scripts/docs-audit/affected-docs.mjs --json 45e9b2f14c91f76683296cd33bb1b477cbaef1bb

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 45e9b2f14c91f76683296cd33bb1b477cbaef1bb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

席位核对 · ACCEPT · PR #29243 · 2026-11-02T10:16Z

Head-sha: fc2494112839c156ca8b947f1458a4e0e0ac506c

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Fixes #29059,与认领 6572608485 一致;10 个文件,均在 cli 申报面内。
  2. 前提实测:席位读 diff:解析失败分支 push 一行再 return,名字缺失时用 path;拒载计数只读引擎已有的 getFlowRegistration(IAutomationService 上的可选读,元数据门已在用),按声明名去重,特性探测、try/catch 与旁边探针同式;全拒时 flowCount === 0 的早返回改为先打拒载行。dev 消融 (1)(2)(3)(5) 各转红并按哈希复原,(2) 另有对照腿证明旧钉在夹具笔误上仍绿。
  3. 门禁:dev 本地 cli typecheck 0、单测 374 文件 / 5252、parity 12/12、dispatch-gates 63/63 exit 0(--ran 0 NOT-MEASURED)、pnpm lint 0。CI 在跑,入队前席位读本 head 全部 check-runs。
  4. 接受的偏差:AutomationReadySummary 新增必填 refusedCount,两处手写字面量的测试被迫加 refusedCount: 0,已在正文点名;该类型不在 @objectstack/cli 任何已发布入口上。open_question(计数只看声明名)取 dev 建议,保持。
  5. 范围外去向:无。Metadata-protocol: none。
  6. 落地条款:Clause-②: no,无契约面(无 packages/spec/src/**,横幅是 stderr 文本),免契约复审;标题无 !,dogfood 不欠。semver:@objectstack/cli patch,与 changeset 一致。check-governed-merges --pr 29243:NOT governed。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 10:30
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 10:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 8e0f3db Nov 2, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-l9edg2 branch November 2, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant