Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/28893-flow-region-shape-at-validate-build.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
'@objectstack/cli': patch
---
Expand All @@ -9,6 +9,6 @@

**BREAKING** accept-set narrowing at `os validate` and `os build` (`@objectstack/cli`): a stack carrying a flow whose `loop` body, `parallel` branches or `try_catch` region is not single-entry/single-exit or is cyclic, at any depth, or whose `parallel` has fewer than two branches, now fails validation and the build instead of passing and emitting an artifact.

Both commands now call `findFlowRegionRefusals`, which hands each flow (each `packages[]` entry's flows, or the stack's own) to the automation engine's own registration verdict: `FlowSchema`, then the spec's `validateControlFlow`. A refused flow prints `Flow '<name>' would be refused at registration: <where>: <the engine's message>` (`VALIDATION_ERROR`, exit 1), and the build writes nothing to `dist/`. Before, both passed it, and the engine refused it at boot with an `[Automation] failed to register flow` warning while the server ran without the flow. What the engine registers is unchanged. The fix is the engine's own sentence, e.g. add one node every exit of the region leads to.
Both commands now hand each flow to the automation engine's own registration verdict: `FlowSchema`, then the spec's `validateControlFlow`. A refused flow is reported by the author-time rule `flow-region-shape-invalid` as `Flow '<name>' would be refused at registration: <where>: <the engine's message>` (exit 1), and the build writes nothing to `dist/`; `os lint` reports it too. Before, both passed it, and the engine refused it at boot with an `[Automation] failed to register flow` warning while the server ran without the flow. What the engine registers is unchanged. The fix is the engine's own sentence, e.g. add one node every exit of the region leads to.

<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: `os validate` and `os build` now run the check `registerFlow` already runs, with the same function (`validateControlFlow` from `@objectstack/spec/automation`) and its message. Every flow they now refuse was already refused at registration and never ran, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone; the remedy is the engine's own sentence (close the region with one exit node, or give the parallel a second branch). -->
20 changes: 20 additions & 0 deletions .changeset/29049-flow-region-shape-lint-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@objectstack/lint': patch
'@objectstack/cli': patch
---

feat(lint): `os lint` refuses a flow whose structured region the automation engine refuses to register (`flow-region-shape-invalid`, ADR-0031)

Clause-②: no (narrowing)
Metadata-protocol: none

**BREAKING** accept-set narrowing at `os lint` (`@objectstack/cli`, through the `@objectstack/lint` rule table): a stack carrying a flow whose `loop` body, `parallel` branches or `try_catch` region is not single-entry/single-exit or is cyclic, at any depth, or whose `parallel` has fewer than two branches, now fails `os lint` (exit 1) instead of passing. `os validate` and `os build` already refused it; their verdicts are unchanged.

- **One rule for all three commands.** The check is now the author-time rule `flow-region-shape-invalid` (`validateFlowRegionShape` and `FLOW_REGION_SHAPE_INVALID`, exported from `@objectstack/lint`). It calls the automation engine's own registration verdict, `FlowSchema` then the spec's `validateControlFlow`, and its message is the engine's refusal behind the flow's name. `os explain flow-region-shape-invalid` prints the full reasoning.
- **What changes at `os validate` and `os build`.** They refuse the same flows as before, now from the rule table, so the refusal prints under `Author-time rules failed` and appears once. Under `--json` it is a rule finding (`rule`, `where`, `path`, `message`, `hint`), in `errors` for `os validate` and in `issues` for `os build`. A flow in a multi-package artifact is located at its place in the artifact's flows (`flows[N]`), with the flow's name in `where`.
- **A flow the flow schema refuses** is reported by the same rule as one that could not be judged, with the schema's issues, never passed. `os validate` and `os build` refuse such a flow at the schema step already; `os lint`, which does not parse, now reports it here.
- **Not judged:** a cycle in a flow's top-level graph, outside any region.

**The remedy.** Fix the flow the way the engine's sentence says: add one node every exit of the region leads to, remove the edge that loops back inside the region, or give the `parallel` a second branch.

