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
13 changes: 13 additions & 0 deletions .changeset/spec-expired-until-it-lands-docblocks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@objectstack/spec': patch
---

Two published docblocks now describe the tree as it is, instead of promising a change that has already landed (#16835, #15239).

Both files ship: `@objectstack/spec` lists `src/**/*.zod.ts` in its `files[]`, so a TSDoc comment in either is a published byte and a reader gets it from the package as well as from `content/docs/references/`. No schema, export, key or predicate changes — comment text only.

**`automation/control-flow.zod.ts`** said the schema and `validateControlFlow` "do not overlap and cannot fight: the schema rejects undeclared KEYS, the analysis rejects malformed STRUCTURE", meeting at "one seam". #16134 removed exactly that division: `FlowSchema`'s `superRefine` now refuses a duplicate node id — a structural fact — across one node-id space spanning the top-level `nodes[]` and every region body. The docblock now names both seams and the boundary between them: the #4001 region-slot `safeParse`, and the #16134 node-id space judged at every depth `collectFlowGraphs` walks. That walk stops at `MAX_REGION_DEPTH` (32), so past the ceiling a region is left raw and `analyzeRegion`'s own `duplicate node id` line is the only refusal of a within-region duplicate — a cross-region collision beyond the ceiling is not judged at all. The two guards overlap there by design and hand off at that measured boundary.

**`security/sharing.zod.ts`** said, in two places, that a `field` sharing rule is skipped at seed "until [#15072] lands". It landed: `mapRecipientType` maps `field` through and `SharingRuleService.expandRecipientForRecord` reads the named column on each matched record. Both sentences now name that executor rather than a schedule.

⚠️ The third sentence the card counted — `queue` is "deliberately NOT authorable until the implementation lands" — is **still true** and deliberately untouched: it is about `sys_queue`, not about `field`, and there is no `sys_queue` object in the tree.
28 changes: 20 additions & 8 deletions content/docs/references/automation/control-flow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,26 @@ a swallowed branch key is a branch that runs without what it was given.

`validateControlFlow` is a **sibling guard, not a key gate** — it answers
"is this region single-entry / single-exit / acyclic", which no amount of
key strictness can answer. The two do not overlap and cannot fight: the
schema rejects undeclared KEYS, the analysis rejects malformed STRUCTURE.
They do now meet at one seam, deliberately — `validateControlFlow`
`safeParse`s each region slot before analyzing it, so from #4001 that parse
is also where a region's undeclared key surfaces, reported as
`<where>: invalid region — <the strictObject message>`. Nothing was
duplicated and nothing was removed; the structural prose this guard exists
for is untouched, and it simply stopped silently repairing its own input.
key strictness can answer. The two are no longer disjoint, and since #16134
that is deliberate: the schema rejects undeclared KEYS *and* one structural
fact — a duplicate node id — while the analysis rejects malformed STRUCTURE.
They meet at two seams.

**#4001** — `validateControlFlow` `safeParse`s each region slot before
analyzing it, so that parse is also where a region's undeclared key
surfaces, reported as `<where>: invalid region — <the strictObject
message>`. That seam duplicated nothing and removed nothing, and left the
structural prose this guard exists for untouched; it simply stopped silently
repairing its own input.

**#16134** — `FlowSchema`'s `superRefine` holds ONE node-id space across the
top-level `nodes[]` and every region body, judged at every depth
`collectFlowGraphs` walks. That walk stops at `MAX_REGION_DEPTH` (32), so
past the ceiling a region is left raw and `analyzeRegion`'s own
`duplicate node id` line, reached through this guard, is the only refusal of
a within-region duplicate (a cross-region collision beyond the ceiling is
not judged). The two guards overlap there by design and hand off at that
measured boundary.

<Callout type="info">
**Source:** `packages/spec/src/automation/control-flow.zod.ts`
Expand Down
28 changes: 20 additions & 8 deletions packages/spec/src/automation/control-flow.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,26 @@
*
* `validateControlFlow` is a **sibling guard, not a key gate** — it answers
* "is this region single-entry / single-exit / acyclic", which no amount of
* key strictness can answer. The two do not overlap and cannot fight: the
* schema rejects undeclared KEYS, the analysis rejects malformed STRUCTURE.
* They do now meet at one seam, deliberately — `validateControlFlow`
* `safeParse`s each region slot before analyzing it, so from #4001 that parse
* is also where a region's undeclared key surfaces, reported as
* `<where>: invalid region — <the strictObject message>`. Nothing was
* duplicated and nothing was removed; the structural prose this guard exists
* for is untouched, and it simply stopped silently repairing its own input.
* key strictness can answer. The two are no longer disjoint, and since #16134
* that is deliberate: the schema rejects undeclared KEYS *and* one structural
* fact — a duplicate node id — while the analysis rejects malformed STRUCTURE.
* They meet at two seams.
*
* **#4001** — `validateControlFlow` `safeParse`s each region slot before
* analyzing it, so that parse is also where a region's undeclared key
* surfaces, reported as `<where>: invalid region — <the strictObject
* message>`. That seam duplicated nothing and removed nothing, and left the
* structural prose this guard exists for untouched; it simply stopped silently
* repairing its own input.
*
* **#16134** — `FlowSchema`'s `superRefine` holds ONE node-id space across the
* top-level `nodes[]` and every region body, judged at every depth
* `collectFlowGraphs` walks. That walk stops at `MAX_REGION_DEPTH` (32), so
* past the ceiling a region is left raw and `analyzeRegion`'s own
* `duplicate node id` line, reached through this guard, is the only refusal of
* a within-region duplicate (a cross-region collision beyond the ceiling is
* not judged). The two guards overlap there by design and hand off at that
* measured boundary.
*/

import { z } from 'zod';
Expand Down
12 changes: 7 additions & 5 deletions packages/spec/src/security/sharing.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ export const SharingLevel = z.enum([
* principal). Unlike every member above, which resolves ONCE PER RULE, a
* `field` recipient expands ONCE PER MATCHED RECORD, and its grants
* re-materialise when the record's own write changes that column. That
* executor half is #15072 (`plugin-sharing`); until it lands, the
* declared-rule bootstrap skips a `field` rule with a logged warning
* (`mapRecipientType` → null) — it never seeds one as anything wider.
* executor half landed as #15072 (`plugin-sharing`): the declared-rule
* bootstrap's `mapRecipientType` maps `field` through, and
* `SharingRuleService.expandRecipientForRecord` reads the named column on
* each matched record — never rule-wide, and never wider than the column.
*
* ⛔ No `manager` member (same ruling). "Share with the owner's manager" is
* authored as a user field the application stores on the record — a snapshot
Expand Down Expand Up @@ -258,8 +259,9 @@ export const CriteriaSharingRuleSchema = lazySchema(() => BaseSharingRuleSchema.
* `unit_and_subordinates` / `business_unit` (ADR-0057 D5; ADR-0090 D3) — every
* authorable recipient expands at runtime (`plugin-sharing` `expandRecipient`)
* — plus `field`, the record-relative recipient (#14103): resolved per matched
* record from a user-typed column on it; its executor is the services half,
* #15072, and until that lands a `field` rule is skipped LOUDLY at seed.
* record from a user-typed column on it. Its services half, #15072, landed:
* `expandRecipientForRecord` resolves it per record, so a `field` rule seeds
* at bootstrap like any other instead of being skipped there.
*
* The whole authorable surface is enforced — nothing here validates and then
* silently does nothing (ADR-0078). Removed to keep it that way: `owner`-type
Expand Down
Loading