From 5b27b7e4b4efa61ff12b20560c12c682b07be9c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 11:53:36 +0000 Subject: [PATCH 01/11] wip(spec,objectql): declare the inert-JSON artifact and registry-record package body stages Four stages, four declarations: authoring, in-memory assembled, on-disk artifact, registry record. The last two had no declaration until now. - packages/spec/src/automation/flow-function.zod.ts exports FlowFunctionLoweredDeclarationSchema, the serialisable half of the pair. - packages/spec/src/stack.zod.ts declares ArtifactStagePackageBodySchema and RecordStagePackageBodySchema BESIDE AssembledPackageBodySchema, which is untouched (composeStacks keeps building bodies that hold live callables). - packages/spec/src/api/package-api.zod.ts rebinds the installed-package row's manifest to the record stage; the z.unknown() override and the docblock defending it are gone. - packages/objectql/src/registry.ts normalises a bare callable functions entry to the declared form at the assembly boundary, so the structural projection reports every declared function instead of dropping the bare ones. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- packages/objectql/src/registry.ts | 76 ++++++++- packages/spec/src/api/package-api.zod.ts | 63 ++++---- .../spec/src/automation/flow-function.zod.ts | 11 +- packages/spec/src/stack.zod.ts | 144 +++++++++++++++++- 4 files changed, 255 insertions(+), 39 deletions(-) diff --git a/packages/objectql/src/registry.ts b/packages/objectql/src/registry.ts index f75d202b2b9..c3784492f27 100644 --- a/packages/objectql/src/registry.ts +++ b/packages/objectql/src/registry.ts @@ -45,6 +45,7 @@ import { resolveTenancyPosture, resolveSearchPinyinEnabled } from '@objectstack/ import { postureEnforcesWall } from '@objectstack/spec/security'; import { provisionSearchCompanion, SEARCH_COMPANION_FIELD } from './search-companion.js'; import { ObjectStackManifest, ManifestSchema, InstalledPackage, InstalledPackageSchema, checkFieldCompleteness } from '@objectstack/spec/kernel'; +import { DEFAULT_FLOW_FUNCTION_EFFECT } from '@objectstack/spec/automation'; import { AppSchema } from '@objectstack/spec/ui'; import { applyProtection } from '@objectstack/spec/shared'; // [ADR-0130 D1] The ONE derivation of the key a package is installed under @@ -1364,6 +1365,74 @@ function toRecordManifest(manifest: ObjectStackManifest): ObjectStackManifest { return out as ObjectStackManifest; } +/** + * Rewrite a BARE callable `functions` map entry into the declared form + * (`{ handler, effect }`) before {@link toRecordManifest} projects the body. + * + * ## The under-report this closes + * + * `functions` accepts two authored spellings, and the projection treats them + * unequally through no fault of its own: + * + * - `{ sweepProjectHealth: { handler: fn, effect: 'writes' } }` — the value is + * a plain object, so it survives with its callable dropped: `{ effect: + * 'writes' }`. The function is still REPORTED, minus what cannot serialise. + * - `{ summarizeCompletedTask: fn }` — the value IS the callable, so the whole + * ENTRY is dropped, key and all. The function vanishes from the record. + * + * `examples/app-showcase` ships one of each, so `GET /packages` reported ONE + * function for a package declaring TWO. A machine-readable read door that + * under-reports is a defect independent of how its rows are declared. + * + * ## Why it is fixed HERE and not in the projection + * + * {@link toRecordManifest}'s rule is STRUCTURAL — 「a live object reached the + * record」, ⛔ never a key denylist — and teaching it that a function found + * under `functions.` means something other than a function found + * anywhere else would overturn exactly that. So the repair is at the assembly + * boundary instead: the two spellings are made structurally EQUAL before the + * projection runs, and the projection then leaves `{ effect }` for both. + * + * ⛔ No ref is minted. `packages/cli`'s `lowerCallables` mints refs with + * `uniqueName(base, taken)` and dedupes by function IDENTITY, so a ref minted + * here would not be guaranteed equal to the one `objectstack build` mints — a + * record could then assert a handler that resolves in no sibling module. An + * ABSENT `handler` on a record is the honest statement 「declared here, not + * serialisable」, and `RecordStagePackageBodySchema` in `@objectstack/spec` + * declares exactly that. + * + * `effect` is the declaration schema's OWN default + * ({@link DEFAULT_FLOW_FUNCTION_EFFECT}, `'pure'`, which + * `FlowFunctionDeclarationSchema.effect` carries as `.default(…)`), ⛔ not a + * value invented here: the bare spelling IS that declaration written the short + * way, and `normalizeFlowFunctionEntry` already reads it the same way at boot. + * The ARRAY form needs nothing — its entries are objects that carry their own + * `name`, so the projection keeps them — and its `effect` is `.optional()` with + * no default, so nothing is written where the schema declares nothing. + * + * ⛔ The caller's manifest is never mutated: the live object is what every + * other read in `installPackage` uses, and a copy is returned only when an + * entry really needed rewriting. + */ +function withDeclaredFunctionEntries(manifest: ObjectStackManifest): ObjectStackManifest { + if (manifest === null || typeof manifest !== 'object') return manifest; + const functions = (manifest as { functions?: unknown }).functions; + if (!functions || typeof functions !== 'object' || Array.isArray(functions)) return manifest; + + let rewrote = false; + const declared: Record = {}; + for (const [name, entry] of Object.entries(functions as Record)) { + if (typeof entry === 'function') { + declared[name] = { handler: entry, effect: DEFAULT_FLOW_FUNCTION_EFFECT }; + rewrote = true; + } else { + declared[name] = entry; + } + } + if (!rewrote) return manifest; + return { ...(manifest as Record), functions: declared } as ObjectStackManifest; +} + /** * [#16159] The ADR-0112 `code` strings this file's three install-time and * registration refusals carry, as constants a consumer can import. @@ -4224,7 +4293,12 @@ export class SchemaRegistry { // {@link toRecordManifest}. Every other read below (`manifest.id`, // `manifest.namespace`) deliberately keeps reading the ARGUMENT: the // projection is what the registry hands out, never what it decides with. - manifest: toRecordManifest(manifest), + // + // {@link withDeclaredFunctionEntries} runs FIRST and at this boundary + // only: it makes the two authored `functions` spellings structurally + // equal so the structural projection reports BOTH, without teaching the + // projection a key name. + manifest: toRecordManifest(withDeclaredFunctionEntries(manifest)), ...lifecycle, installedAt: now, updatedAt: now, diff --git a/packages/spec/src/api/package-api.zod.ts b/packages/spec/src/api/package-api.zod.ts index bf671d79d1d..00cca4e3d2f 100644 --- a/packages/spec/src/api/package-api.zod.ts +++ b/packages/spec/src/api/package-api.zod.ts @@ -8,7 +8,7 @@ import { UpgradePlanSchema } from '../kernel/package-upgrade.zod'; import { PackageArtifactSchema } from '../kernel/package-artifact.zod'; import { ManifestSchema } from '../kernel/manifest.zod'; import { ArtifactReferenceSchema } from '../marketplace/marketplace.zod'; -import { AssembledPackageBodySchema } from '../stack.zod'; +import { RecordStagePackageBodySchema } from '../stack.zod'; /** * # Package API Protocol @@ -76,15 +76,9 @@ export type PackagePathParams = z.input; * The body half is deliberately typed `Record`; the reason is * recorded at `AssembledPackageBodySchema` and is not repeated here. The RUNTIME * schema still carries the manifest's every field plus every collection's full - * declaration, so a wrong-shaped body is refused exactly as it is there — with - * the one measured exception {@link AssembledPackageRecordBodySchema} states - * and pins. - */ -/** - * The assembled package body AS THE REGISTRY RECORDS IT — the same declaration, - * with the two collections that have no JSON form left unchecked. + * declaration, so a wrong-shaped body is refused exactly as it is there. * - * ## Why this exists at all, measured rather than assumed + * ## The row's manifest is the RECORD stage, not the assembled one * * `SchemaRegistry.installPackage` does not store the caller's object; it stores * `toRecordManifest(manifest)`, a structural JSON projection that DROPS @@ -96,37 +90,34 @@ export type PackagePathParams = z.input; * - `hooks` — a `z.custom()` branch (a lifecycle handler). * * Those same two are the reason `AssembledPackageBodySchema` has NO JSON Schema - * at all: `z.toJSONSchema` refuses a function and a custom type, which is also - * why `ArtifactPackageSchema` and `ObjectStackDefinitionSchema` publish none. - * Embedding the body verbatim in the two published response schemas below made - * BOTH of them disappear from `json-schema/api/`, which the build's own - * disappearance ratchet refuses and whose only other remedy is retiring two - * published defs. `build-schemas.ts` names the remedy taken here instead: + * at all: `z.toJSONSchema` refuses a function and a custom type, and embedding + * the body verbatim in the two published response schemas below made BOTH of + * them disappear from `json-schema/api/`, which the build's own disappearance + * ratchet refuses. `build-schemas.ts` names the remedy taken here: * «make it emit — narrow the unrepresentable member». * - * ⛔ The override set is NOT hand-picked, and must never become so. It is the - * measured set of shape members with no JSON form, pinned key-by-key in - * `./package-api.test.ts`: a new collection with no JSON form reddens there, - * naming itself, instead of silently unpublishing these responses again. - * - * ⚠️ What `unknown` costs, stated plainly: on THIS surface those two keys are - * accepted without being checked. It is a widening from today, where both are - * refused outright by `ManifestSchema`'s strict close while the door really can - * serve them — so the declaration moves from wrong to incomplete, never from - * checked to tolerant. Every other key, `objects` included, is checked at the - * assembled stage exactly as `AssembledPackageBodySchema` declares it. The - * ARTIFACT surface is untouched and keeps both collections fully declared. + * ⚠️ ⛔ Those two members are NOT why `ArtifactPackageSchema` and + * `ObjectStackDefinitionSchema` publish no JSON Schema — an earlier version of + * this docblock said they were, and it is false. `src/stack.zod.ts` is not one + * of the subpath namespaces `build-schemas.ts` walks, so neither schema is ever + * reached by the emit loop; repairing the two branches would not make either + * appear. What the narrowing below buys is this file's own two responses, which + * ARE in the emit loop. + * + * ⭐ The narrowing is a DECLARATION rather than a hole. Until #17518 these two + * keys were `z.unknown().optional()` here — accepted without being checked — + * and that hole is what `RecordStagePackageBodySchema` replaces: the registry + * record stage, declared in `../stack.zod` beside the assembled and artifact + * stages, is the assembled body with both collections lowered and + * `functions[].handler` optional. ⛔ Never widen either key back to `unknown` + * to make a row fit: a row that parses through neither declared stage is a + * producer defect, and the record stage exists to keep saying so. The set of + * members that need the treatment is MEASURED, never hand-picked — pinned + * key-by-key in `./package-api.test.ts`, so a new collection with no JSON form + * reddens there, naming itself. */ -const AssembledPackageRecordBodySchema = lazySchema(() => - (AssembledPackageBodySchema as unknown as z.ZodObject).extend({ - functions: z.unknown().optional() - .describe('Named handler functions, as they survived the record JSON projection'), - hooks: z.unknown().optional() - .describe('Object lifecycle hooks, as they survived the record JSON projection'), - }).describe('One package as assembled, as the registry RECORDS it (JSON only)')); - export const AssembledInstalledPackageSchema = lazySchema(() => InstalledPackageSchema.extend({ - manifest: AssembledPackageRecordBodySchema.describe('The ASSEMBLED package body this row carries'), + manifest: RecordStagePackageBodySchema.describe('The ASSEMBLED package body this row carries, at the stage the registry records it'), }).describe('Installed package row whose manifest is the assembled package body')); export type AssembledInstalledPackage = z.input; /** Post-parse shape of {@link AssembledInstalledPackage} — defaults applied, transforms run (ADR-0122). */ diff --git a/packages/spec/src/automation/flow-function.zod.ts b/packages/spec/src/automation/flow-function.zod.ts index 24358351950..e2b5a29755e 100644 --- a/packages/spec/src/automation/flow-function.zod.ts +++ b/packages/spec/src/automation/flow-function.zod.ts @@ -207,8 +207,17 @@ export type FlowFunctionDeclarationParsed = z.infer FlowFunctionDeclarationSchema.extend({ +export const FlowFunctionLoweredDeclarationSchema = lazySchema(() => FlowFunctionDeclarationSchema.extend({ handler: z.string().min(1) .describe('The lowered handler ref (built artifacts) — the callable rides in the sibling ESM module'), }).describe('A lowered `functions` declaration: what the function declared about itself, with its callable replaced by a handler ref')); diff --git a/packages/spec/src/stack.zod.ts b/packages/spec/src/stack.zod.ts index 11f3c284217..60c13b43c6c 100644 --- a/packages/spec/src/stack.zod.ts +++ b/packages/spec/src/stack.zod.ts @@ -34,7 +34,7 @@ import { ActionSchema, InlineActionSchema } from './ui/action.zod'; // Automation Protocol import { FlowSchema } from './automation/flow.zod'; import { resolveFlowTriggerKind } from './automation/flow-trigger-kind'; -import { FlowFunctionEntrySchema, FlowFunctionEffectSchema } from './automation/flow-function.zod'; +import { FlowFunctionEntrySchema, FlowFunctionEffectSchema, FlowFunctionLoweredDeclarationSchema } from './automation/flow-function.zod'; import { JobSchema } from './system/job.zod'; // Security Protocol @@ -1293,6 +1293,148 @@ export type AssembledPackageBody = z.input; /** Post-parse shape of {@link AssembledPackageBody} — defaults applied, transforms run (ADR-0122). */ export type AssembledPackageBodyParsed = z.infer; +/** + * `hooks`, as a JSON document can hold it: the assembled declaration with its + * `handler` narrowed to the lowered string ref. + * + * The narrowing is written HERE rather than on `HookSchema`, because the + * authoring door must keep accepting the inline callable — `objectstack build` + * is what lowers it, and `data/hook.zod.ts` says so in its own words: 「The + * JSON artifact therefore only ever contains the string form.」 `handler` keeps + * the base's `.optional()`: a hook that carries a `body` instead declares no + * handler at all, and the registry record of a hook whose handler was an inline + * callable has none either (the projection drops it). + */ +function jsonStageHooksKey() { + return z.array(HookSchema.extend({ + handler: z.string().optional() + .describe('Handler function name — the lowered string ref the JSON artifact carries'), + })).optional().describe('Object Lifecycle Hooks, as a JSON document carries them'); +} + +/** + * `functions`, as a JSON document can hold it: the two LOWERED members of + * {@link FlowFunctionEntrySchema} for the map form, and the array member with + * its callable branch dropped. + * + * Both lowered spellings are kept, not just the record one: `objectstack build` + * emits `{ myFn: 'myFn' }` for a bare entry and + * `{ myFn: { handler: 'myFn', effect: 'writes' } }` for a declared one, so a + * stage that admitted only the second would refuse artifacts this repo really + * writes. + * + * @param handlerOptional the RECORD stage's one difference from the artifact + * stage. A registry row is `toRecordManifest`'s structural JSON projection of + * the live assembled body, and that projection drops the callable it finds + * under `handler` — so the record of a declared function is the declaration + * MINUS its handler. Nothing replaces it, and nothing should: a ref minted + * anywhere but `objectstack build` is not guaranteed to be the ref `build` + * mints, so an absent `handler` is the honest statement 「declared here, not + * serialisable」. `effect` carries whatever optionality each form already gives + * it — `.default('pure')` on the map declaration, `.optional()` on the array + * entry — and neither is restated here. + */ +function jsonStageFunctionsKey(handlerOptional: boolean) { + const handlerRef = z.string().min(1) + .describe('The lowered handler ref (built artifacts) — the callable rides in the sibling ESM module'); + return z.union([ + z.record(z.string(), z.union([ + handlerRef, + handlerOptional + ? FlowFunctionLoweredDeclarationSchema.extend({ handler: handlerRef.optional() }) + : FlowFunctionLoweredDeclarationSchema, + ])), + // Transcribed rather than derived from the authoring array member above: + // that member is declared INLINE inside the assembled body's own shape, and + // narrowing it in place is the one thing this pair may not do. The key sets + // are held equal by a pin in `stack-json-stage-package-body.test.ts`, so a + // key added there and not here reddens by name. + z.array(z.object({ + name: z.string(), + handler: handlerOptional ? handlerRef.optional() : handlerRef, + packageId: z.string().optional(), + effect: FlowFunctionEffectSchema.optional(), + })), + ]).optional().describe('Named handler functions, lowered to the refs a JSON document carries'); +} + +/** + * One package as an INERT JSON release artifact carries it — the third of the + * four stages a package body passes through, and the first one that is really + * JSON. + * + * ## Why this is a separate declaration and not a narrowing of the assembled body + * + * {@link AssembledPackageBodySchema} spans the IN-MEMORY composed stage, where + * `functions` and `hooks` legitimately hold live callables: + * `composeStacks(stacks, { manifest: 'preserve' })` builds exactly such a body + * and the load path registers it, which is the invariant this file states + * further down. Narrowing the assembled body would refuse a published + * composition function's own output — so the artifact stage gets its own name + * instead, and the assembled one is ⛔ untouched. + * + * ## What it buys, measured + * + * Exactly two of the assembled body's members have no JSON Schema form — + * `functions` (a `z.function()` branch) and `hooks` (a `z.custom()` branch) — + * and one unrepresentable member costs every embedder its whole JSON Schema. + * With both narrowed to their lowered spellings this body converts under + * `z.toJSONSchema`, so a JSON surface that wants the assembled stage can + * declare it instead of writing `z.unknown()` and accepting anything. + * + * ADR-0130 D4 is what says an artifact is inert JSON: 「a plugin written inside + * `packages[i].manifest` could never be constructed by a loader, so a reader + * that resolved it there would register garbage where it used to skip in + * silence.」 A callable in an artifact is the same case. + */ +/* + * ANNOTATED with the same STRUCTURAL type as the assembled body above, for the + * same two measured reasons recorded there (TS7056 on the inferred type; a + * named alias turning `stack.zod` into a shared chunk). ⛔ Do not replace either + * annotation with an inferred or named type without re-reading that note. + */ +export const ArtifactStagePackageBodySchema: z.ZodType, Record> = + lazySchema(() => + ManifestSchema.extend({ + ...assembledPackageBodyShape(), + functions: jsonStageFunctionsKey(false), + hooks: jsonStageHooksKey(), + }).describe('One package as an inert-JSON release artifact carries it (ADR-0130 D4)')); + +/** The artifact-stage package body as authored. */ +export type ArtifactStagePackageBody = z.input; +/** Post-parse shape of {@link ArtifactStagePackageBody} — defaults applied, transforms run (ADR-0122). */ +export type ArtifactStagePackageBodyParsed = z.infer; + +/** + * One package as the package REGISTRY records it — the fourth stage, and the + * one that had no declaration until now. + * + * It is {@link ArtifactStagePackageBodySchema} with `functions[].handler` + * OPTIONAL, in both the map-record form and the array form, and nothing else. + * That single difference is the whole distance between an artifact and a + * record: an artifact is written by `objectstack build`, which lowers every + * callable to a ref, while a record is `toRecordManifest`'s structural JSON + * projection of a LIVE body, which drops the callable and mints nothing in its + * place. A record therefore reports what each function is named and what it + * declared, with `handler` absent where the callable was. + * + * ⛔ Never widen this to `z.unknown()` to make a row fit. A row that parses + * through neither this stage nor the authoring one is a producer defect, and + * this is the declaration that has to keep saying so. + */ +/* ANNOTATED structurally — see the note on the artifact stage above. */ +export const RecordStagePackageBodySchema: z.ZodType, Record> = + lazySchema(() => + (ArtifactStagePackageBodySchema as unknown as z.ZodObject).extend({ + functions: jsonStageFunctionsKey(true), + }).describe('One package as the package registry records it — the artifact stage with `functions[].handler` optional')); + +/** The record-stage package body as authored. */ +export type RecordStagePackageBody = z.input; +/** Post-parse shape of {@link RecordStagePackageBody} — defaults applied, transforms run (ADR-0122). */ +export type RecordStagePackageBodyParsed = z.infer; + /** * One package carried by a release artifact, in its ASSEMBLED form — the * element type of `packages` on {@link ObjectStackDefinitionSchema}. From a0d3c56f64638f158dd16f44ab2defb3348442bb Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 12:03:44 +0000 Subject: [PATCH 02/11] chore(spec,objectql): regenerate the spec ledgers the new export moves, and fix the record-copy cast - json-schema.manifest / authorable-surface / authorable-defaults gain automation/FlowFunctionLoweredDeclaration: the lowered record now publishes, which is what unemitted-schemas.baseline.json already claimed about it. - dropped-refinements.baseline.json gains one site per installed-package response: the record stage DECLARES hooks where z.unknown() declared nothing, so HookSchema's `object` refinement now reaches the runtime and not the file. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- packages/objectql/src/registry.ts | 2 +- packages/spec/authorable-defaults/automation.json | 1 + packages/spec/authorable-surface/automation.json | 2 ++ packages/spec/dropped-refinements.baseline.json | 6 +++++- packages/spec/json-schema.manifest/automation.json | 1 + 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/objectql/src/registry.ts b/packages/objectql/src/registry.ts index c3784492f27..b0af60d72dc 100644 --- a/packages/objectql/src/registry.ts +++ b/packages/objectql/src/registry.ts @@ -1430,7 +1430,7 @@ function withDeclaredFunctionEntries(manifest: ObjectStackManifest): ObjectStack } } if (!rewrote) return manifest; - return { ...(manifest as Record), functions: declared } as ObjectStackManifest; + return { ...manifest, functions: declared } as ObjectStackManifest; } /** diff --git a/packages/spec/authorable-defaults/automation.json b/packages/spec/authorable-defaults/automation.json index 21266b7d520..1cd83127d43 100644 --- a/packages/spec/authorable-defaults/automation.json +++ b/packages/spec/authorable-defaults/automation.json @@ -42,6 +42,7 @@ "automation/Flow:version = 1", "automation/FlowEdge:isDefault = false", "automation/FlowEdge:type = \"default\"", + "automation/FlowFunctionLoweredDeclaration:effect = \"pure\"", "automation/FlowVariable:isInput = false", "automation/FlowVariable:isOutput = false", "automation/LoopConfig:iteratorVariable = \"item\"", diff --git a/packages/spec/authorable-surface/automation.json b/packages/spec/authorable-surface/automation.json index 35396430b34..a3cb0dd2dd6 100644 --- a/packages/spec/authorable-surface/automation.json +++ b/packages/spec/authorable-surface/automation.json @@ -178,6 +178,8 @@ "automation/FlowEdge:source", "automation/FlowEdge:target", "automation/FlowEdge:type", + "automation/FlowFunctionLoweredDeclaration:effect", + "automation/FlowFunctionLoweredDeclaration:handler", "automation/FlowNode:boundaryConfig", "automation/FlowNode:config", "automation/FlowNode:connectorConfig", diff --git a/packages/spec/dropped-refinements.baseline.json b/packages/spec/dropped-refinements.baseline.json index 09732185ca7..a63504d2a4b 100644 --- a/packages/spec/dropped-refinements.baseline.json +++ b/packages/spec/dropped-refinements.baseline.json @@ -3,7 +3,7 @@ "measured": { "zod": "4.4.3", "publishedSchemasWithDroppedRefinements": 204, - "droppedRefinementSites": 560, + "droppedRefinementSites": 564, "refinementSitesThatDidProject": 357, "refinementSitesWithNoJsonFormToCompare": 9 }, @@ -61,6 +61,7 @@ "manifest.flows.element", "manifest.flows.element.errorHandling", "manifest.flows.element.nodes.element.in.waitEventConfig", + "manifest.hooks.element.object", "manifest.jobs.element.schedule.options[0].timezone", "manifest.navigationContributions.element.items.element.lazy.options[0]", "manifest.objectExtensions.element", @@ -153,6 +154,7 @@ "data.options[1].manifest.flows.element", "data.options[1].manifest.flows.element.errorHandling", "data.options[1].manifest.flows.element.nodes.element.in.waitEventConfig", + "data.options[1].manifest.hooks.element.object", "data.options[1].manifest.jobs.element.schedule.options[0].timezone", "data.options[1].manifest.objectExtensions.element", "data.options[1].manifest.objects.element", @@ -234,6 +236,7 @@ "options[1].manifest.flows.element", "options[1].manifest.flows.element.errorHandling", "options[1].manifest.flows.element.nodes.element.in.waitEventConfig", + "options[1].manifest.hooks.element.object", "options[1].manifest.jobs.element.schedule.options[0].timezone", "options[1].manifest.objectExtensions.element", "options[1].manifest.objects.element", @@ -276,6 +279,7 @@ "data.packages.element.options[1].manifest.flows.element", "data.packages.element.options[1].manifest.flows.element.errorHandling", "data.packages.element.options[1].manifest.flows.element.nodes.element.in.waitEventConfig", + "data.packages.element.options[1].manifest.hooks.element.object", "data.packages.element.options[1].manifest.jobs.element.schedule.options[0].timezone", "data.packages.element.options[1].manifest.objectExtensions.element", "data.packages.element.options[1].manifest.objects.element", diff --git a/packages/spec/json-schema.manifest/automation.json b/packages/spec/json-schema.manifest/automation.json index 0ebb133885f..c42c760c510 100644 --- a/packages/spec/json-schema.manifest/automation.json +++ b/packages/spec/json-schema.manifest/automation.json @@ -39,6 +39,7 @@ "automation/Flow", "automation/FlowEdge", "automation/FlowFunctionEffect", + "automation/FlowFunctionLoweredDeclaration", "automation/FlowNode", "automation/FlowNodeAction", "automation/FlowRegion", From 0adcf52ce222958eb63e2e29aecb8b850a9d28c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 12:10:48 +0000 Subject: [PATCH 03/11] test(spec,objectql): pin the two JSON stages and the record's function reporting Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- ...stry-package-manifest-serializable.test.ts | 124 +++++++++ packages/spec/src/api/package-api.test.ts | 71 ++++- .../src/stack-json-stage-package-body.test.ts | 252 ++++++++++++++++++ 3 files changed, 440 insertions(+), 7 deletions(-) create mode 100644 packages/spec/src/stack-json-stage-package-body.test.ts diff --git a/packages/objectql/src/registry-package-manifest-serializable.test.ts b/packages/objectql/src/registry-package-manifest-serializable.test.ts index b52c8991e47..1f4a8dad473 100644 --- a/packages/objectql/src/registry-package-manifest-serializable.test.ts +++ b/packages/objectql/src/registry-package-manifest-serializable.test.ts @@ -192,3 +192,127 @@ describe('SchemaRegistry.installPackage — the record is serializable', () => { expect(registry.getNamespaceOwners('showcase')).toEqual(['com.example.showcase']); }); }); + +/** + * #17518 — the record reports EVERY declared function, in one shape. + * + * The projection above is structural and stays so: 「a live object reached the + * record」, ⛔ never a key denylist. That rule treated the two authored + * `functions` spellings unequally through no fault of its own — a DECLARED + * entry (`{ handler, effect }`) is a plain object, so it survived with its + * callable dropped, while a BARE callable entry IS the callable, so the whole + * key disappeared. `examples/app-showcase` ships one of each, so a package + * declaring two functions was reported as declaring one on `GET /packages`. + * + * The repair makes the two spellings structurally EQUAL before the projection + * runs, at the assembly boundary. ⛔ No ref is minted: `objectstack build` + * mints refs with `uniqueName(base, taken)` and dedupes by function identity, + * so a ref minted here could name a handler that resolves in no sibling module. + * An ABSENT `handler` is the honest statement 「declared here, not + * serialisable」, and `RecordStagePackageBodySchema` in `@objectstack/spec` + * declares exactly that. + */ +describe('SchemaRegistry.installPackage — the record reports every declared function', () => { + let registry: SchemaRegistry; + + beforeEach(() => { + registry = new SchemaRegistry({ multiTenant: false, collisionPolicy: 'error' }); + registry.logLevel = 'silent'; + }); + + /** The two spellings `examples/app-showcase/objectstack.config.ts` ships. */ + const showcaseFunctions = () => ({ + summarizeCompletedTask: () => 'summary', + sweepProjectHealth: { handler: () => 'swept', effect: 'writes' as const }, + }); + + it('records BOTH spellings — the bare one as a handler-less declaration', () => { + registry.installPackage(baseManifest({ functions: showcaseFunctions() })); + const stored = registry.getPackage('com.example.showcase')!.manifest as any; + + expect(Object.keys(stored.functions).sort()).toEqual(['summarizeCompletedTask', 'sweepProjectHealth']); + // The bare entry keeps what the bare spelling MEANS — the declaration + // `effect: 'pure'`, written the short way — read off the declaration + // schema's own default rather than invented here. + expect(stored.functions.summarizeCompletedTask).toEqual({ effect: 'pure' }); + // The declared entry is unchanged in substance: only the callable is gone. + expect(stored.functions.sweepProjectHealth).toEqual({ effect: 'writes' }); + }); + + it('⛔ mints no handler ref — an absent `handler` is the honest statement', () => { + registry.installPackage(baseManifest({ functions: showcaseFunctions() })); + const stored = registry.getPackage('com.example.showcase')!.manifest as any; + + for (const entry of Object.values(stored.functions) as Array>) { + expect('handler' in entry).toBe(false); + } + }); + + it('leaves an ALREADY LOWERED body exactly as it found it', () => { + // What `objectstack build` writes, and what an artifact boot installs when + // no runtime module re-attached the callables: a bare ref and a lowered + // record. Neither is a callable, so neither is normalised. + const lowered = { bare: 'bare', declared: { handler: 'declared', effect: 'writes' } }; + registry.installPackage(baseManifest({ functions: lowered })); + + expect((registry.getPackage('com.example.showcase')!.manifest as any).functions).toEqual(lowered); + }); + + it('leaves the ARRAY form alone — its entries are objects that name themselves', () => { + // An array entry carries its own `name`, so the projection already keeps + // it; only its callable goes. Its `effect` is `.optional()` with NO + // default, so ⛔ nothing is written where the schema declares nothing. + registry.installPackage(baseManifest({ + functions: [{ name: 'syncBilling', handler: () => 'billed', effect: 'writes' }], + })); + const stored = registry.getPackage('com.example.showcase')!.manifest as any; + + expect(stored.functions).toEqual([{ name: 'syncBilling', effect: 'writes' }]); + const bare = new SchemaRegistry({ multiTenant: false, collisionPolicy: 'error' }); + bare.logLevel = 'silent'; + bare.installPackage(baseManifest({ functions: [{ name: 'syncBilling', handler: () => 'billed' }] })); + expect((bare.getPackage('com.example.showcase')!.manifest as any).functions) + .toEqual([{ name: 'syncBilling' }]); + }); + + it('⛔ does not mutate the caller’s manifest — the live callables stay live', () => { + // `ObjectQL.registerApp` and the hook binder read the callables off THIS + // object. Normalising must copy, exactly as the projection does. + const functions = showcaseFunctions(); + const manifest = baseManifest({ functions }); + registry.installPackage(manifest); + + expect(typeof manifest.functions.summarizeCompletedTask).toBe('function'); + expect(manifest.functions).toBe(functions); + expect(typeof functions.sweepProjectHealth.handler).toBe('function'); + }); + + it('touches nothing when there is nothing to normalise', () => { + // A manifest with no `functions` key, and one whose entries are all already + // declared, must reach the projection as the SAME object — the normaliser + // copies only when it really rewrote an entry. + const untouched = baseManifest(); + registry.installPackage(untouched); + const stored = registry.getPackage('com.example.showcase')!.manifest as any; + expect('functions' in stored).toBe(false); + expect(stored.objects).toEqual(untouched.objects); + }); + + it('hooks are NOT normalised — an inline handler is simply dropped', () => { + // `hooks[].handler` is optional on its own declaration, so a hook whose + // inline callable is projected away is still a well-formed record. ⛔ This + // repair does not reach into a second collection. + registry.installPackage(baseManifest({ + hooks: [ + { name: 'on_insert', object: 'invoice', events: ['beforeInsert'], handler: () => 'hooked' }, + { name: 'on_update', object: 'invoice', events: ['beforeUpdate'], handler: 'on_update' }, + ], + })); + const stored = registry.getPackage('com.example.showcase')!.manifest as any; + + expect(stored.hooks).toEqual([ + { name: 'on_insert', object: 'invoice', events: ['beforeInsert'] }, + { name: 'on_update', object: 'invoice', events: ['beforeUpdate'], handler: 'on_update' }, + ]); + }); +}); diff --git a/packages/spec/src/api/package-api.test.ts b/packages/spec/src/api/package-api.test.ts index 1d7884cab01..5446312d549 100644 --- a/packages/spec/src/api/package-api.test.ts +++ b/packages/spec/src/api/package-api.test.ts @@ -573,7 +573,7 @@ describe('`InstalledPackageAtEitherStageSchema` admits both stages and NOTHING e }); }); -describe('the record-body override set is MEASURED, never hand-picked', () => { +describe('the set the record stage must RE-DECLARE is MEASURED, never hand-picked', () => { /** Does this schema have a JSON Schema form at all? */ const emits = (schema: unknown): boolean => { try { @@ -585,12 +585,12 @@ describe('the record-body override set is MEASURED, never hand-picked', () => { }; it('exactly `functions` and `hooks` have no JSON form on the assembled body', () => { - // The two published response schemas below embed the assembled body. Any - // collection with no JSON form makes them BOTH vanish from - // `json-schema/api/`, which the build's disappearance ratchet refuses — so - // the read-API record body overrides exactly this set, and this pin is what - // keeps the two in step. A new non-serialisable collection reddens HERE, - // naming itself, rather than unpublishing two response schemas. + // The two published response schemas below embed the row, whose manifest is + // the RECORD stage. Any collection with no JSON form makes them BOTH vanish + // from `json-schema/api/`, which the build's disappearance ratchet refuses + // — so the record stage declares exactly this set in its lowered form, and + // this pin is what keeps the two in step. A new non-serialisable collection + // reddens HERE, naming itself, rather than unpublishing two responses. const shape = (AssembledPackageBodySchema as unknown as { shape: Record }).shape; const noJsonForm = Object.keys(shape).filter((k) => !emits(shape[k])); expect(noJsonForm.sort()).toEqual(['functions', 'hooks']); @@ -604,6 +604,63 @@ describe('the record-body override set is MEASURED, never hand-picked', () => { }); }); +describe('#17518 the row\'s manifest is the RECORD stage — a declaration, ⛔ not `z.unknown()`', () => { + /** + * What `toRecordManifest` really leaves on a `GET /packages` row: each + * `functions` declaration MINUS its callable, and a hook whose inline handler + * is gone. Until #17518 both keys were `z.unknown().optional()` here, i.e. + * accepted without being checked. + */ + const RECORD_ROW = { + ...LIFECYCLE, + manifest: { + ...MANIFEST_BASE, + objects: [{ name: 'stage_lead', fields: { title: { type: 'text' } } }], + functions: { + summarizeCompletedTask: { effect: 'pure' }, + sweepProjectHealth: { effect: 'writes' }, + }, + hooks: [{ name: 'on_insert', object: 'stage_lead', events: ['beforeInsert'] }], + }, + }; + + it('parses a row carrying the residual the projection really produces', () => { + expect(AssembledInstalledPackageSchema.safeParse(RECORD_ROW).success).toBe(true); + expect(InstalledPackageAtEitherStageSchema.safeParse(RECORD_ROW).success).toBe(true); + }); + + it('parses a row carrying what `objectstack build` lowered', () => { + const lowered = { + ...RECORD_ROW, + manifest: { + ...RECORD_ROW.manifest, + functions: { bare: 'bare', declared: { handler: 'declared', effect: 'writes' } }, + hooks: [{ name: 'on_insert', object: 'stage_lead', events: ['beforeInsert'], handler: 'on_insert' }], + }, + }; + expect(AssembledInstalledPackageSchema.safeParse(lowered).success).toBe(true); + }); + + it('⛔ REFUSES a live callable — a row the registry can never serve', () => { + // The direction that matters: the two keys moved from "accepts anything" to + // a declaration, so a value no JSON row can hold is refused by name instead + // of waved through. ⛔ Never widen either key back to `unknown` to make a + // payload fit: a row parsing through neither declared stage is a producer + // defect. + const live = { + ...RECORD_ROW, + manifest: { ...RECORD_ROW.manifest, functions: { sweepProjectHealth: () => 'ran' } }, + }; + const verdict = AssembledInstalledPackageSchema.safeParse(live); + expect(verdict.success).toBe(false); + expect(verdict.error!.issues.some((i) => i.path.join('.').startsWith('manifest.functions'))).toBe(true); + }); + + it('⛔ still refuses the AUTHORING spelling of `objects` — the stage boundary did not move', () => { + expect(AssembledInstalledPackageSchema.safeParse(GLOB_ROW).success).toBe(false); + }); +}); + describe('the read-API responses are declared at both stages (#17431)', () => { const envelope = (data: unknown) => ({ success: true, data }); diff --git a/packages/spec/src/stack-json-stage-package-body.test.ts b/packages/spec/src/stack-json-stage-package-body.test.ts new file mode 100644 index 00000000000..359b1ba7081 --- /dev/null +++ b/packages/spec/src/stack-json-stage-package-body.test.ts @@ -0,0 +1,252 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * #17518 — the two JSON stages of a package body, declared beside the assembled + * one, and pinned as three genuinely different shapes rather than three names + * for one. + * + * ## The defect, stated as the measurement that found it + * + * ADR-0130 D4 says an artifact is inert JSON: 「a plugin written inside + * `packages[i].manifest` could never be constructed by a loader, so a reader + * that resolved it there would register garbage where it used to skip in + * silence.」 Of {@link AssembledPackageBodySchema}'s 55 members exactly two + * declare that they accept a callable — `functions` (a `z.function()` branch) + * and `hooks` (a `z.custom()` branch) — and one unrepresentable member costs + * EVERY embedder its whole JSON Schema. That is why the installed-package read + * responses could only carry the body with both keys written `z.unknown()`: + * accepted without being checked. + * + * ⛔ The remedy is NOT narrowing the assembled body. Those callables are LIVE + * on the stage that schema declares itself for — `composeStacks(stacks, { + * manifest: 'preserve' })` builds exactly such a body and the load path + * registers it — so narrowing in place would refuse a published composition + * function's own output. The stages get their own declarations instead, and + * this file's job is to keep them from collapsing back into one. + * + * ## The four stages, and which two are new + * + * | stage | declaration | `functions` entry | + * |---|---|---| + * | authoring | `ManifestSchema` + the stack's collections | anything the author writes | + * | in-memory assembled | {@link AssembledPackageBodySchema} | a live callable, or lowered | + * | on-disk artifact | {@link ArtifactStagePackageBodySchema} | lowered ONLY | + * | registry record | {@link RecordStagePackageBodySchema} | lowered, `handler` optional | + * + * The last two are new. They differ from each other in exactly one thing — + * whether `handler` is required — because that is the whole distance between + * what `objectstack build` writes (it lowers every callable to a ref) and what + * `toRecordManifest` projects (it DROPS the callable and mints nothing in its + * place, which is correct: a ref minted anywhere but `build` is not guaranteed + * to be the ref `build` mints). + */ + +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; + +import { + ArtifactStagePackageBodySchema, + AssembledPackageBodySchema, + RecordStagePackageBodySchema, +} from './stack.zod'; +import * as Automation from './automation'; +import { FlowFunctionLoweredDeclarationSchema } from './automation/flow-function.zod'; + +/** Does this schema have a JSON Schema form at all? */ +const emits = (schema: unknown): boolean => { + try { + z.toJSONSchema(schema as never, { io: 'input' } as never); + return true; + } catch { + return false; + } +}; + +const shapeKeys = (schema: unknown): string[] => + Object.keys((schema as { shape: Record }).shape).sort(); + +const IDENTITY = { + id: 'com.example.stages', + name: 'Stages', + version: '1.0.0', + type: 'app' as const, + namespace: 'stages', +}; + +const liveCallable = () => 'ran'; + +/** What a `defineStack()` host hands the load path: the callable is still there. */ +const IN_MEMORY_BODY = { + ...IDENTITY, + functions: { + summarizeCompletedTask: liveCallable, + sweepProjectHealth: { handler: liveCallable, effect: 'writes' as const }, + }, + hooks: [{ name: 'on_insert', object: 'task', events: ['beforeInsert' as const], handler: liveCallable }], +}; + +/** What `objectstack build` writes into `dist/objectstack.json`: refs only. */ +const ARTIFACT_BODY = { + ...IDENTITY, + functions: { + summarizeCompletedTask: 'summarizeCompletedTask', + sweepProjectHealth: { handler: 'sweepProjectHealth', effect: 'writes' as const }, + }, + hooks: [{ name: 'on_insert', object: 'task', events: ['beforeInsert' as const], handler: 'on_insert' }], +}; + +/** + * What `toRecordManifest` leaves behind: each declaration MINUS its callable. + * The bare entry reaches this shape because `installPackage` normalises it to + * the declared form first — see `withDeclaredFunctionEntries` in + * `@objectstack/objectql`'s registry, which is what stops the record + * under-reporting a package's functions. + */ +const RECORD_BODY = { + ...IDENTITY, + functions: { + summarizeCompletedTask: { effect: 'pure' as const }, + sweepProjectHealth: { effect: 'writes' as const }, + }, + hooks: [{ name: 'on_insert', object: 'task', events: ['beforeInsert' as const] }], +}; + +describe('#17518 the two JSON stages CONVERT, which is the whole point of declaring them', () => { + it('both new bodies convert under `z.toJSONSchema` — over the WHOLE body, not two members', () => { + // Stated over the whole body on purpose: the tax this closes is paid by + // EMBEDDERS, and an embedder loses its JSON Schema to any one + // unrepresentable member anywhere beneath it. + expect(emits(ArtifactStagePackageBodySchema)).toBe(true); + expect(emits(RecordStagePackageBodySchema)).toBe(true); + }); + + it('CONTROL: the assembled body still does NOT convert, and that is correct', () => { + // Without this half the pin above could pass while measuring nothing. The + // assembled body keeps its callables because `composeStacks` really builds + // one; ⛔ it is not the schema to narrow. + expect(emits(AssembledPackageBodySchema)).toBe(false); + }); + + it('LIT and DARK controls on the probe itself', () => { + expect(emits(z.string())).toBe(true); + expect(emits(z.object({ a: z.function() as never }))).toBe(false); + }); + + it('the lowered declaration is REACHABLE from the `automation` namespace', () => { + // Step 1 of the ruling, and the reason + // `unemitted-schemas.baseline.json`'s entry for + // `Automation.FlowFunctionDeclarationSchema` can say the lowered record + // 「publishes normally」 without lying: `export *` only re-exports bindings + // that are already exported, so a module-local const was reachable by + // nobody. + expect(Object.keys(Automation)).toContain('FlowFunctionLoweredDeclarationSchema'); + expect(emits(FlowFunctionLoweredDeclarationSchema)).toBe(true); + }); +}); + +describe('#17518 the three stages have the SAME key set — they narrow, they do not drop', () => { + it('artifact and record carry every member the assembled body carries', () => { + const assembled = shapeKeys(AssembledPackageBodySchema); + expect(shapeKeys(ArtifactStagePackageBodySchema)).toEqual(assembled); + expect(shapeKeys(RecordStagePackageBodySchema)).toEqual(assembled); + // Anti-vacuity: the body really is the wide one, not an empty shape. + expect(assembled.length).toBeGreaterThan(40); + }); + + it('exactly `functions` and `hooks` are the members with no JSON form', () => { + // The set the two JSON stages have to re-declare is MEASURED, never + // hand-picked. A new collection with no JSON form reddens HERE, naming + // itself, instead of silently unpublishing the read-API responses. + const shape = (AssembledPackageBodySchema as unknown as { shape: Record }).shape; + expect(Object.keys(shape).filter((k) => !emits(shape[k])).sort()).toEqual(['functions', 'hooks']); + }); + + it('the JSON stages\' ARRAY member declares the same keys as the authoring one', () => { + // `functions`' array member is declared INLINE inside the assembled body's + // own shape, and narrowing it in place is the one thing this pair may not + // do — so the JSON stages transcribe it. This is the drift guard that + // transcription owes: a key added to the authoring array entry and not to + // the lowered one reddens by name. + const arrayEntryKeys = (body: unknown): string[] => { + const functions = (body as { shape: Record }).shape.functions; + const union = (functions as unknown as { def: { innerType: { def: { options: unknown[] } } } }).def.innerType; + const arrayMember = union.def.options.find( + (option) => (option as { def: { type: string } }).def.type === 'array', + ); + const element = (arrayMember as { def: { element: unknown } }).def.element; + return Object.keys((element as { shape: Record }).shape).sort(); + }; + const authored = arrayEntryKeys(AssembledPackageBodySchema); + expect(authored).toEqual(['effect', 'handler', 'name', 'packageId']); + expect(arrayEntryKeys(ArtifactStagePackageBodySchema)).toEqual(authored); + expect(arrayEntryKeys(RecordStagePackageBodySchema)).toEqual(authored); + }); +}); + +describe('#17518 each stage accepts ITS OWN payload and refuses the neighbouring ones', () => { + it('assembled accepts the live composed body; both JSON stages refuse it', () => { + expect(AssembledPackageBodySchema.safeParse(IN_MEMORY_BODY).success).toBe(true); + expect(ArtifactStagePackageBodySchema.safeParse(IN_MEMORY_BODY).success).toBe(false); + expect(RecordStagePackageBodySchema.safeParse(IN_MEMORY_BODY).success).toBe(false); + }); + + it('artifact accepts what `objectstack build` writes — BOTH lowered spellings', () => { + // `build` emits `{ myFn: 'myFn' }` for a bare entry and + // `{ myFn: { handler: 'myFn', effect } }` for a declared one, so a stage + // admitting only the record form would refuse artifacts this repo writes. + expect(ArtifactStagePackageBodySchema.safeParse(ARTIFACT_BODY).success).toBe(true); + expect(RecordStagePackageBodySchema.safeParse(ARTIFACT_BODY).success).toBe(true); + }); + + it('record accepts the handler-less declaration; ⛔ the ARTIFACT stage refuses it', () => { + // This asymmetry IS the fourth stage. An artifact always has a ref, because + // `build` mints one; a record never does, because the projection drops the + // callable and the registry ⛔ mints nothing. + expect(RecordStagePackageBodySchema.safeParse(RECORD_BODY).success).toBe(true); + expect(ArtifactStagePackageBodySchema.safeParse(RECORD_BODY).success).toBe(false); + }); + + it('⛔ neither JSON stage became tolerant — globs and unknown keys are still refused', () => { + // The two keys moved from `unknown` (accepts anything) to a declaration. + // ⛔ Nothing else moved, and this is where a future widening reddens. + for (const schema of [ArtifactStagePackageBodySchema, RecordStagePackageBodySchema]) { + expect(schema.safeParse({ ...IDENTITY, objects: ['./src/*.object.yml'] }).success).toBe(false); + expect(schema.safeParse({ ...IDENTITY, namesapce: 'typo' }).success).toBe(false); + expect(schema.safeParse({ ...IDENTITY, functions: { f: liveCallable } }).success).toBe(false); + expect(schema.safeParse({ ...IDENTITY, hooks: [{ name: 'h', object: 'task', events: ['beforeInsert'], handler: liveCallable }] }).success).toBe(false); + } + }); +}); + +describe('#17518 `effect` is READ off the declaration schema, never minted here', () => { + it('a lowered entry with no `effect` materialises the declaration default', () => { + // `FlowFunctionDeclarationSchema.effect` is + // `FlowFunctionEffectSchema.default(DEFAULT_FLOW_FUNCTION_EFFECT)` — a + // DEFAULT, not a requirement — and both JSON stages inherit that by + // deriving from it rather than restating it. + const parsed = ArtifactStagePackageBodySchema.parse({ + ...IDENTITY, + functions: { probeSweep: { handler: 'probeSweep' } }, + }) as { functions: Record }; + expect(parsed.functions.probeSweep.effect).toBe('pure'); + }); + + it('the ARRAY member states no default, so nothing is written where none is declared', () => { + const parsed = ArtifactStagePackageBodySchema.parse({ + ...IDENTITY, + functions: [{ name: 'probeSweep', handler: 'probeSweep' }], + }) as { functions: Array> }; + expect('effect' in parsed.functions[0]).toBe(false); + }); + + it('a misspelled `effect` is still refused by name on the lowered record', () => { + // The strictness travels with the derivation: the lowered form is + // `FlowFunctionDeclarationSchema.extend(...)`, so it keeps the named + // surface and the `` `efect` → `effect` `` prescription. + const verdict = ArtifactStagePackageBodySchema.safeParse({ + ...IDENTITY, + functions: { probeSweep: { handler: 'probeSweep', efect: 'writes' } }, + }); + expect(verdict.success).toBe(false); + }); +}); From f6885ddeee75dc5919e24ae92ea7796ca6bdf898 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 12:27:59 +0000 Subject: [PATCH 04/11] chore(spec): regenerate the spec surface artifacts; name the lowered declaration's type aliases Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- content/docs/references/api/package-api.mdx | 14 ++++---- .../references/automation/flow-function.mdx | 16 ++++++++- content/docs/references/index.mdx | 10 +++--- .../spec/api-surface-declarations/api.txt | 36 ++++--------------- .../api-surface-declarations/automation.txt | 13 +++++-- .../spec/api-surface-declarations/root.txt | 22 ++++++++++-- packages/spec/api-surface/automation.json | 1 + packages/spec/api-surface/root.json | 6 ++++ packages/spec/declaration-map/automation.json | 2 ++ packages/spec/export-origins/automation.json | 1 + packages/spec/export-origins/root.json | 6 ++++ .../spec/src/automation/flow-function.zod.ts | 4 +++ .../objectstack-platform/references/_index.md | 2 +- 13 files changed, 85 insertions(+), 48 deletions(-) diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index 6d6b0f0a7ac..34144b2f112 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -47,7 +47,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -89,7 +89,7 @@ Installed package row whose manifest is the assembled package body | **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) | | **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) | | **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) | -| **functions** | `any` | optional | Named handler functions, as they survived the record JSON projection | +| **functions** | `Record }> \| { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' \| 'writes'> }[]` | optional | Named handler functions, lowered to the refs a JSON document carries | | **datasourceMapping** | `{ namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[]` | optional | Centralized datasource routing rules for packages/namespaces/objects | | **translations** | `Record; apps?: Record; messages?: Record; globalActions?: Record; … }>[]` | optional | I18n Translation Bundles | | **objectExtensions** | `{ extend: string; fields?: Record; label?: string; pluralLabel?: string; … }[]` | optional | Extensions to objects owned by other packages | @@ -113,7 +113,7 @@ Installed package row whose manifest is the assembled package body | **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents | | **tools** | `{ name: string; label: string; description: string; parameters: Record; … }[]` | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_`` tools (ADR-0109) | | **skills** | `{ name: string; label: string; description?: string; surface?: Enum<'ask' \| 'build' \| 'both'>; … }[]` | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) | -| **hooks** | `any` | optional | Object lifecycle hooks, as they survived the record JSON projection | +| **hooks** | `{ name: string; label?: string; object: string \| string[]; events: Enum<'beforeFind' \| 'afterFind' \| 'beforeInsert' \| 'afterInsert' \| 'beforeUpdate' \| …>[]; … }[]` | optional | Object Lifecycle Hooks, as a JSON document carries them | | **mappings** | `{ name: string; label?: string; sourceFormat?: Enum<'csv' \| 'json' \| 'xml' \| 'sql'>; targetObject: string; … }[]` | optional | Data Import/Export Mappings | | **analyticsCubes** | `{ name: string; title?: string; description?: string; sql: string; … }[]` | optional | Analytics Semantic Layer Cubes | | **connectors** | `{ name: string; label: string; type: Enum<'saas' \| 'database' \| 'file_storage' \| 'message_queue' \| 'api' \| 'custom'>; description?: string; … }[]` | optional | External System Connectors. A provider-bound entry (has `provider`: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via `connector_action`; credentials are `auth.credentialRef` references, never inline secrets. An entry with no `provider` is a catalog descriptor only (NOT dispatchable) — set `enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0097). | @@ -205,7 +205,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -300,7 +300,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -342,7 +342,7 @@ Installed package row whose manifest is the assembled package body | **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) | | **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) | | **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) | -| **functions** | `any` | optional | Named handler functions, as they survived the record JSON projection | +| **functions** | `Record }> \| { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' \| 'writes'> }[]` | optional | Named handler functions, lowered to the refs a JSON document carries | | **datasourceMapping** | `{ namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[]` | optional | Centralized datasource routing rules for packages/namespaces/objects | | **translations** | `Record; apps?: Record; messages?: Record; globalActions?: Record; … }>[]` | optional | I18n Translation Bundles | | **objectExtensions** | `{ extend: string; fields?: Record; label?: string; pluralLabel?: string; … }[]` | optional | Extensions to objects owned by other packages | @@ -366,7 +366,7 @@ Installed package row whose manifest is the assembled package body | **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents | | **tools** | `{ name: string; label: string; description: string; parameters: Record; … }[]` | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_`` tools (ADR-0109) | | **skills** | `{ name: string; label: string; description?: string; surface?: Enum<'ask' \| 'build' \| 'both'>; … }[]` | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) | -| **hooks** | `any` | optional | Object lifecycle hooks, as they survived the record JSON projection | +| **hooks** | `{ name: string; label?: string; object: string \| string[]; events: Enum<'beforeFind' \| 'afterFind' \| 'beforeInsert' \| 'afterInsert' \| 'beforeUpdate' \| …>[]; … }[]` | optional | Object Lifecycle Hooks, as a JSON document carries them | | **mappings** | `{ name: string; label?: string; sourceFormat?: Enum<'csv' \| 'json' \| 'xml' \| 'sql'>; targetObject: string; … }[]` | optional | Data Import/Export Mappings | | **analyticsCubes** | `{ name: string; title?: string; description?: string; sql: string; … }[]` | optional | Analytics Semantic Layer Cubes | | **connectors** | `{ name: string; label: string; type: Enum<'saas' \| 'database' \| 'file_storage' \| 'message_queue' \| 'api' \| 'custom'>; description?: string; … }[]` | optional | External System Connectors. A provider-bound entry (has `provider`: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via `connector_action`; credentials are `auth.credentialRef` references, never inline secrets. An entry with no `provider` is a catalog descriptor only (NOT dispatchable) — set `enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0097). | diff --git a/content/docs/references/automation/flow-function.mdx b/content/docs/references/automation/flow-function.mdx index 83f1451ed5b..73cc6ff44bb 100644 --- a/content/docs/references/automation/flow-function.mdx +++ b/content/docs/references/automation/flow-function.mdx @@ -65,7 +65,7 @@ the platform's own counters stop being wrong for it. ## TypeScript Usage ```typescript -import { FlowFunctionEffectSchema } from '@objectstack/spec/automation'; +import { FlowFunctionEffectSchema, FlowFunctionLoweredDeclarationSchema } from '@objectstack/spec/automation'; import type { FlowFunctionEffect } from '@objectstack/spec/automation'; // Validate data @@ -86,3 +86,17 @@ What a script-node function does to data: 'pure' (computes and returns — the c --- +## FlowFunctionLoweredDeclaration + +A lowered `functions` declaration: what the function declared about itself, with its callable replaced by a handler ref + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **handler** | `string` | ✅ | The lowered handler ref (built artifacts) — the callable rides in the sibling ESM module | +| **effect** | `Enum<'pure' \| 'writes'>` | optional (default: `"pure"`) | What the function does to data — omit for the pure default | + + +--- + diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 3913e991867..7c64230db85 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1532 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1533 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | | [API Protocol](/docs/references/api) | 31 | 441 | REST contracts, endpoints, routing, realtime, batch, discovery. | -| [Automation Protocol](/docs/references/automation) | 14 | 74 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | +| [Automation Protocol](/docs/references/automation) | 14 | 75 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Data Protocol](/docs/references/data) | 29 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | | [Integration Protocol](/docs/references/integration) | 1 | 24 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 157 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **195** | **1532** | 14 protocol modules | +| **Total** | **195** | **1533** | 14 protocol modules | --- @@ -104,7 +104,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. ## Automation Protocol -**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 74 schemas** +**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 75 schemas** Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. @@ -116,7 +116,7 @@ Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execu | [`control-flow.zod.ts`](/docs/references/automation/control-flow) | `FlowRegion`, `LoopConfig`, `ParallelBranch`, `ParallelConfig`, `RetryPolicy`, `TryCatchConfig`, `TryCatchErrorValue` | | [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary`, `ScheduleState` | | [`flow.zod.ts`](/docs/references/automation/flow) | `Flow`, `FlowEdge`, `FlowNode`, `FlowNodeAction`, `FlowVariable`, `FlowVersionHistory` | -| [`flow-function.zod.ts`](/docs/references/automation/flow-function) | `FlowFunctionEffect` | +| [`flow-function.zod.ts`](/docs/references/automation/flow-function) | `FlowFunctionEffect`, `FlowFunctionLoweredDeclaration` | | [`io-node-config.zod.ts`](/docs/references/automation/io-node-config) | `HttpConfig`, `NotifyConfig` | | [`node-executor.zod.ts`](/docs/references/automation/node-executor) | `ActionCategory`, `ActionDescriptor`, `ActionParadigm`, `NodeExecutorDescriptor`, `WaitEventType`, `WaitExecutorConfig`, `WaitResumePayload`, `WaitTimeoutBehavior` | | [`schedule-organization.zod.ts`](/docs/references/automation/schedule-organization) | `ScheduleOrganization` | diff --git a/packages/spec/api-surface-declarations/api.txt b/packages/spec/api-surface-declarations/api.txt index 9347bcb8083..2dff8fd6ea6 100644 --- a/packages/spec/api-surface-declarations/api.txt +++ b/packages/spec/api-surface-declarations/api.txt @@ -1471,11 +1471,7 @@ declare const AssembledInstalledPackageSchema: z.ZodObject<{ migrationLog: z.ZodOptional>; }, z.core.$strip>>>; registeredNamespaces: z.ZodOptional>; - manifest: z.ZodObject<{ - readonly [x: string]: z.core.$ZodType>; - functions: z.ZodOptional; - hooks: z.ZodOptional; - }, z.core.$strip>; + manifest: z.ZodType, Record, z.core.$ZodTypeInternals, Record>>; }, z.core.$strip>; // ── AuditMetaItemRequest (type) ── @@ -8813,11 +8809,7 @@ declare const GetInstalledPackageResponseSchema: z.ZodObject<{ migrationLog: z.ZodOptional>; }, z.core.$strip>>>; registeredNamespaces: z.ZodOptional>; - manifest: z.ZodObject<{ - readonly [x: string]: z.core.$ZodType>; - functions: z.ZodOptional; - hooks: z.ZodOptional; - }, z.core.$strip>; + manifest: z.ZodType, Record, z.core.$ZodTypeInternals, Record>>; }, z.core.$strip>]>; }, z.core.$strip>; @@ -13548,11 +13540,7 @@ declare const InstalledPackageAtEitherStageSchema: z.ZodUnion>; }, z.core.$strip>>>; registeredNamespaces: z.ZodOptional>; - manifest: z.ZodObject<{ - readonly [x: string]: z.core.$ZodType>; - functions: z.ZodOptional; - hooks: z.ZodOptional; - }, z.core.$strip>; + manifest: z.ZodType, Record, z.core.$ZodTypeInternals, Record>>; }, z.core.$strip>]>; // ── ListAiConversationsRequest (type) ── @@ -14073,11 +14061,7 @@ declare const ListInstalledPackagesResponseSchema: z.ZodObject<{ migrationLog: z.ZodOptional>; }, z.core.$strip>>>; registeredNamespaces: z.ZodOptional>; - manifest: z.ZodObject<{ - readonly [x: string]: z.core.$ZodType>; - functions: z.ZodOptional; - hooks: z.ZodOptional; - }, z.core.$strip>; + manifest: z.ZodType, Record, z.core.$ZodTypeInternals, Record>>; }, z.core.$strip>]>>; total: z.ZodOptional; nextCursor: z.ZodOptional; @@ -27209,11 +27193,7 @@ declare const PackageApiContracts: { migrationLog: z.ZodOptional>; }, z.core.$strip>>>; registeredNamespaces: z.ZodOptional>; - manifest: z.ZodObject<{ - readonly [x: string]: z.core.$ZodType>; - functions: z.ZodOptional; - hooks: z.ZodOptional; - }, z.core.$strip>; + manifest: z.ZodType, Record, z.core.$ZodTypeInternals, Record>>; }, z.core.$strip>]>>; total: z.ZodOptional; nextCursor: z.ZodOptional; @@ -27422,11 +27402,7 @@ declare const PackageApiContracts: { migrationLog: z.ZodOptional>; }, z.core.$strip>>>; registeredNamespaces: z.ZodOptional>; - manifest: z.ZodObject<{ - readonly [x: string]: z.core.$ZodType>; - functions: z.ZodOptional; - hooks: z.ZodOptional; - }, z.core.$strip>; + manifest: z.ZodType, Record, z.core.$ZodTypeInternals, Record>>; }, z.core.$strip>]>; }, z.core.$strip>; }; diff --git a/packages/spec/api-surface-declarations/automation.txt b/packages/spec/api-surface-declarations/automation.txt index 31cf33027d7..4bfc112b880 100644 --- a/packages/spec/api-surface-declarations/automation.txt +++ b/packages/spec/api-surface-declarations/automation.txt @@ -12,8 +12,8 @@ # excluded: documentation drift is `check:docs`'s axis, not this one. # # entry: ./automation -# exported names: 274 -# declarations: 280 +# exported names: 275 +# declarations: 281 # # GENERATED — ⛔ never hand-edited. Regenerate after a real build: # pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec gen:api-surface-declarations @@ -1099,6 +1099,15 @@ declare const FlowFunctionEntrySchema: z.ZodUnion]>; +// ── FlowFunctionLoweredDeclarationSchema (const) ── +declare const FlowFunctionLoweredDeclarationSchema: z.ZodObject<{ + effect: z.ZodDefault>; + handler: z.ZodString; +}, z.core.$strict>; + // ── FlowGraph (interface) ── interface FlowGraph { /** diff --git a/packages/spec/api-surface-declarations/root.txt b/packages/spec/api-surface-declarations/root.txt index 9fb0bf0687f..f74a0b6e44e 100644 --- a/packages/spec/api-surface-declarations/root.txt +++ b/packages/spec/api-surface-declarations/root.txt @@ -12,8 +12,8 @@ # excluded: documentation drift is `check:docs`'s axis, not this one. # # entry: . -# exported names: 219 -# declarations: 220 +# exported names: 225 +# declarations: 226 # # GENERATED — ⛔ never hand-edited. Regenerate after a real build: # pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec gen:api-surface-declarations @@ -256,6 +256,15 @@ declare const ArtifactPackageSchema: z.ZodObject<{ manifest: z.ZodType, Record, z.core.$ZodTypeInternals, Record>>; }, z.core.$strict>; +// ── ArtifactStagePackageBody (type) ── +type ArtifactStagePackageBody = z.input; + +// ── ArtifactStagePackageBodyParsed (type) ── +type ArtifactStagePackageBodyParsed = z.infer; + +// ── ArtifactStagePackageBodySchema (const) ── +declare const ArtifactStagePackageBodySchema: z.ZodType, Record>; + // ── AssembledPackageBody (type) ── type AssembledPackageBody = z.input; @@ -44927,6 +44936,15 @@ declare const RETIRED_DEFS_BY_MAJOR: Readonly> // ── RETIRED_KEYS_BY_MAJOR (const) ── declare const RETIRED_KEYS_BY_MAJOR: Readonly>; +// ── RecordStagePackageBody (type) ── +type RecordStagePackageBody = z.input; + +// ── RecordStagePackageBodyParsed (type) ── +type RecordStagePackageBodyParsed = z.infer; + +// ── RecordStagePackageBodySchema (const) ── +declare const RecordStagePackageBodySchema: z.ZodType, Record>; + // ── ReleaseSurfaceDiff (interface) ── interface ReleaseSurfaceDiff { added: readonly string[]; diff --git a/packages/spec/api-surface/automation.json b/packages/spec/api-surface/automation.json index 6189e5214c2..a33da0bbcff 100644 --- a/packages/spec/api-surface/automation.json +++ b/packages/spec/api-surface/automation.json @@ -130,6 +130,7 @@ "FlowFunctionEntry (type)", "FlowFunctionEntryParsed (type)", "FlowFunctionEntrySchema (const)", + "FlowFunctionLoweredDeclarationSchema (const)", "FlowGraph (interface)", "FlowNode (type)", "FlowNodeAction (const)", diff --git a/packages/spec/api-surface/root.json b/packages/spec/api-surface/root.json index d0a072af865..63797641405 100644 --- a/packages/spec/api-surface/root.json +++ b/packages/spec/api-surface/root.json @@ -15,6 +15,9 @@ "ArtifactPackageEntrySchema (const)", "ArtifactPackageParsed (type)", "ArtifactPackageSchema (const)", + "ArtifactStagePackageBody (type)", + "ArtifactStagePackageBodyParsed (type)", + "ArtifactStagePackageBodySchema (const)", "AssembledPackageBody (type)", "AssembledPackageBodyParsed (type)", "AssembledPackageBodySchema (const)", @@ -130,6 +133,9 @@ "PreviousReleaseRegistries (interface)", "RETIRED_DEFS_BY_MAJOR (const)", "RETIRED_KEYS_BY_MAJOR (const)", + "RecordStagePackageBody (type)", + "RecordStagePackageBodyParsed (type)", + "RecordStagePackageBodySchema (const)", "ReleaseSurfaceDiff (interface)", "STACK_DEFINITION_KEYS (const)", "STACK_KEY_GUIDANCE (const)", diff --git a/packages/spec/declaration-map/automation.json b/packages/spec/declaration-map/automation.json index 7436be618c4..654f1bba43a 100644 --- a/packages/spec/declaration-map/automation.json +++ b/packages/spec/declaration-map/automation.json @@ -69,8 +69,10 @@ "Flow": "automation/Flow", "FlowEdge": "automation/FlowEdge", "FlowEdgeSchema": "automation/FlowEdge", + "FlowFunctionDeclarationSchema": "automation/FlowFunctionLoweredDeclaration", "FlowFunctionEffect": "automation/FlowFunctionEffect", "FlowFunctionEffectSchema": "automation/FlowFunctionEffect", + "FlowFunctionLoweredDeclarationSchema": "automation/FlowFunctionLoweredDeclaration", "FlowNode": "automation/FlowNode", "FlowNodeAction": "automation/FlowNodeAction", "FlowNodeSchema": "automation/FlowNode", diff --git a/packages/spec/export-origins/automation.json b/packages/spec/export-origins/automation.json index 1eaea1c7be5..aa6b0185a9d 100644 --- a/packages/spec/export-origins/automation.json +++ b/packages/spec/export-origins/automation.json @@ -126,6 +126,7 @@ "FlowFunctionEntry": "src/automation/flow-function.zod.ts#FlowFunctionEntry (type)", "FlowFunctionEntryParsed": "src/automation/flow-function.zod.ts#FlowFunctionEntryParsed (type)", "FlowFunctionEntrySchema": "src/automation/flow-function.zod.ts#FlowFunctionEntrySchema (const)", + "FlowFunctionLoweredDeclarationSchema": "src/automation/flow-function.zod.ts#FlowFunctionLoweredDeclarationSchema (const)", "FlowGraph": "src/automation/control-flow.zod.ts#FlowGraph (interface)", "FlowNode": "src/automation/flow.zod.ts#FlowNode (type)", "FlowNodeAction": "src/automation/flow.zod.ts#FlowNodeAction (type)", diff --git a/packages/spec/export-origins/root.json b/packages/spec/export-origins/root.json index aae573e5662..206c67586bd 100644 --- a/packages/spec/export-origins/root.json +++ b/packages/spec/export-origins/root.json @@ -15,6 +15,9 @@ "ArtifactPackageEntrySchema": "src/stack.zod.ts#ArtifactPackageEntrySchema (const)", "ArtifactPackageParsed": "src/stack.zod.ts#ArtifactPackageParsed (type)", "ArtifactPackageSchema": "src/stack.zod.ts#ArtifactPackageSchema (const)", + "ArtifactStagePackageBody": "src/stack.zod.ts#ArtifactStagePackageBody (type)", + "ArtifactStagePackageBodyParsed": "src/stack.zod.ts#ArtifactStagePackageBodyParsed (type)", + "ArtifactStagePackageBodySchema": "src/stack.zod.ts#ArtifactStagePackageBodySchema (const)", "AssembledPackageBody": "src/stack.zod.ts#AssembledPackageBody (type)", "AssembledPackageBodyParsed": "src/stack.zod.ts#AssembledPackageBodyParsed (type)", "AssembledPackageBodySchema": "src/stack.zod.ts#AssembledPackageBodySchema (const)", @@ -129,6 +132,9 @@ "PreviousReleaseRegistries": "src/migrations/spec-changes.ts#PreviousReleaseRegistries (interface)", "RETIRED_DEFS_BY_MAJOR": "src/migrations/registry.ts#RETIRED_DEFS_BY_MAJOR (const)", "RETIRED_KEYS_BY_MAJOR": "src/migrations/registry.ts#RETIRED_KEYS_BY_MAJOR (const)", + "RecordStagePackageBody": "src/stack.zod.ts#RecordStagePackageBody (type)", + "RecordStagePackageBodyParsed": "src/stack.zod.ts#RecordStagePackageBodyParsed (type)", + "RecordStagePackageBodySchema": "src/stack.zod.ts#RecordStagePackageBodySchema (const)", "ReleaseSurfaceDiff": "src/migrations/spec-changes.ts#ReleaseSurfaceDiff (interface)", "STACK_DEFINITION_KEYS": "src/stack.zod.ts#STACK_DEFINITION_KEYS (const)", "STACK_KEY_GUIDANCE": "src/data/authoring-key-lint.ts#STACK_KEY_GUIDANCE (const)", diff --git a/packages/spec/src/automation/flow-function.zod.ts b/packages/spec/src/automation/flow-function.zod.ts index e2b5a29755e..11f8f16f8d5 100644 --- a/packages/spec/src/automation/flow-function.zod.ts +++ b/packages/spec/src/automation/flow-function.zod.ts @@ -222,6 +222,10 @@ export const FlowFunctionLoweredDeclarationSchema = lazySchema(() => FlowFunctio .describe('The lowered handler ref (built artifacts) — the callable rides in the sibling ESM module'), }).describe('A lowered `functions` declaration: what the function declared about itself, with its callable replaced by a handler ref')); +export type FlowFunctionLoweredDeclaration = z.input; +/** Post-parse shape of {@link FlowFunctionLoweredDeclaration} — defaults applied, transforms run (ADR-0122). */ +export type FlowFunctionLoweredDeclarationParsed = z.infer; + /** * One entry of the `functions` map, in the four shapes it legitimately takes: * the handler alone (pure), a {@link FlowFunctionDeclarationSchema} that states diff --git a/skills/objectstack-platform/references/_index.md b/skills/objectstack-platform/references/_index.md index 02b7c7bb756..5db87a4239d 100644 --- a/skills/objectstack-platform/references/_index.md +++ b/skills/objectstack-platform/references/_index.md @@ -18,7 +18,7 @@ from `node_modules` — there is no local copy in the skill bundle. - `node_modules/@objectstack/spec/src/kernel/plugin-loading.zod.ts` — Plugin Loading Protocol - `node_modules/@objectstack/spec/src/kernel/plugin.zod.ts` — Exports: PluginContextSchema, PluginSchema - `node_modules/@objectstack/spec/src/kernel/service-registry.zod.ts` — Service Registry Protocol -- `node_modules/@objectstack/spec/src/stack.zod.ts` — Exports: DatasourceMappingRuleSchema, ArtifactPackageEntrySchema, AssembledPackageBodySchema, ArtifactPackageSchema, ObjectStackDefinitionSchema +- `node_modules/@objectstack/spec/src/stack.zod.ts` — Exports: DatasourceMappingRuleSchema, ArtifactPackageEntrySchema, AssembledPackageBodySchema, ArtifactStagePackageBodySchema, RecordStagePackageBodySchema ## Transitive dependencies From ed6b986c7e99383c09832bd2b4d695076c5198e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 12:36:47 +0000 Subject: [PATCH 05/11] chore(changeset): declare the spec and objectql halves of the inert-JSON stage work Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- .../17518-inert-json-package-body-stages.md | 14 + ...-registry-record-reports-every-function.md | 12 + .../references/automation/flow-function.mdx | 2 +- .../api-surface-declarations/automation.txt | 10 +- packages/spec/api-surface-declarations/ui.txt | 368 +++++++++--------- packages/spec/api-surface/automation.json | 2 + packages/spec/declaration-map/automation.json | 1 + packages/spec/export-origins/automation.json | 2 + 8 files changed, 224 insertions(+), 187 deletions(-) create mode 100644 .changeset/17518-inert-json-package-body-stages.md create mode 100644 .changeset/17518-registry-record-reports-every-function.md diff --git a/.changeset/17518-inert-json-package-body-stages.md b/.changeset/17518-inert-json-package-body-stages.md new file mode 100644 index 00000000000..5ce069b34f8 --- /dev/null +++ b/.changeset/17518-inert-json-package-body-stages.md @@ -0,0 +1,14 @@ +--- +"@objectstack/spec": minor +--- + +A package body now has a declaration at every stage it really passes through: `ArtifactStagePackageBodySchema` and `RecordStagePackageBodySchema` join `AssembledPackageBodySchema`, and the installed-package read rows are declared against the record stage instead of two `z.unknown()` holes (#17518). + +ADR-0130 D4 says an artifact is inert JSON — "a plugin written inside `packages[i].manifest` could never be constructed by a loader, so a reader that resolved it there would register garbage where it used to skip in silence". Of `AssembledPackageBodySchema`'s 55 members exactly two declare that they accept a callable: `functions`, whose entry union opens with `z.function()`, and `hooks`, whose `handler` carries a `z.custom()` branch. One unrepresentable member costs every embedder its whole JSON Schema, which is why `api/ListInstalledPackagesResponse` and `api/GetInstalledPackageResponse` could only carry the body with both keys written `z.unknown().optional()` — accepted without being checked, as that file's own docblock said. + +- **⛔ The assembled body is untouched, and that is the point.** Those callables are LIVE on the stage it declares itself for: `composeStacks(stacks, { manifest: 'preserve' })` builds exactly such a body and the load path registers it, and `stack.zod.ts` states the invariant that binds the two. Narrowing in place would refuse a published composition function's own output. The two JSON stages are declared BESIDE it instead. +- **Artifact stage** — what `objectstack build` writes: `functions` entries are the lowered spellings (a bare handler ref, or `FlowFunctionLoweredDeclarationSchema`), `hooks[].handler` is a string. **Record stage** — what `SchemaRegistry.installPackage` stores: the artifact stage with `functions[].handler` OPTIONAL, in both the map-record and the array form. That single difference is the whole distance between the two: `build` mints a ref for every callable, while `toRecordManifest`'s structural projection DROPS the callable and mints nothing in its place, so a record states what each function is named and what it declared with `handler` absent where the callable was. Measured: both bodies convert under `z.toJSONSchema` over the whole body, where the assembled body still does not. +- **`FlowFunctionLoweredDeclarationSchema` is exported** from `@objectstack/spec/automation`, with its `FlowFunctionLoweredDeclaration` / `…Parsed` aliases. It was a module-local `const`, and `export * from './flow-function.zod'` only re-exports what is already exported — so `unemitted-schemas.baseline.json`'s reason for `Automation.FlowFunctionDeclarationSchema`, which says the lowered record "is the serialisable half … and it publishes normally", pointed at a schema no consumer could reach. It publishes now: `automation/FlowFunctionLoweredDeclaration` is in the schema manifest. +- **`effect` is READ, not minted.** `FlowFunctionDeclarationSchema.effect` is `FlowFunctionEffectSchema.default('pure')` — a default, not a requirement — and the array member's is `.optional()` with no default. Both JSON stages inherit each form's optionality by deriving from it rather than restating it. +- **⚠️ What narrows, stated plainly**: on the two installed-package responses, `functions` and `hooks` move from `unknown` (accepts anything) to their declared JSON shapes. No row the doors really serve is withdrawn — measured through the real `SchemaRegistry.installPackage` on the shape `examples/app-showcase` ships, on the array form, and on the already-lowered body an artifact boot installs. Every other key, `objects` included, is checked exactly as before, and both stages still refuse an authoring glob and an unknown key. +- One correction in the same edit: `package-api.zod.ts` said those two members were also why `ArtifactPackageSchema` and `ObjectStackDefinitionSchema` publish no JSON Schema. They are not — `src/stack.zod.ts` is not one of the subpath namespaces `build-schemas.ts` walks, so neither is ever reached by the emit loop. diff --git a/.changeset/17518-registry-record-reports-every-function.md b/.changeset/17518-registry-record-reports-every-function.md new file mode 100644 index 00000000000..1f532c9932d --- /dev/null +++ b/.changeset/17518-registry-record-reports-every-function.md @@ -0,0 +1,12 @@ +--- +"@objectstack/objectql": patch +--- + +`GET /packages` reports every function a package declares. A bare callable `functions` entry is normalised to the declared form at the assembly boundary, so the registry record no longer drops it (#17518). + +`SchemaRegistry.installPackage` stores `toRecordManifest(manifest)`, a structural JSON projection whose rule is "a live object reached the record" and deliberately ⛔ not a key denylist. That rule treated the two authored `functions` spellings unequally through no fault of its own: a DECLARED entry (`{ handler, effect: 'writes' }`) is a plain object, so it survived with its callable dropped, while a BARE callable entry IS the callable, so the whole key vanished. `examples/app-showcase` ships one of each, so a package declaring two functions was reported as declaring one — a machine-readable read door under-reporting by construction. + +- **The repair is at the assembly boundary, ⛔ not in the projection.** `installPackage` makes the two spellings structurally equal before projecting, so the structural rule is untouched and no key name is special-cased. The projection then leaves `{ effect }` for both. +- **⛔ No ref is minted.** `objectstack build` mints refs with `uniqueName(base, taken)` and dedupes by function identity, so a ref minted in the registry is not guaranteed to be the one `build` mints — a record could assert a handler that resolves in no sibling module. An absent `handler` is the honest statement "declared here, not serialisable", which is exactly what `@objectstack/spec`'s new `RecordStagePackageBodySchema` declares. +- **⛔ No entry is dropped**, either: under-reporting by design was the other arm, and it also throws away the `effect` declaration, the one half that survived. +- The caller's manifest is never mutated — `ObjectQL.registerApp` and the hook binder read the live callables off that object — and a copy is made only when an entry really needed rewriting. The ARRAY form is untouched: its entries are objects carrying their own `name`, so the projection already kept them. diff --git a/content/docs/references/automation/flow-function.mdx b/content/docs/references/automation/flow-function.mdx index 73cc6ff44bb..5159d97a622 100644 --- a/content/docs/references/automation/flow-function.mdx +++ b/content/docs/references/automation/flow-function.mdx @@ -66,7 +66,7 @@ the platform's own counters stop being wrong for it. ```typescript import { FlowFunctionEffectSchema, FlowFunctionLoweredDeclarationSchema } from '@objectstack/spec/automation'; -import type { FlowFunctionEffect } from '@objectstack/spec/automation'; +import type { FlowFunctionEffect, FlowFunctionLoweredDeclaration } from '@objectstack/spec/automation'; // Validate data const result = FlowFunctionEffectSchema.parse(data); diff --git a/packages/spec/api-surface-declarations/automation.txt b/packages/spec/api-surface-declarations/automation.txt index 4bfc112b880..5adc9abbb93 100644 --- a/packages/spec/api-surface-declarations/automation.txt +++ b/packages/spec/api-surface-declarations/automation.txt @@ -12,8 +12,8 @@ # excluded: documentation drift is `check:docs`'s axis, not this one. # # entry: ./automation -# exported names: 275 -# declarations: 281 +# exported names: 277 +# declarations: 283 # # GENERATED — ⛔ never hand-edited. Regenerate after a real build: # pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec gen:api-surface-declarations @@ -1099,6 +1099,12 @@ declare const FlowFunctionEntrySchema: z.ZodUnion]>; +// ── FlowFunctionLoweredDeclaration (type) ── +type FlowFunctionLoweredDeclaration = z.input; + +// ── FlowFunctionLoweredDeclarationParsed (type) ── +type FlowFunctionLoweredDeclarationParsed = z.infer; + // ── FlowFunctionLoweredDeclarationSchema (const) ── declare const FlowFunctionLoweredDeclarationSchema: z.ZodObject<{ effect: z.ZodDefault>; agentId: z.ZodOptional; context: z.ZodOptional>; @@ -2929,8 +2929,8 @@ declare const ComponentPropsMap: { }>>; type: z.ZodOptional; position: z.ZodDefault>; alwaysShowStrip: z.ZodOptional; items: z.ZodArray>; allowMultiple: z.ZodDefault; variant: z.ZodDefault>; aria: z.ZodOptional & { @@ -3179,10 +3179,10 @@ declare const ComponentPropsMap: { right: "right"; }>>; summary: z.ZodOptional, z.ZodObject<{ type: z.ZodEnum<{ - count: "count"; none: "none"; min: "min"; max: "max"; + count: "count"; sum: "sum"; avg: "avg"; count_empty: "count_empty"; @@ -3368,14 +3368,14 @@ declare const ComponentPropsMap: { file: "file"; email: "email"; system: "system"; - note: "note"; sharing: "sharing"; approval: "approval"; - event: "event"; comment: "comment"; field_change: "field_change"; task: "task"; + event: "event"; call: "call"; + note: "note"; record_create: "record_create"; record_delete: "record_delete"; }>, z.ZodString]>>>; @@ -3426,14 +3426,14 @@ declare const ComponentPropsMap: { file: "file"; email: "email"; system: "system"; - note: "note"; sharing: "sharing"; approval: "approval"; - event: "event"; comment: "comment"; field_change: "field_change"; task: "task"; + event: "event"; call: "call"; + note: "note"; record_create: "record_create"; record_delete: "record_delete"; }>, z.ZodString]>>>; @@ -3502,14 +3502,14 @@ declare const ComponentPropsMap: { file: "file"; email: "email"; system: "system"; - note: "note"; sharing: "sharing"; approval: "approval"; - event: "event"; comment: "comment"; field_change: "field_change"; task: "task"; + event: "event"; call: "call"; + note: "note"; record_create: "record_create"; record_delete: "record_delete"; }>, z.ZodString]>>>; @@ -3618,8 +3618,8 @@ declare const ComponentPropsMap: { severity: z.ZodOptional>; title: z.ZodOptional & { key?: never; @@ -3688,9 +3688,9 @@ declare const ComponentPropsMap: { default: "default"; link: "link"; secondary: "secondary"; - ghost: "ghost"; - outline: "outline"; destructive: "destructive"; + outline: "outline"; + ghost: "ghost"; }>>; }, z.core.$strict>>; dismissible: z.ZodOptional; @@ -3717,15 +3717,15 @@ declare const ComponentPropsMap: { default: "default"; link: "link"; secondary: "secondary"; - ghost: "ghost"; - outline: "outline"; destructive: "destructive"; + outline: "outline"; + ghost: "ghost"; }>>; size: z.ZodOptional>; }, z.core.$strict>; readonly 'record:history': z.ZodObject<{ @@ -3745,8 +3745,8 @@ declare const ComponentPropsMap: { readonly 'ai:chat_window': z.ZodObject<{ mode: z.ZodDefault>; agentId: z.ZodOptional; context: z.ZodOptional>; @@ -3786,10 +3786,9 @@ declare const ComponentPropsMap: { defaultValue?: never; }>>]>; variant: z.ZodDefault>>; align: z.ZodDefault; aggregate: z.ZodEnum<{ - count: "count"; min: "min"; max: "max"; + count: "count"; sum: "sum"; avg: "avg"; }>; @@ -3911,8 +3911,8 @@ declare const ComponentPropsMap: { }, z.core.$strict>; readonly 'element:metadata_viewer': z.ZodObject<{ type: z.ZodEnum<{ - state_machine: "state_machine"; flow: "flow"; + state_machine: "state_machine"; permission: "permission"; }>; name: z.ZodString; @@ -3968,8 +3968,8 @@ declare const ComponentPropsMap: { }>>>; size: z.ZodDefault>>; icon: z.ZodOptional; iconPosition: z.ZodDefault>; target: z.ZodOptional; params: z.ZodOptional>; required: z.ZodDefault>; @@ -4145,7 +4145,7 @@ declare const ComponentPropsMap: { key?: never; defaultValue?: never; }) | undefined; - type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "tags" | "email" | "phone" | "user" | "datetime" | "location" | "text" | "url" | "time" | "formula" | "textarea" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "vector" | undefined; + type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "email" | "url" | "datetime" | "time" | "formula" | "text" | "textarea" | "phone" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "user" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "location" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector" | undefined; options?: { label: string | (Record & { key?: never; @@ -4206,7 +4206,7 @@ declare const ComponentPropsMap: { key?: never; defaultValue?: never; }) | undefined; - type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "tags" | "email" | "phone" | "user" | "datetime" | "location" | "text" | "url" | "time" | "formula" | "textarea" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "vector" | undefined; + type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "email" | "url" | "datetime" | "time" | "formula" | "text" | "textarea" | "phone" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "user" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "location" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector" | undefined; options?: { label: string | (Record & { key?: never; @@ -4260,7 +4260,7 @@ declare const ComponentPropsMap: { requiresFeature?: "organization" | "twoFactor" | "multiOrgEnabled" | "degradedTenancy" | "oidcProvider" | "sso" | "ssoEnforced" | "deviceAuthorization" | "admin" | "phoneNumber" | "phoneNumberOtp" | undefined; }>>>>; name: z.ZodOptional; - label: z.ZodOptional & { + errorMessage: z.ZodOptional & { key?: never; defaultValue?: never; }, Record & { @@ -4273,7 +4273,7 @@ declare const ComponentPropsMap: { key?: never; defaultValue?: never; }>>]>>; - errorMessage: z.ZodOptional & { + label: z.ZodOptional & { key?: never; defaultValue?: never; }, Record & { @@ -4286,11 +4286,9 @@ declare const ComponentPropsMap: { key?: never; defaultValue?: never; }>>]>>; - method: z.ZodOptional>; confirmText: z.ZodOptional & { key?: never; @@ -4305,12 +4303,14 @@ declare const ComponentPropsMap: { key?: never; defaultValue?: never; }>>]>>; + method: z.ZodOptional>; bodyExtra: z.ZodOptional>; opensInNewTab: z.ZodOptional; - openIn: z.ZodOptional>; successMessage: z.ZodOptional & { key?: never; defaultValue?: never; @@ -4463,8 +4463,8 @@ declare const ComponentPropsMap: { inputType: z.ZodDefault>>; @@ -4632,8 +4632,8 @@ declare const ComponentPropsMap: { url: z.ZodString; method: z.ZodDefault>>; @@ -4645,8 +4645,8 @@ declare const ComponentPropsMap: { url: z.ZodString; method: z.ZodDefault>>; @@ -4709,12 +4709,12 @@ declare const ComponentPropsMap: { colorVariant: z.ZodOptional>; aggregate: z.ZodOptional; filter: z.ZodOptional>; @@ -4813,10 +4813,10 @@ declare const ComponentPropsMap: { size: z.ZodDefault>; width: z.ZodOptional>; }, z.core.$strict>>; @@ -4870,9 +4870,9 @@ declare const ComponentPropsMap: { mode: z.ZodDefault>; @@ -4882,10 +4882,10 @@ declare const ComponentPropsMap: { size: z.ZodDefault>; width: z.ZodOptional>; }, z.core.$strict>>; @@ -4894,15 +4894,15 @@ declare const ComponentPropsMap: { objectName: z.ZodOptional; recordId: z.ZodOptional>; mode: z.ZodOptional>; formType: z.ZodOptional>; @@ -4944,9 +4944,9 @@ declare const ComponentPropsMap: { }>>]>>; defaultTab: z.ZodOptional; tabPosition: z.ZodOptional>; allowSkip: z.ZodOptional; @@ -4958,9 +4958,9 @@ declare const ComponentPropsMap: { splitSize: z.ZodOptional; splitResizable: z.ZodOptional; drawerSide: z.ZodOptional>; drawerWidth: z.ZodOptional>; @@ -5054,8 +5054,8 @@ declare const ComponentPropsMap: { objectName: z.ZodOptional; recordId: z.ZodOptional>; mode: z.ZodOptional>; formType: z.ZodOptional>>; @@ -5132,8 +5132,8 @@ declare const ComponentPropsMap: { url: z.ZodString; method: z.ZodDefault>>; @@ -5208,8 +5208,8 @@ declare const ComponentPropsMap: { url: z.ZodString; method: z.ZodDefault>>; @@ -5221,8 +5221,8 @@ declare const ComponentPropsMap: { url: z.ZodString; method: z.ZodDefault>>; @@ -5312,8 +5312,8 @@ declare const ComponentPropsMap: { lockField: z.ZodOptional; objectField: z.ZodOptional; summaryExtent: z.ZodOptional>; defaultCollapsedDepth: z.ZodOptional; dependencyTypes: z.ZodOptional; @@ -5372,8 +5372,8 @@ declare const ComponentPropsMap: { url: z.ZodString; method: z.ZodDefault>>; @@ -5385,8 +5385,8 @@ declare const ComponentPropsMap: { url: z.ZodString; method: z.ZodDefault>>; @@ -5510,9 +5510,9 @@ declare const ComponentPropsMap: { mode: z.ZodDefault>; @@ -5522,10 +5522,10 @@ declare const ComponentPropsMap: { size: z.ZodDefault>; width: z.ZodOptional>; }, z.core.$strict>>; @@ -6764,8 +6764,8 @@ declare const ElementButtonPropsSchema: z.ZodObject<{ }>>>; size: z.ZodDefault>>; icon: z.ZodOptional; iconPosition: z.ZodDefault>; target: z.ZodOptional; params: z.ZodOptional>; required: z.ZodDefault>; @@ -6941,7 +6941,7 @@ declare const ElementButtonPropsSchema: z.ZodObject<{ key?: never; defaultValue?: never; }) | undefined; - type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "tags" | "email" | "phone" | "user" | "datetime" | "location" | "text" | "url" | "time" | "formula" | "textarea" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "vector" | undefined; + type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "email" | "url" | "datetime" | "time" | "formula" | "text" | "textarea" | "phone" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "user" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "location" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector" | undefined; options?: { label: string | (Record & { key?: never; @@ -7002,7 +7002,7 @@ declare const ElementButtonPropsSchema: z.ZodObject<{ key?: never; defaultValue?: never; }) | undefined; - type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "tags" | "email" | "phone" | "user" | "datetime" | "location" | "text" | "url" | "time" | "formula" | "textarea" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "vector" | undefined; + type?: "number" | "boolean" | "code" | "date" | "record" | "file" | "email" | "url" | "datetime" | "time" | "formula" | "text" | "textarea" | "phone" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "user" | "image" | "avatar" | "video" | "audio" | "summary" | "autonumber" | "composite" | "repeater" | "location" | "address" | "json" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector" | undefined; options?: { label: string | (Record & { key?: never; @@ -7056,7 +7056,7 @@ declare const ElementButtonPropsSchema: z.ZodObject<{ requiresFeature?: "organization" | "twoFactor" | "multiOrgEnabled" | "degradedTenancy" | "oidcProvider" | "sso" | "ssoEnforced" | "deviceAuthorization" | "admin" | "phoneNumber" | "phoneNumberOtp" | undefined; }>>>>; name: z.ZodOptional; - label: z.ZodOptional & { + errorMessage: z.ZodOptional & { key?: never; defaultValue?: never; }, Record & { @@ -7069,7 +7069,7 @@ declare const ElementButtonPropsSchema: z.ZodObject<{ key?: never; defaultValue?: never; }>>]>>; - errorMessage: z.ZodOptional & { + label: z.ZodOptional & { key?: never; defaultValue?: never; }, Record & { @@ -7082,11 +7082,9 @@ declare const ElementButtonPropsSchema: z.ZodObject<{ key?: never; defaultValue?: never; }>>]>>; - method: z.ZodOptional>; confirmText: z.ZodOptional & { key?: never; @@ -7101,12 +7099,14 @@ declare const ElementButtonPropsSchema: z.ZodObject<{ key?: never; defaultValue?: never; }>>]>>; + method: z.ZodOptional>; bodyExtra: z.ZodOptional>; opensInNewTab: z.ZodOptional; - openIn: z.ZodOptional>; successMessage: z.ZodOptional & { key?: never; defaultValue?: never; @@ -7239,8 +7239,8 @@ declare const ElementImagePropsSchema: z.ZodObject<{ // ── ElementMetadataViewerPropsSchema (const) ── declare const ElementMetadataViewerPropsSchema: z.ZodObject<{ type: z.ZodEnum<{ - state_machine: "state_machine"; flow: "flow"; + state_machine: "state_machine"; permission: "permission"; }>; name: z.ZodString; @@ -7284,9 +7284,9 @@ declare const ElementNumberPropsSchema: z.ZodObject<{ object: z.ZodString; field: z.ZodOptional; aggregate: z.ZodEnum<{ - count: "count"; min: "min"; max: "max"; + count: "count"; sum: "sum"; avg: "avg"; }>; @@ -7454,8 +7454,8 @@ declare const ElementTextInputPropsSchema: z.ZodObject<{ inputType: z.ZodDefault>>; @@ -7537,10 +7537,9 @@ declare const ElementTextPropsSchema: z.ZodObject<{ defaultValue?: never; }>>]>; variant: z.ZodDefault>>; align: z.ZodDefault; field: z.ZodObject<{ _lock: z.ZodOptional>; _lockReason: z.ZodOptional; _lockSource: z.ZodOptional; description: z.ZodOptional; @@ -7773,8 +7773,8 @@ declare const FieldWidgetPropsSchema: z.ZodObject<{ date: "date"; file: "file"; datetime: "datetime"; - text: "text"; time: "time"; + text: "text"; currency: "currency"; select: "select"; lookup: "lookup"; @@ -7905,9 +7905,9 @@ declare const FieldWidgetPropsSchema: z.ZodObject<{ object: z.ZodString; field: z.ZodString; function: z.ZodEnum<{ - count: "count"; min: "min"; max: "max"; + count: "count"; sum: "sum"; avg: "avg"; }>; @@ -7997,8 +7997,8 @@ declare const FieldWidgetPropsSchema: z.ZodObject<{ readonly: z.ZodDefault; requiredPermissions: z.ZodOptional>; maskingRule: z.ZodOptional; declare const NotificationSeveritySchema: z.ZodEnum<{ error: "error"; success: "success"; - info: "info"; warning: "warning"; + info: "info"; }>; // ── NotificationType (type) ── @@ -10901,11 +10901,11 @@ type NotificationType = z.input; // ── NotificationTypeSchema (const) ── declare const NotificationTypeSchema: z.ZodEnum<{ - alert: "alert"; - toast: "toast"; inline: "inline"; + toast: "toast"; banner: "banner"; snackbar: "snackbar"; + alert: "alert"; }>; // ── ObjectCalendarProps (type) ── @@ -10964,9 +10964,9 @@ declare const ObjectCalendarPropsSchema: z.ZodObject<{ mode: z.ZodDefault>; @@ -10976,10 +10976,10 @@ declare const ObjectCalendarPropsSchema: z.ZodObject<{ size: z.ZodDefault>; width: z.ZodOptional>; }, z.core.$strict>>; @@ -10993,15 +10993,15 @@ declare const ObjectFormPropsSchema: z.ZodObject<{ objectName: z.ZodOptional; recordId: z.ZodOptional>; mode: z.ZodOptional>; formType: z.ZodOptional>; @@ -11043,9 +11043,9 @@ declare const ObjectFormPropsSchema: z.ZodObject<{ }>>]>>; defaultTab: z.ZodOptional; tabPosition: z.ZodOptional>; allowSkip: z.ZodOptional; @@ -11057,9 +11057,9 @@ declare const ObjectFormPropsSchema: z.ZodObject<{ splitSize: z.ZodOptional; splitResizable: z.ZodOptional; drawerSide: z.ZodOptional>; drawerWidth: z.ZodOptional>; @@ -11168,8 +11168,8 @@ declare const ObjectGanttPropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -11181,8 +11181,8 @@ declare const ObjectGanttPropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -11272,8 +11272,8 @@ declare const ObjectGanttPropsSchema: z.ZodObject<{ lockField: z.ZodOptional; objectField: z.ZodOptional; summaryExtent: z.ZodOptional>; defaultCollapsedDepth: z.ZodOptional; dependencyTypes: z.ZodOptional; @@ -11432,8 +11432,8 @@ declare const ObjectGridPropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -11445,8 +11445,8 @@ declare const ObjectGridPropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -11516,9 +11516,9 @@ declare const ObjectKanbanPropsSchema: z.ZodObject<{ mode: z.ZodDefault>; @@ -11528,10 +11528,10 @@ declare const ObjectKanbanPropsSchema: z.ZodObject<{ size: z.ZodDefault>; width: z.ZodOptional>; }, z.core.$strict>>; @@ -12292,8 +12292,8 @@ declare const ObjectMapPropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -12305,8 +12305,8 @@ declare const ObjectMapPropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -12379,8 +12379,8 @@ declare const ObjectMasterDetailFormPropsSchema: z.ZodObject<{ objectName: z.ZodOptional; recordId: z.ZodOptional>; mode: z.ZodOptional>; formType: z.ZodOptional>; aggregate: z.ZodOptional; filter: z.ZodOptional>; @@ -12694,10 +12694,10 @@ declare const ObjectTimelinePropsSchema: z.ZodObject<{ size: z.ZodDefault>; width: z.ZodOptional>; }, z.core.$strict>>; @@ -12721,8 +12721,8 @@ declare const ObjectTreePropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -12734,8 +12734,8 @@ declare const ObjectTreePropsSchema: z.ZodObject<{ url: z.ZodString; method: z.ZodDefault>>; @@ -12865,8 +12865,8 @@ declare const PageAccordionProps: z.ZodObject<{ }, z.core.$strict>>; allowMultiple: z.ZodDefault; variant: z.ZodDefault>; aria: z.ZodOptional & { @@ -18688,8 +18688,8 @@ declare const PageTabsProps: z.ZodObject<{ }>>; type: z.ZodOptional; position: z.ZodDefault>; alwaysShowStrip: z.ZodOptional; items: z.ZodArray, z.ZodString]>>>; @@ -18957,9 +18957,9 @@ declare const RecordAlertActionSchema: z.ZodObject<{ default: "default"; link: "link"; secondary: "secondary"; - ghost: "ghost"; - outline: "outline"; destructive: "destructive"; + outline: "outline"; + ghost: "ghost"; }>>; }, z.core.$strict>; @@ -18968,8 +18968,8 @@ declare const RecordAlertProps: z.ZodObject<{ severity: z.ZodOptional>; title: z.ZodOptional & { key?: never; @@ -19038,9 +19038,9 @@ declare const RecordAlertProps: z.ZodObject<{ default: "default"; link: "link"; secondary: "secondary"; - ghost: "ghost"; - outline: "outline"; destructive: "destructive"; + outline: "outline"; + ghost: "ghost"; }>>; }, z.core.$strict>>; dismissible: z.ZodOptional; @@ -19068,14 +19068,14 @@ declare const RecordChatterProps: z.ZodObject<{ file: "file"; email: "email"; system: "system"; - note: "note"; sharing: "sharing"; approval: "approval"; - event: "event"; comment: "comment"; field_change: "field_change"; task: "task"; + event: "event"; call: "call"; + note: "note"; record_create: "record_create"; record_delete: "record_delete"; }>, z.ZodString]>>>; @@ -19319,15 +19319,15 @@ declare const RecordQuickActionsProps: z.ZodObject<{ default: "default"; link: "link"; secondary: "secondary"; - ghost: "ghost"; - outline: "outline"; destructive: "destructive"; + outline: "outline"; + ghost: "ghost"; }>>; size: z.ZodOptional>; }, z.core.$strict>; @@ -19385,10 +19385,10 @@ declare const RecordRelatedListProps: z.ZodObject<{ right: "right"; }>>; summary: z.ZodOptional, z.ZodObject<{ type: z.ZodEnum<{ - count: "count"; none: "none"; min: "min"; max: "max"; + count: "count"; sum: "sum"; avg: "avg"; count_empty: "count_empty"; @@ -27553,19 +27553,19 @@ declare const WidgetColorVariantSchema: z.ZodEnum<{ // ── actionForm (const) ── declare const actionForm: { - type: "split" | "simple" | "modal" | "drawer" | "tabbed" | "wizard"; + type: "split" | "drawer" | "modal" | "simple" | "tabbed" | "wizard"; layout?: "grid" | "inline" | "vertical" | "horizontal" | undefined; columns?: number | undefined; title?: string | undefined; description?: string | undefined; defaultTab?: string | undefined; - tabPosition?: "top" | "left" | "right" | "bottom" | undefined; + tabPosition?: "left" | "right" | "top" | "bottom" | undefined; allowSkip?: boolean | undefined; showStepIndicator?: boolean | undefined; splitDirection?: "vertical" | "horizontal" | undefined; splitSize?: number | undefined; splitResizable?: boolean | undefined; - drawerSide?: "top" | "left" | "right" | "bottom" | undefined; + drawerSide?: "left" | "right" | "top" | "bottom" | undefined; drawerWidth?: string | undefined; modalSize?: "default" | "full" | "sm" | "lg" | "xl" | undefined; data?: { @@ -27575,14 +27575,14 @@ declare const actionForm: { provider: "api"; read?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; } | undefined; write?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; @@ -27753,19 +27753,19 @@ declare const actionForm: { // ── appForm (const) ── declare const appForm: { - type: "split" | "simple" | "modal" | "drawer" | "tabbed" | "wizard"; + type: "split" | "drawer" | "modal" | "simple" | "tabbed" | "wizard"; layout?: "grid" | "inline" | "vertical" | "horizontal" | undefined; columns?: number | undefined; title?: string | undefined; description?: string | undefined; defaultTab?: string | undefined; - tabPosition?: "top" | "left" | "right" | "bottom" | undefined; + tabPosition?: "left" | "right" | "top" | "bottom" | undefined; allowSkip?: boolean | undefined; showStepIndicator?: boolean | undefined; splitDirection?: "vertical" | "horizontal" | undefined; splitSize?: number | undefined; splitResizable?: boolean | undefined; - drawerSide?: "top" | "left" | "right" | "bottom" | undefined; + drawerSide?: "left" | "right" | "top" | "bottom" | undefined; drawerWidth?: string | undefined; modalSize?: "default" | "full" | "sm" | "lg" | "xl" | undefined; data?: { @@ -27775,14 +27775,14 @@ declare const appForm: { provider: "api"; read?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; } | undefined; write?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; @@ -28010,19 +28010,19 @@ declare function compileListViewGroupRowsQuery(view: Pick | undefined; params?: Record | undefined; body?: unknown; } | undefined; write?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; @@ -28210,19 +28210,19 @@ declare const dashboardForm: { // ── datasetForm (const) ── declare const datasetForm: { - type: "split" | "simple" | "modal" | "drawer" | "tabbed" | "wizard"; + type: "split" | "drawer" | "modal" | "simple" | "tabbed" | "wizard"; layout?: "grid" | "inline" | "vertical" | "horizontal" | undefined; columns?: number | undefined; title?: string | undefined; description?: string | undefined; defaultTab?: string | undefined; - tabPosition?: "top" | "left" | "right" | "bottom" | undefined; + tabPosition?: "left" | "right" | "top" | "bottom" | undefined; allowSkip?: boolean | undefined; showStepIndicator?: boolean | undefined; splitDirection?: "vertical" | "horizontal" | undefined; splitSize?: number | undefined; splitResizable?: boolean | undefined; - drawerSide?: "top" | "left" | "right" | "bottom" | undefined; + drawerSide?: "left" | "right" | "top" | "bottom" | undefined; drawerWidth?: string | undefined; modalSize?: "default" | "full" | "sm" | "lg" | "xl" | undefined; data?: { @@ -28232,14 +28232,14 @@ declare const datasetForm: { provider: "api"; read?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; } | undefined; write?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; @@ -28489,19 +28489,19 @@ declare function objectNavTargetExclusivity(item: { // ── pageForm (const) ── declare const pageForm: { - type: "split" | "simple" | "modal" | "drawer" | "tabbed" | "wizard"; + type: "split" | "drawer" | "modal" | "simple" | "tabbed" | "wizard"; layout?: "grid" | "inline" | "vertical" | "horizontal" | undefined; columns?: number | undefined; title?: string | undefined; description?: string | undefined; defaultTab?: string | undefined; - tabPosition?: "top" | "left" | "right" | "bottom" | undefined; + tabPosition?: "left" | "right" | "top" | "bottom" | undefined; allowSkip?: boolean | undefined; showStepIndicator?: boolean | undefined; splitDirection?: "vertical" | "horizontal" | undefined; splitSize?: number | undefined; splitResizable?: boolean | undefined; - drawerSide?: "top" | "left" | "right" | "bottom" | undefined; + drawerSide?: "left" | "right" | "top" | "bottom" | undefined; drawerWidth?: string | undefined; modalSize?: "default" | "full" | "sm" | "lg" | "xl" | undefined; data?: { @@ -28511,14 +28511,14 @@ declare const pageForm: { provider: "api"; read?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; } | undefined; write?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; @@ -28695,19 +28695,19 @@ declare function reactBlockTagFor(schemaType: string): string; // ── reportForm (const) ── declare const reportForm: { - type: "split" | "simple" | "modal" | "drawer" | "tabbed" | "wizard"; + type: "split" | "drawer" | "modal" | "simple" | "tabbed" | "wizard"; layout?: "grid" | "inline" | "vertical" | "horizontal" | undefined; columns?: number | undefined; title?: string | undefined; description?: string | undefined; defaultTab?: string | undefined; - tabPosition?: "top" | "left" | "right" | "bottom" | undefined; + tabPosition?: "left" | "right" | "top" | "bottom" | undefined; allowSkip?: boolean | undefined; showStepIndicator?: boolean | undefined; splitDirection?: "vertical" | "horizontal" | undefined; splitSize?: number | undefined; splitResizable?: boolean | undefined; - drawerSide?: "top" | "left" | "right" | "bottom" | undefined; + drawerSide?: "left" | "right" | "top" | "bottom" | undefined; drawerWidth?: string | undefined; modalSize?: "default" | "full" | "sm" | "lg" | "xl" | undefined; data?: { @@ -28717,14 +28717,14 @@ declare const reportForm: { provider: "api"; read?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; } | undefined; write?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; @@ -28915,19 +28915,19 @@ declare function validateActionParams(resolved: ResolvedActionParam[], bag: Reco // ── viewForm (const) ── declare const viewForm: { - type: "split" | "simple" | "modal" | "drawer" | "tabbed" | "wizard"; + type: "split" | "drawer" | "modal" | "simple" | "tabbed" | "wizard"; layout?: "grid" | "inline" | "vertical" | "horizontal" | undefined; columns?: number | undefined; title?: string | undefined; description?: string | undefined; defaultTab?: string | undefined; - tabPosition?: "top" | "left" | "right" | "bottom" | undefined; + tabPosition?: "left" | "right" | "top" | "bottom" | undefined; allowSkip?: boolean | undefined; showStepIndicator?: boolean | undefined; splitDirection?: "vertical" | "horizontal" | undefined; splitSize?: number | undefined; splitResizable?: boolean | undefined; - drawerSide?: "top" | "left" | "right" | "bottom" | undefined; + drawerSide?: "left" | "right" | "top" | "bottom" | undefined; drawerWidth?: string | undefined; modalSize?: "default" | "full" | "sm" | "lg" | "xl" | undefined; data?: { @@ -28937,14 +28937,14 @@ declare const viewForm: { provider: "api"; read?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; } | undefined; write?: { url: string; - method: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record | undefined; params?: Record | undefined; body?: unknown; diff --git a/packages/spec/api-surface/automation.json b/packages/spec/api-surface/automation.json index a33da0bbcff..7dd61f5447b 100644 --- a/packages/spec/api-surface/automation.json +++ b/packages/spec/api-surface/automation.json @@ -130,6 +130,8 @@ "FlowFunctionEntry (type)", "FlowFunctionEntryParsed (type)", "FlowFunctionEntrySchema (const)", + "FlowFunctionLoweredDeclaration (type)", + "FlowFunctionLoweredDeclarationParsed (type)", "FlowFunctionLoweredDeclarationSchema (const)", "FlowGraph (interface)", "FlowNode (type)", diff --git a/packages/spec/declaration-map/automation.json b/packages/spec/declaration-map/automation.json index 654f1bba43a..97be6e386b4 100644 --- a/packages/spec/declaration-map/automation.json +++ b/packages/spec/declaration-map/automation.json @@ -72,6 +72,7 @@ "FlowFunctionDeclarationSchema": "automation/FlowFunctionLoweredDeclaration", "FlowFunctionEffect": "automation/FlowFunctionEffect", "FlowFunctionEffectSchema": "automation/FlowFunctionEffect", + "FlowFunctionLoweredDeclaration": "automation/FlowFunctionLoweredDeclaration", "FlowFunctionLoweredDeclarationSchema": "automation/FlowFunctionLoweredDeclaration", "FlowNode": "automation/FlowNode", "FlowNodeAction": "automation/FlowNodeAction", diff --git a/packages/spec/export-origins/automation.json b/packages/spec/export-origins/automation.json index aa6b0185a9d..8b1dd265d11 100644 --- a/packages/spec/export-origins/automation.json +++ b/packages/spec/export-origins/automation.json @@ -126,6 +126,8 @@ "FlowFunctionEntry": "src/automation/flow-function.zod.ts#FlowFunctionEntry (type)", "FlowFunctionEntryParsed": "src/automation/flow-function.zod.ts#FlowFunctionEntryParsed (type)", "FlowFunctionEntrySchema": "src/automation/flow-function.zod.ts#FlowFunctionEntrySchema (const)", + "FlowFunctionLoweredDeclaration": "src/automation/flow-function.zod.ts#FlowFunctionLoweredDeclaration (type)", + "FlowFunctionLoweredDeclarationParsed": "src/automation/flow-function.zod.ts#FlowFunctionLoweredDeclarationParsed (type)", "FlowFunctionLoweredDeclarationSchema": "src/automation/flow-function.zod.ts#FlowFunctionLoweredDeclarationSchema (const)", "FlowGraph": "src/automation/control-flow.zod.ts#FlowGraph (interface)", "FlowNode": "src/automation/flow.zod.ts#FlowNode (type)", From 0003b7cd8e1bff1229f1ff01b19ceffcb3a10d56 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 13:36:04 +0000 Subject: [PATCH 06/11] chore(spec): re-apply the four record-stage hook sites on the merged dropped-refinements ledger Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- packages/spec/dropped-refinements.baseline.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/spec/dropped-refinements.baseline.json b/packages/spec/dropped-refinements.baseline.json index bd2f0e2cb7c..52c868d1a4a 100644 --- a/packages/spec/dropped-refinements.baseline.json +++ b/packages/spec/dropped-refinements.baseline.json @@ -3,7 +3,7 @@ "measured": { "zod": "4.4.3", "publishedSchemasWithDroppedRefinements": 204, - "droppedRefinementSites": 569, + "droppedRefinementSites": 573, "refinementSitesThatDidProject": 357, "refinementSitesWithNoJsonFormToCompare": 9 }, @@ -61,6 +61,7 @@ "manifest.flows.element", "manifest.flows.element.errorHandling", "manifest.flows.element.nodes.element.in.waitEventConfig", + "manifest.hooks.element.object", "manifest.jobs.element.schedule.options[0].timezone", "manifest.navigationContributions.element.items.element.lazy.options[0]", "manifest.objectExtensions.element", @@ -154,6 +155,7 @@ "data.options[1].manifest.flows.element", "data.options[1].manifest.flows.element.errorHandling", "data.options[1].manifest.flows.element.nodes.element.in.waitEventConfig", + "data.options[1].manifest.hooks.element.object", "data.options[1].manifest.jobs.element.schedule.options[0].timezone", "data.options[1].manifest.objectExtensions.element", "data.options[1].manifest.objects.element", @@ -236,6 +238,7 @@ "options[1].manifest.flows.element", "options[1].manifest.flows.element.errorHandling", "options[1].manifest.flows.element.nodes.element.in.waitEventConfig", + "options[1].manifest.hooks.element.object", "options[1].manifest.jobs.element.schedule.options[0].timezone", "options[1].manifest.objectExtensions.element", "options[1].manifest.objects.element", @@ -279,6 +282,7 @@ "data.packages.element.options[1].manifest.flows.element", "data.packages.element.options[1].manifest.flows.element.errorHandling", "data.packages.element.options[1].manifest.flows.element.nodes.element.in.waitEventConfig", + "data.packages.element.options[1].manifest.hooks.element.object", "data.packages.element.options[1].manifest.jobs.element.schedule.options[0].timezone", "data.packages.element.options[1].manifest.objectExtensions.element", "data.packages.element.options[1].manifest.objects.element", From 3a7ab9ca24ef723f6b2901eb1803653b15860484 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 14:48:42 +0000 Subject: [PATCH 07/11] chore(spec): regenerate the package-api reference on the merged tree Discharges the os-regen deferral taken on the merge commit. Step 2 restored main's side of content/docs/references/api/package-api.mdx (both sides moved it, so the driver had silently kept one); this regeneration re-derives this branch's generated content on top of it, which is why the `functions` and `hooks` rows read as the record-stage declaration again instead of `any`, while main's retired `limit`/`cursor` query-parameter rows stay. `pnpm --filter @objectstack/spec check:generated`: all 15 generated artifacts up to date. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- content/docs/references/api/package-api.mdx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index 34144b2f112..988bedaa480 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -135,11 +135,14 @@ Installed package row whose manifest is the assembled package body ## GetInstalledPackageRequest +Get installed package request + ### Properties | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **packageId** | `string` | ✅ | Package identifier | +| **version** | `string` | optional | Scope the read to this exact installed version; `latest` or omitted reads the installed row | --- @@ -398,8 +401,9 @@ List installed packages request | :--- | :--- | :--- | :--- | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional | Filter by package status | | **enabled** | `boolean` | optional | Filter by enabled state | -| **limit** | `integer` | optional (default: `50`) | Maximum number of packages to return | -| **cursor** | `string` | optional | Cursor for pagination | +| **type** | `string` | optional | Filter by the installed manifest's `type` — exact match, unmatched values select nothing | +| **limit** | `never` | optional | [REMOVED] `limit` / `cursor` were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on `status` / `type` and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. `limit` also declared `.default(50)`, so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and `hasMore` on the response is a constant `false` that is now true by construction. Filter with `status` and `type` instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key. | +| **cursor** | `never` | optional | [REMOVED] `limit` / `cursor` were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on `status` / `type` and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. `limit` also declared `.default(50)`, so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and `hasMore` on the response is a constant `false` that is now true by construction. Filter with `status` and `type` instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key. | --- @@ -447,7 +451,7 @@ List installed packages response | **packages** | `({ manifest: object; status?: Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>; enabled?: boolean; installedAt?: string; … } \| … +1 more)[]` | ✅ | Installed packages | | **total** | `integer` | optional | Total matching packages | | **nextCursor** | `string` | optional | Cursor for the next page | -| **hasMore** | `boolean` | ✅ | Whether more packages are available | +| **hasMore** | `boolean` | ✅ | Whether more packages are available — this door serves one page, so always `false` | --- @@ -971,11 +975,14 @@ Resolve dependencies response ## UninstallPackageApiRequest +Uninstall package request + ### Properties | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **packageId** | `string` | ✅ | Package identifier | +| **keepData** | `boolean` | optional | Preserve object tables and remove metadata only; on the wire, `?keepData=true` or `?keepData=1` | --- From 03ad959c8b73ff0b0b30e740ca3ad2c8be725f39 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 14:48:54 +0000 Subject: [PATCH 08/11] merge origin/main (os-regen artifacts taken from main; regeneration follows) --- content/docs/references/api/package-api.mdx | 14 +++++++------- content/docs/references/index.mdx | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index 988bedaa480..925dcd62c08 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -47,7 +47,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -89,7 +89,7 @@ Installed package row whose manifest is the assembled package body | **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) | | **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) | | **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) | -| **functions** | `Record }> \| { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' \| 'writes'> }[]` | optional | Named handler functions, lowered to the refs a JSON document carries | +| **functions** | `any` | optional | Named handler functions, as they survived the record JSON projection | | **datasourceMapping** | `{ namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[]` | optional | Centralized datasource routing rules for packages/namespaces/objects | | **translations** | `Record; apps?: Record; messages?: Record; globalActions?: Record; … }>[]` | optional | I18n Translation Bundles | | **objectExtensions** | `{ extend: string; fields?: Record; label?: string; pluralLabel?: string; … }[]` | optional | Extensions to objects owned by other packages | @@ -113,7 +113,7 @@ Installed package row whose manifest is the assembled package body | **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents | | **tools** | `{ name: string; label: string; description: string; parameters: Record; … }[]` | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_`` tools (ADR-0109) | | **skills** | `{ name: string; label: string; description?: string; surface?: Enum<'ask' \| 'build' \| 'both'>; … }[]` | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) | -| **hooks** | `{ name: string; label?: string; object: string \| string[]; events: Enum<'beforeFind' \| 'afterFind' \| 'beforeInsert' \| 'afterInsert' \| 'beforeUpdate' \| …>[]; … }[]` | optional | Object Lifecycle Hooks, as a JSON document carries them | +| **hooks** | `any` | optional | Object lifecycle hooks, as they survived the record JSON projection | | **mappings** | `{ name: string; label?: string; sourceFormat?: Enum<'csv' \| 'json' \| 'xml' \| 'sql'>; targetObject: string; … }[]` | optional | Data Import/Export Mappings | | **analyticsCubes** | `{ name: string; title?: string; description?: string; sql: string; … }[]` | optional | Analytics Semantic Layer Cubes | | **connectors** | `{ name: string; label: string; type: Enum<'saas' \| 'database' \| 'file_storage' \| 'message_queue' \| 'api' \| 'custom'>; description?: string; … }[]` | optional | External System Connectors. A provider-bound entry (has `provider`: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via `connector_action`; credentials are `auth.credentialRef` references, never inline secrets. An entry with no `provider` is a catalog descriptor only (NOT dispatchable) — set `enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0097). | @@ -208,7 +208,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -303,7 +303,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -345,7 +345,7 @@ Installed package row whose manifest is the assembled package body | **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) | | **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) | | **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) | -| **functions** | `Record }> \| { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' \| 'writes'> }[]` | optional | Named handler functions, lowered to the refs a JSON document carries | +| **functions** | `any` | optional | Named handler functions, as they survived the record JSON projection | | **datasourceMapping** | `{ namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[]` | optional | Centralized datasource routing rules for packages/namespaces/objects | | **translations** | `Record; apps?: Record; messages?: Record; globalActions?: Record; … }>[]` | optional | I18n Translation Bundles | | **objectExtensions** | `{ extend: string; fields?: Record; label?: string; pluralLabel?: string; … }[]` | optional | Extensions to objects owned by other packages | @@ -369,7 +369,7 @@ Installed package row whose manifest is the assembled package body | **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents | | **tools** | `{ name: string; label: string; description: string; parameters: Record; … }[]` | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_`` tools (ADR-0109) | | **skills** | `{ name: string; label: string; description?: string; surface?: Enum<'ask' \| 'build' \| 'both'>; … }[]` | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) | -| **hooks** | `{ name: string; label?: string; object: string \| string[]; events: Enum<'beforeFind' \| 'afterFind' \| 'beforeInsert' \| 'afterInsert' \| 'beforeUpdate' \| …>[]; … }[]` | optional | Object Lifecycle Hooks, as a JSON document carries them | +| **hooks** | `any` | optional | Object lifecycle hooks, as they survived the record JSON projection | | **mappings** | `{ name: string; label?: string; sourceFormat?: Enum<'csv' \| 'json' \| 'xml' \| 'sql'>; targetObject: string; … }[]` | optional | Data Import/Export Mappings | | **analyticsCubes** | `{ name: string; title?: string; description?: string; sql: string; … }[]` | optional | Analytics Semantic Layer Cubes | | **connectors** | `{ name: string; label: string; type: Enum<'saas' \| 'database' \| 'file_storage' \| 'message_queue' \| 'api' \| 'custom'>; description?: string; … }[]` | optional | External System Connectors. A provider-bound entry (has `provider`: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via `connector_action`; credentials are `auth.credentialRef` references, never inline secrets. An entry with no `provider` is a catalog descriptor only (NOT dispatchable) — set `enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0097). | diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 7c64230db85..3913e991867 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1533 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1532 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | | [API Protocol](/docs/references/api) | 31 | 441 | REST contracts, endpoints, routing, realtime, batch, discovery. | -| [Automation Protocol](/docs/references/automation) | 14 | 75 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | +| [Automation Protocol](/docs/references/automation) | 14 | 74 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Data Protocol](/docs/references/data) | 29 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | | [Integration Protocol](/docs/references/integration) | 1 | 24 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 157 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **195** | **1533** | 14 protocol modules | +| **Total** | **195** | **1532** | 14 protocol modules | --- @@ -104,7 +104,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. ## Automation Protocol -**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 75 schemas** +**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 74 schemas** Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. @@ -116,7 +116,7 @@ Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execu | [`control-flow.zod.ts`](/docs/references/automation/control-flow) | `FlowRegion`, `LoopConfig`, `ParallelBranch`, `ParallelConfig`, `RetryPolicy`, `TryCatchConfig`, `TryCatchErrorValue` | | [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary`, `ScheduleState` | | [`flow.zod.ts`](/docs/references/automation/flow) | `Flow`, `FlowEdge`, `FlowNode`, `FlowNodeAction`, `FlowVariable`, `FlowVersionHistory` | -| [`flow-function.zod.ts`](/docs/references/automation/flow-function) | `FlowFunctionEffect`, `FlowFunctionLoweredDeclaration` | +| [`flow-function.zod.ts`](/docs/references/automation/flow-function) | `FlowFunctionEffect` | | [`io-node-config.zod.ts`](/docs/references/automation/io-node-config) | `HttpConfig`, `NotifyConfig` | | [`node-executor.zod.ts`](/docs/references/automation/node-executor) | `ActionCategory`, `ActionDescriptor`, `ActionParadigm`, `NodeExecutorDescriptor`, `WaitEventType`, `WaitExecutorConfig`, `WaitResumePayload`, `WaitTimeoutBehavior` | | [`schedule-organization.zod.ts`](/docs/references/automation/schedule-organization) | `ScheduleOrganization` | From 8810ee64cf538d73aa77ed66d4befe1fecb7b286 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 14:50:04 +0000 Subject: [PATCH 09/11] chore(spec): re-derive the two reference artifacts step 2 took main's side of `scripts/pm/os-regen-merge.sh` was rerun once more after the regeneration commit had landed. Its step 2 is not idempotent across that boundary: with the branch's regenerated bytes in HEAD the "both sides changed it" test now fires for content/docs/references/api/package-api.mdx and content/docs/references/index.mdx, so it restored main's side of both and committed it -- erasing `FlowFunctionLoweredDeclaration` from the automation listing (schema count 1533 -> 1532) and rolling the package-api `functions`/`hooks` rows back to `any`. This commit re-runs `gen:schema && gen:docs` on the merged tree. The result is byte-identical to the regeneration commit: `git diff 3a7ab9ca24e -- content/` is empty. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- content/docs/references/api/package-api.mdx | 14 +++++++------- content/docs/references/index.mdx | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index 925dcd62c08..988bedaa480 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -47,7 +47,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -89,7 +89,7 @@ Installed package row whose manifest is the assembled package body | **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) | | **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) | | **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) | -| **functions** | `any` | optional | Named handler functions, as they survived the record JSON projection | +| **functions** | `Record }> \| { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' \| 'writes'> }[]` | optional | Named handler functions, lowered to the refs a JSON document carries | | **datasourceMapping** | `{ namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[]` | optional | Centralized datasource routing rules for packages/namespaces/objects | | **translations** | `Record; apps?: Record; messages?: Record; globalActions?: Record; … }>[]` | optional | I18n Translation Bundles | | **objectExtensions** | `{ extend: string; fields?: Record; label?: string; pluralLabel?: string; … }[]` | optional | Extensions to objects owned by other packages | @@ -113,7 +113,7 @@ Installed package row whose manifest is the assembled package body | **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents | | **tools** | `{ name: string; label: string; description: string; parameters: Record; … }[]` | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_`` tools (ADR-0109) | | **skills** | `{ name: string; label: string; description?: string; surface?: Enum<'ask' \| 'build' \| 'both'>; … }[]` | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) | -| **hooks** | `any` | optional | Object lifecycle hooks, as they survived the record JSON projection | +| **hooks** | `{ name: string; label?: string; object: string \| string[]; events: Enum<'beforeFind' \| 'afterFind' \| 'beforeInsert' \| 'afterInsert' \| 'beforeUpdate' \| …>[]; … }[]` | optional | Object Lifecycle Hooks, as a JSON document carries them | | **mappings** | `{ name: string; label?: string; sourceFormat?: Enum<'csv' \| 'json' \| 'xml' \| 'sql'>; targetObject: string; … }[]` | optional | Data Import/Export Mappings | | **analyticsCubes** | `{ name: string; title?: string; description?: string; sql: string; … }[]` | optional | Analytics Semantic Layer Cubes | | **connectors** | `{ name: string; label: string; type: Enum<'saas' \| 'database' \| 'file_storage' \| 'message_queue' \| 'api' \| 'custom'>; description?: string; … }[]` | optional | External System Connectors. A provider-bound entry (has `provider`: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via `connector_action`; credentials are `auth.credentialRef` references, never inline secrets. An entry with no `provider` is a catalog descriptor only (NOT dispatchable) — set `enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0097). | @@ -208,7 +208,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -303,7 +303,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries | +| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it | | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional (default: `"installed"`) | Package state: installed, disabled, installing, upgrading, uninstalling, or error | | **enabled** | `boolean` | optional (default: `true`) | Whether the package is currently enabled | | **installedAt** | `string` | optional | Installation timestamp | @@ -345,7 +345,7 @@ Installed package row whose manifest is the assembled package body | **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) | | **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) | | **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) | -| **functions** | `any` | optional | Named handler functions, as they survived the record JSON projection | +| **functions** | `Record }> \| { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' \| 'writes'> }[]` | optional | Named handler functions, lowered to the refs a JSON document carries | | **datasourceMapping** | `{ namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[]` | optional | Centralized datasource routing rules for packages/namespaces/objects | | **translations** | `Record; apps?: Record; messages?: Record; globalActions?: Record; … }>[]` | optional | I18n Translation Bundles | | **objectExtensions** | `{ extend: string; fields?: Record; label?: string; pluralLabel?: string; … }[]` | optional | Extensions to objects owned by other packages | @@ -369,7 +369,7 @@ Installed package row whose manifest is the assembled package body | **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents | | **tools** | `{ name: string; label: string; description: string; parameters: Record; … }[]` | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_`` tools (ADR-0109) | | **skills** | `{ name: string; label: string; description?: string; surface?: Enum<'ask' \| 'build' \| 'both'>; … }[]` | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) | -| **hooks** | `any` | optional | Object lifecycle hooks, as they survived the record JSON projection | +| **hooks** | `{ name: string; label?: string; object: string \| string[]; events: Enum<'beforeFind' \| 'afterFind' \| 'beforeInsert' \| 'afterInsert' \| 'beforeUpdate' \| …>[]; … }[]` | optional | Object Lifecycle Hooks, as a JSON document carries them | | **mappings** | `{ name: string; label?: string; sourceFormat?: Enum<'csv' \| 'json' \| 'xml' \| 'sql'>; targetObject: string; … }[]` | optional | Data Import/Export Mappings | | **analyticsCubes** | `{ name: string; title?: string; description?: string; sql: string; … }[]` | optional | Analytics Semantic Layer Cubes | | **connectors** | `{ name: string; label: string; type: Enum<'saas' \| 'database' \| 'file_storage' \| 'message_queue' \| 'api' \| 'custom'>; description?: string; … }[]` | optional | External System Connectors. A provider-bound entry (has `provider`: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via `connector_action`; credentials are `auth.credentialRef` references, never inline secrets. An entry with no `provider` is a catalog descriptor only (NOT dispatchable) — set `enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0097). | diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 3913e991867..7c64230db85 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1532 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1533 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | | [API Protocol](/docs/references/api) | 31 | 441 | REST contracts, endpoints, routing, realtime, batch, discovery. | -| [Automation Protocol](/docs/references/automation) | 14 | 74 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | +| [Automation Protocol](/docs/references/automation) | 14 | 75 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Data Protocol](/docs/references/data) | 29 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | | [Integration Protocol](/docs/references/integration) | 1 | 24 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 157 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **195** | **1532** | 14 protocol modules | +| **Total** | **195** | **1533** | 14 protocol modules | --- @@ -104,7 +104,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. ## Automation Protocol -**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 74 schemas** +**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 75 schemas** Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. @@ -116,7 +116,7 @@ Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execu | [`control-flow.zod.ts`](/docs/references/automation/control-flow) | `FlowRegion`, `LoopConfig`, `ParallelBranch`, `ParallelConfig`, `RetryPolicy`, `TryCatchConfig`, `TryCatchErrorValue` | | [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary`, `ScheduleState` | | [`flow.zod.ts`](/docs/references/automation/flow) | `Flow`, `FlowEdge`, `FlowNode`, `FlowNodeAction`, `FlowVariable`, `FlowVersionHistory` | -| [`flow-function.zod.ts`](/docs/references/automation/flow-function) | `FlowFunctionEffect` | +| [`flow-function.zod.ts`](/docs/references/automation/flow-function) | `FlowFunctionEffect`, `FlowFunctionLoweredDeclaration` | | [`io-node-config.zod.ts`](/docs/references/automation/io-node-config) | `HttpConfig`, `NotifyConfig` | | [`node-executor.zod.ts`](/docs/references/automation/node-executor) | `ActionCategory`, `ActionDescriptor`, `ActionParadigm`, `NodeExecutorDescriptor`, `WaitEventType`, `WaitExecutorConfig`, `WaitResumePayload`, `WaitTimeoutBehavior` | | [`schedule-organization.zod.ts`](/docs/references/automation/schedule-organization) | `ScheduleOrganization` | From aac764cc36113b4e52820c1695715f000ccbe1b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 15:34:12 +0000 Subject: [PATCH 10/11] chore(spec): regenerate the protocol index on the merged tree Discharges the os-regen deferral the merge commit recorded. `origin/main` and this branch both moved content/docs/references/index.mdx, so the driver resolved it with exit 0 and silently kept one side -- measured: the merged blob was byte-identical to this branch's, and main's side (the UI section's `DashboardWidgetChartConfig` row and its counts) was gone. Step 2 restored main's side into the working tree only, and this commit is `gen:schema && gen:docs` re-derived on top of it. The result is the union of both intents: the UI module reads 16 pages / 158 schemas with `DashboardWidgetChartConfig` listed, the automation module reads 14 pages / 75 schemas with `FlowFunctionLoweredDeclaration` listed, and the total moves 1533 -> 1534. Against each side separately the regenerated file differs by exactly the other side's delta and by nothing else. No hand edit: the file's own header routes it to `pnpm --filter @objectstack/spec gen:schema && gen:docs`. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- content/docs/references/index.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 7c64230db85..6290447bd9a 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1533 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1534 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -32,8 +32,8 @@ counts are sums of the rows they head. Regenerate with | [Shared Protocol](/docs/references/shared) | 10 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. | | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | -| [UI Protocol](/docs/references/ui) | 16 | 157 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **195** | **1533** | 14 protocol modules | +| [UI Protocol](/docs/references/ui) | 16 | 158 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | +| **Total** | **195** | **1534** | 14 protocol modules | --- @@ -361,7 +361,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a ## UI Protocol -**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 157 schemas** +**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 158 schemas** Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. @@ -373,7 +373,7 @@ Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI lay | [`bulk-action.zod.ts`](/docs/references/ui/bulk-action) | `BulkActionDef`, `BulkActionExecution`, `BulkActionOperation`, `BulkActionParam` | | [`chart.zod.ts`](/docs/references/ui/chart) | `ChartAggregate`, `ChartAggregateFunction`, `ChartAnnotation`, `ChartAxis`, `ChartConfig`, `ChartDrillDown`, `ChartGroupBy`, `ChartInteraction`, `ChartSeries`, `ChartType` | | [`component.zod.ts`](/docs/references/ui/component) | `AIChatWindowProps`, `ElementButtonProps`, `ElementFilterProps`, `ElementFormProps`, `ElementImageProps`, `ElementMetadataViewerProps`, `ElementNumberProps`, `ElementRecordPickerProps`, `ElementTextInputProps`, `ElementTextProps`, `ObjectCalendarProps`, `ObjectFormProps`, `ObjectGanttProps`, `ObjectGridProps`, `ObjectKanbanProps`, `ObjectMapProps`, `ObjectMasterDetailFormProps`, `ObjectMetricProps`, `ObjectTimelineProps`, `ObjectTreeProps`, `PageAccordionProps`, `PageCardProps`, `PageContainerProps`, `PageHeaderProps`, `PageTabsProps`, `RecordActivityProps`, `RecordAlertAction`, `RecordAlertProps`, `RecordChatterProps`, `RecordDetailsProps`, `RecordHighlightsField`, `RecordHighlightsProps`, `RecordHistoryProps`, `RecordPathProps`, `RecordQuickActionsProps`, `RecordReferenceRailProps`, `RecordRelatedListProps`, `ReferenceRailEntry` | -| [`dashboard.zod.ts`](/docs/references/ui/dashboard) | `Dashboard`, `DashboardHeader`, `DashboardHeaderAction`, `DashboardWidget`, `DashboardWidgetOptions`, `GlobalFilter`, `GlobalFilterOptionsFrom`, `WidgetActionType`, `WidgetColorVariant` | +| [`dashboard.zod.ts`](/docs/references/ui/dashboard) | `Dashboard`, `DashboardHeader`, `DashboardHeaderAction`, `DashboardWidget`, `DashboardWidgetChartConfig`, `DashboardWidgetOptions`, `GlobalFilter`, `GlobalFilterOptionsFrom`, `WidgetActionType`, `WidgetColorVariant` | | [`dataset.zod.ts`](/docs/references/ui/dataset) | `Dataset`, `DatasetDimension`, `DatasetMeasure`, `DerivedMeasureOp` | | [`expression-bindable-text-keys.zod.ts`](/docs/references/ui/expression-bindable-text-keys) | `ExpressionBindableTextKey` | | [`i18n.zod.ts`](/docs/references/ui/i18n) | `AriaProps`, `I18nLabel`, `InlineLocaleMap` | From 96dd3549ff68924bcc665fe3209542d44257a6c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 06:52:42 +0000 Subject: [PATCH 11/11] chore(spec): regenerate the protocol reference on the merged tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discharges the os-regen deferral recorded by the merge commit. Both routed paths are regenerated whole from the merged sources; no byte is hand-edited. Union proof, both directions, per path: content/docs/references/api/package-api.mdx regen vs this branch's side == main's delta (20 lines, identical) regen vs main's side == this branch's (14 lines, identical) content/docs/references/index.mdx regen vs this branch's side == main's delta (12 lines, identical) regen vs main's side == this branch's (6 lines, identical) The two lines excluded from that comparison carry the running schema TOTAL, which a union must move where neither side alone does: base 1533, each side alone 1534, merged tree 1535 — and 1535 is what the generator itself reports for the merged sources, so the total is measured rather than reconciled. main brought DatasetSelection/DatasetCompareTo/DatasetTotals into api/analytics and retired KernelSecurityScanResult/KernelSecurityVulnerability from kernel/plugin-security-advanced. this branch brought FlowFunctionLoweredDeclaration into automation/flow-function. Both survive in the merged output. The ledger's last header counter is the build's own reading of the merged tree: gen:schema measures 565 dropped sites across 205 published schemas, 366 projected, 9 with no JSON form — the first two already matched the union resolved in the merge commit, so only refinementSitesThatDidProject moved. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude --- content/docs/references/api/package-api.mdx | 20 +++++++++---------- content/docs/references/index.mdx | 16 +++++++-------- .../spec/dropped-refinements.baseline.json | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index 988bedaa480..2c9e97b8cd8 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -64,7 +64,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | @@ -257,7 +257,7 @@ Installed package with runtime lifecycle state | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | @@ -320,7 +320,7 @@ Installed package row whose manifest is the assembled package body | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | @@ -402,8 +402,8 @@ List installed packages request | **status** | `Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>` | optional | Filter by package status | | **enabled** | `boolean` | optional | Filter by enabled state | | **type** | `string` | optional | Filter by the installed manifest's `type` — exact match, unmatched values select nothing | -| **limit** | `never` | optional | [REMOVED] `limit` / `cursor` were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on `status` / `type` and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. `limit` also declared `.default(50)`, so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and `hasMore` on the response is a constant `false` that is now true by construction. Filter with `status` and `type` instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key. | -| **cursor** | `never` | optional | [REMOVED] `limit` / `cursor` were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on `status` / `type` and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. `limit` also declared `.default(50)`, so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and `hasMore` on the response is a constant `false` that is now true by construction. Filter with `status` and `type` instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key. | +| **limit** | `never` | optional | [REMOVED] `limit` / `cursor` were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on `status` / `type` / `enabled` and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. `limit` also declared `.default(50)`, so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and `hasMore` on the response is a constant `false` that is now true by construction. Filter with `status`, `type` and `enabled` instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key. | +| **cursor** | `never` | optional | [REMOVED] `limit` / `cursor` were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on `status` / `type` / `enabled` and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. `limit` also declared `.default(50)`, so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and `hasMore` on the response is a constant `false` that is now true by construction. Filter with `status`, `type` and `enabled` instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key. | --- @@ -504,7 +504,7 @@ Install package request | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | @@ -548,7 +548,7 @@ Install package request | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | @@ -667,7 +667,7 @@ Install package request | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | @@ -800,7 +800,7 @@ Upgrade package request | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | @@ -895,7 +895,7 @@ Resolve dependencies request | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique package identifier (reverse domain style) | +| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) | | **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") | | **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package | | **version** | `string` | ✅ | Package version (semantic versioning) | diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 6290447bd9a..a2e0ce9dd5c 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1534 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1535 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -20,12 +20,12 @@ counts are sums of the rows they head. Regenerate with | Module | Pages | Schemas | Description | | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | -| [API Protocol](/docs/references/api) | 31 | 441 | REST contracts, endpoints, routing, realtime, batch, discovery. | +| [API Protocol](/docs/references/api) | 31 | 444 | REST contracts, endpoints, routing, realtime, batch, discovery. | | [Automation Protocol](/docs/references/automation) | 14 | 75 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Data Protocol](/docs/references/data) | 29 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | | [Integration Protocol](/docs/references/integration) | 1 | 24 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. | -| [Kernel Protocol](/docs/references/kernel) | 30 | 159 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. | +| [Kernel Protocol](/docs/references/kernel) | 30 | 157 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. | | [Marketplace Protocol](/docs/references/marketplace) | 4 | 30 | The package & marketplace format — package identity and versions, listing, publish, review, search, install, template manifests. | | [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. | | [Security Protocol](/docs/references/security) | 5 | 30 | Permission sets, row-level security, sharing rules, tenancy posture. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 158 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **195** | **1534** | 14 protocol modules | +| **Total** | **195** | **1535** | 14 protocol modules | --- @@ -62,13 +62,13 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations. ## API Protocol -**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **31 pages, 441 schemas** +**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **31 pages, 444 schemas** REST contracts, endpoints, routing, realtime, batch, discovery. | File | Schemas | | :--- | :--- | -| [`analytics.zod.ts`](/docs/references/api/analytics) | `AnalyticsEndpoint`, `AnalyticsMetadataResponse`, `AnalyticsQueryRequest`, `AnalyticsResultResponse`, `AnalyticsSqlResponse`, `GetAnalyticsMetaRequest` | +| [`analytics.zod.ts`](/docs/references/api/analytics) | `AnalyticsEndpoint`, `AnalyticsMetadataResponse`, `AnalyticsQueryRequest`, `AnalyticsResultResponse`, `AnalyticsSqlResponse`, `DatasetCompareTo`, `DatasetSelection`, `DatasetTotals`, `GetAnalyticsMetaRequest` | | [`auth.zod.ts`](/docs/references/api/auth) | `AuthProvider`, `LoginRequest`, `LoginType`, `RefreshTokenRequest`, `RegisterRequest`, `Session`, `SessionResponse`, `SessionUser`, `UserProfileResponse` | | [`auth-endpoints.zod.ts`](/docs/references/api/auth-endpoints) | `AuthEndpoint`, `AuthFeaturesConfig`, `AuthProviderInfo`, `DeviceRequestResponse`, `DeviceTokenResponse`, `EmailPasswordConfigPublic`, `GetAuthConfigResponse` | | [`automation-api.zod.ts`](/docs/references/api/automation-api) | `AutomationApiErrorCode`, `AutomationFlowPathParams`, `AutomationRunPathParams`, `CreateFlowRequest`, `CreateFlowResponse`, `DeleteFlowRequest`, `DeleteFlowResponse`, `FlowSummary`, `GetFlowRequest`, `GetFlowResponse`, `GetRunRequest`, `GetRunResponse`, `ListFlowsRequest`, `ListFlowsResponse`, `ListRunsRequest`, `ListRunsResponse`, `ResumeFailureDetails`, `ToggleFlowRequest`, `ToggleFlowResponse`, `TriggerFlowRequest`, `TriggerFlowResponse`, `UpdateFlowRequest`, `UpdateFlowResponse` | @@ -197,7 +197,7 @@ The single connector protocol (ADR-0097) — catalog descriptors and provider-bo ## Kernel Protocol -**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **30 pages, 159 schemas** +**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **30 pages, 157 schemas** Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. @@ -227,7 +227,7 @@ Plugin lifecycle and manifests, capabilities and security, metadata loading, ser | [`plugin-loading.zod.ts`](/docs/references/kernel/plugin-loading) | `PluginLoadingEvent`, `PluginLoadingState` | | [`plugin-registry.zod.ts`](/docs/references/kernel/plugin-registry) | `PluginInstallConfig`, `PluginQualityMetrics`, `PluginRegistryEntry`, `PluginSearchFilters`, `PluginStatistics`, `PluginVendor` | | [`plugin-security.zod.ts`](/docs/references/kernel/plugin-security) | `DependencyGraph`, `DependencyGraphNode`, `PackageDependencyConflict`, `PackageDependencyResolutionResult`, `PluginProvenance`, `PluginTrustScore`, `ResolvedPackageDependency`, `SBOM`, `SBOMEntry`, `SecurityPolicy`, `SecurityScanResult`, `SecurityVulnerability`, `VulnerabilitySeverity` | -| [`plugin-security-advanced.zod.ts`](/docs/references/kernel/plugin-security-advanced) | `KernelSecurityPolicy`, `KernelSecurityScanResult`, `KernelSecurityVulnerability`, `PermissionAction`, `PermissionScope`, `PluginPermission`, `PluginPermissionSet`, `PluginSecurityManifest`, `PluginTrustLevel`, `ResourceType`, `RuntimeConfig`, `SandboxConfig` | +| [`plugin-security-advanced.zod.ts`](/docs/references/kernel/plugin-security-advanced) | `KernelSecurityPolicy`, `PermissionAction`, `PermissionScope`, `PluginPermission`, `PluginPermissionSet`, `PluginSecurityManifest`, `PluginTrustLevel`, `ResourceType`, `RuntimeConfig`, `SandboxConfig` | | [`plugin-structure.zod.ts`](/docs/references/kernel/plugin-structure) | `OpsDomainModule`, `OpsFilePath`, `OpsPluginStructure` | | [`plugin-validator.zod.ts`](/docs/references/kernel/plugin-validator) | `PluginMetadata`, `ValidationError`, `ValidationResult`, `ValidationWarning` | | [`plugin-versioning.zod.ts`](/docs/references/kernel/plugin-versioning) | `BreakingChange`, `CompatibilityLevel`, `CompatibilityMatrixEntry`, `DependencyConflict`, `DeprecationNotice`, `MultiVersionSupport`, `PluginCompatibilityMatrix`, `PluginDependencyResolutionResult`, `PluginVersionMetadata`, `SemanticVersion`, `VersionConstraint` | diff --git a/packages/spec/dropped-refinements.baseline.json b/packages/spec/dropped-refinements.baseline.json index c93cf1875b2..7e6d0dd6876 100644 --- a/packages/spec/dropped-refinements.baseline.json +++ b/packages/spec/dropped-refinements.baseline.json @@ -4,7 +4,7 @@ "zod": "4.4.3", "publishedSchemasWithDroppedRefinements": 205, "droppedRefinementSites": 565, - "refinementSitesThatDidProject": 357, + "refinementSitesThatDidProject": 366, "refinementSitesWithNoJsonFormToCompare": 9 }, "entries": {