<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped: `os lint` now runs the check `registerFlow`, `os validate` and `os build` already run, with the same function (`validateControlFlow` from `@objectstack/spec/automation`) and its message. Every flow it now refuses was already refused by `os validate`, `os build` and at registration, and never ran, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. -->
2 changes: 1 addition & 1 deletion .changeset/29059-flow-region-gate-hardening.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
'@objectstack/cli': patch
---
Expand All @@ -7,5 +7,5 @@
Clause-②: no
Metadata-protocol: none

- **No silent pass.** The flow-region check `os validate` and `os build` run (the automation engine's registration verdict, ADR-0031) parses each flow with `FlowSchema` before it judges the regions. When that parse failed it used to skip the flow with no row, so the flow passed unjudged. It now refuses it with a `VALIDATION_ERROR` row at the flow's `path`, naming the flow and the schema's issues. No authored stack reaches this branch today: the stack schema has already parsed every flow the check reads, and `FlowSchema` accepts its own output. Nothing refused or accepted today changes.
- **No silent pass.** The flow-region check `os validate` and `os build` run (the automation engine's registration verdict, ADR-0031) parses each flow with `FlowSchema` before it judges the regions. When that parse failed it used to skip the flow with no row, so the flow passed unjudged. It now refuses it with a `flow-region-shape-invalid` finding at the flow's `path`, naming the flow and the schema's issues. No authored stack reaches this branch today: the stack schema has already parsed every flow the check reads, and `FlowSchema` accepts its own output. Nothing refused or accepted today changes.
- **Refused flows in the boot banner.** When the automation engine refuses a declared flow at registration, boot still continues and the per-flow reason is still under Boot diagnostics. The `os serve` / `os dev` banner now also prints one line beside the declared count, for example `⚠ Flows: 2 flow(s) declared, 1 refused at registration`, read from the engine's own record (`getFlowRegistration`). It prints even when every declared flow was refused and nothing registered. A boot with nothing refused prints no such line.
2 changes: 1 addition & 1 deletion content/docs/deployment/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ os compile --json # JSON output for CI pipelines
→ Normalizing stack definition...
→ Lowering inline handlers...
→ Validating protocol compliance...
→ Running author-time rules (61)...
→ Running author-time rules (62)...
→ Checking that every required capability has a provider installable in this edition...
→ Collecting package docs (ADR-0046)... 0 collected
→ Writing artifact...
Expand Down
2 changes: 1 addition & 1 deletion content/docs/deployment/validating-metadata.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ A clean run walks the registry and reports timing:
Config: /path/to/support-desk/objectstack.config.ts
Load time: 21ms
→ Validating against ObjectStack Protocol...
→ Running author-time rules (61)...
→ Running author-time rules (62)...
→ Checking that every required capability has a provider installable in this edition...
→ Checking package docs (ADR-0046)...

Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/build-with-claude-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ visible: 'status != "resolved"'
◆ Validate
────────────────────────────────────────
→ Validating against ObjectStack Protocol...
→ Running author-time rules (61)...
→ Running author-time rules (62)...

✗ Author-time rules failed (1 issue)
• stack · action 'resolve_ticket' visible: bare reference `status` — a
Expand Down
2 changes: 1 addition & 1 deletion content/docs/ui/react-pages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ objectstack validate
────────────────────────────────────────
→ Loading configuration...
→ Validating against ObjectStack Protocol...
→ Running author-time rules (61)...
→ Running author-time rules (62)...
→ Checking that every required capability has a provider installable in this edition...
→ Checking package docs (ADR-0046)...

Expand Down
28 changes: 3 additions & 25 deletions packages/cli/src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,6 @@ import { findOverrideDeclarationRefusals } from '../utils/override-declarations.
// `os validate` step 3a-iv runs, over the save and install doors' own judge
// (`judgeExtensionFieldSuffix`).
import { findExtensionFieldSuffixRefusals } from '../utils/extension-field-suffix.js';
// [#28893] The ADR-0031 region-shape check — the walk `os validate` step 3a-vi
// runs, over the automation engine's own registration verdict.
import { findFlowRegionRefusals } from '../utils/flow-region-shape.js';

export default class Compile extends Command {
static override description = 'Compile ObjectStack configuration to JSON artifact';
Expand Down Expand Up @@ -840,28 +837,9 @@ export default class Compile extends Command {
this.exit(1);
}

// 3b-iii-ter. [#28893] Flow region shape (ADR-0031), at build time — the
// SAME call `os validate` makes at its step 3a-vi: the engine's
// registration verdict (`FlowSchema`, then the spec's
// `validateControlFlow`). Before this the build emitted a flow the
// engine then refused at boot, logging a warning and running without
// it; now the door that ships refuses it and writes no artifact. Same
// two faces as the gates above.
const regionRefusals = findFlowRegionRefusals(result.data as Record<string, unknown>);
if (regionRefusals.length > 0) {
if (flags.json) {
await emitJson({ success: false, errors: regionRefusals, warnings: warningsSoFar(), conversions: conversionNotices }, 0, { compact: true });
this.exit(1);
}
const n = regionRefusals.length;
console.log('');
printError(`The automation engine would refuse to register ${n} flow${n > 1 ? 's' : ''} for a malformed region (ADR-0031)`);
printBulletList(
regionRefusals.map((r) => `${r.path} [${r.code}] ${r.message}`),
{ noun: 'flow region refusal(s)', remedy: JSON_FULL_LIST_REMEDY },
);
this.exit(1);
}
// 3b-iii-ter. [#28893] Flow region shape (ADR-0031) is judged by the
// rule table at step 3b since #29049 (`flow-region-shape-invalid`),
// the same verdict `os validate` and `os lint` give.

// 3b-quater. ADR-0141 D2, author side — every `manifest.dependencies`
// range of the packages one install act would deliver, judged by the
Expand Down
36 changes: 4 additions & 32 deletions packages/cli/src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ import { findExtensionFieldSuffixRefusals } from '../utils/extension-field-suffi
// [ADR-0146 D10, second rule] The field-patch target check's walk; the verdict
// is the marketplace install door's own judge.
import { findFieldPatchTargetRefusals } from '../utils/field-patch-targets.js';
// [#28893] The ADR-0031 region-shape check: the automation engine's own
// registration verdict (`FlowSchema` then the spec's `validateControlFlow`).
import { findFlowRegionRefusals } from '../utils/flow-region-shape.js';
// A field `picklist` that names no picklist the stack declares — refused, or
// reported when the declaring package depends on packages outside the stack.
// Walked the way the load path registers, like the refusal above.
Expand Down Expand Up @@ -754,35 +751,10 @@ export default class Validate extends Command {
this.exit(1);
}

// 3a-vi. [#28893] Flow region shape (ADR-0031): every `loop` body,
// `parallel` branch and `try_catch` region, at every depth, must be
// single-entry/single-exit and acyclic, and a `parallel` needs two
// branches. The verdict is the engine's registration call
// (`FlowSchema`, then the spec's `validateControlFlow`), so a flow
// green here is one boot registers instead of logging
// `[Automation] failed to register flow` and running without it; see
// `utils/flow-region-shape.ts`.
const regionRefusals = findFlowRegionRefusals(result.data as Record<string, unknown>);
if (regionRefusals.length > 0) {
if (flags.json) {
await emitJson({
valid: false,
errors: regionRefusals,
warnings: warningsSoFar(),
conversions: conversionNotices,
duration: timer.elapsed(),
});
this.exit(1);
}
const n = regionRefusals.length;
console.log('');
printError(`The automation engine would refuse to register ${n} flow${n > 1 ? 's' : ''} for a malformed region (ADR-0031)`);
printBulletList(
regionRefusals.map((r) => `${r.path} [${r.code}] ${r.message}`),
{ noun: 'flow region refusal(s)', remedy: JSON_FULL_LIST_REMEDY },
);
this.exit(1);
}
// 3a-vi. [#28893] Flow region shape (ADR-0031) is judged by the rule
// table above since #29049 (`flow-region-shape-invalid`,
// `@objectstack/lint`'s `validate-flow-region-shape.ts`), so all
// three authoring commands give the engine's registration verdict.

// 3b. [#3366] Installable-provider preflight — the shift-left of the
// `serve`-time capability check. `os validate` previously only checked
Expand Down
48 changes: 0 additions & 48 deletions packages/cli/src/utils/flow-region-shape.test.ts

This file was deleted.

Loading
Loading