@octaviaflow/flow-rules 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ /**
2
+ * ONE answer to "what does this step's config look like?"
3
+ *
4
+ * THE PROBLEM THIS EXISTS TO END. Every action carried its shape in two places
5
+ * that never spoke to each other:
6
+ *
7
+ * index.ts `inputSchema.fields` — what `describe_action_type` reports to an
8
+ * agent and what the config fence enforces;
9
+ * config.ts `XxxConfig.schema` + `validate()` — what the panel actually binds
10
+ * and what the platform's own validation checks.
11
+ *
12
+ * `index.ts` never imported `config.ts`, so they drifted. Measured:
13
+ * - `transform` declared 2 input fields; its panel binds 13.
14
+ * - `variables` declared 0; the real key is `fields` — an agent guessed
15
+ * `variables`, was told "no fields defined", and gave up on the action.
16
+ * - `slack_message` declared `message`; the panel stores `text`; the engine
17
+ * accepts either — so an agent wrote `message`, Slack received it, and the
18
+ * panel showed an empty box. Its own `config.ts` had said all along:
19
+ * "Canonical message body: `text`. Legacy alias: `message`."
20
+ * - On beta, 71 of 131 real config keys were undeclared.
21
+ *
22
+ * The agent was fed the stale half of the catalog and did exactly what it was
23
+ * told. So this module makes `config.ts` the source of truth: every consumer
24
+ * that needs a step's shape — the fence, `describe_action_type`, the editor —
25
+ * reads `contractFieldsFor(definition)`, which is `inputSchema` (kept for its
26
+ * labels, descriptions and option lists) UNIONED with everything the contract
27
+ * declares. A key the contract knows can no longer be "undeclared".
28
+ *
29
+ * And `contractDrift()` is a TEST: any `inputSchema` key the contract does not
30
+ * recognise fails the build. That is what would have caught `message`.
31
+ */
32
+ import type { ActionDefinition, FieldConfig } from "../catalog/types";
33
+ /** The vocabulary the `config.ts` schema maps already use. Not extended. */
34
+ export type ContractFieldType = "string" | "text" | "select" | "json" | "connection" | "number" | "boolean" | "array" | "object" | "url" | "expression" | "code" | "none";
35
+ /**
36
+ * A field as the `config.ts` maps actually write it. `type` is the vocabulary
37
+ * above, but the maps are plain object literals (so TypeScript sees `string`),
38
+ * and several carry `description`/`min` for the panel. The vocabulary is
39
+ * enforced at derivation — an unknown type becomes a String field and is
40
+ * flagged by `contractVocabularyDrift` — rather than by making 22 files cast.
41
+ */
42
+ export interface ContractField {
43
+ type: string;
44
+ required?: boolean;
45
+ options?: ReadonlyArray<string | number>;
46
+ [extra: string]: unknown;
47
+ }
48
+ export interface ConfigContract {
49
+ schema: Readonly<Record<string, ContractField>>;
50
+ /** The platform's own check, exactly as the panel runs it. */
51
+ validate?: (config: Record<string, unknown>) => {
52
+ valid: boolean;
53
+ errors: string[];
54
+ };
55
+ }
56
+ /**
57
+ * Every action that has a `config.ts`. Keyed by action id.
58
+ *
59
+ * Fourteen actions have no contract yet (group, ai_agent, idp_extract, the
60
+ * file-format and storage actions, connector_trigger). For those,
61
+ * `contractFor` returns undefined and consumers fall back to `inputSchema`
62
+ * alone — which is exactly what they did before, so nothing regresses. Adding a
63
+ * contract for one of them is a `config.ts` plus one line here.
64
+ */
65
+ export declare const CONFIG_CONTRACTS: Readonly<Record<string, ConfigContract>>;
66
+ export declare function contractFor(actionId: string | undefined): ConfigContract | undefined;
67
+ /** Keys the contract declares — `none`-typed placeholders excluded. */
68
+ export declare function declaredKeys(actionId: string | undefined): string[];
69
+ /**
70
+ * Keys the EDITOR persists for itself — `_outputSchema`, `_lastOutput`,
71
+ * `_outputSchemaHash`, `sampledColumnSchemas`-style caches. They are derived,
72
+ * never authored, and a value in one of them is a snapshot the editor will
73
+ * overwrite. Nothing outside the editor should write them, and an agent that
74
+ * does is writing a lie the next open will erase.
75
+ */
76
+ export declare function isEditorPrivateKey(key: string): boolean;
77
+ /**
78
+ * What a step ACCEPTS: `inputSchema.fields` — kept, because it carries labels,
79
+ * descriptions and option lists worth showing — unioned with every contract
80
+ * key it forgot. The single thing the fence, `describe_action_type` and the
81
+ * editor should read.
82
+ */
83
+ export declare function contractFieldsFor(definition: ActionDefinition): FieldConfig[];
84
+ /**
85
+ * `inputSchema` keys the contract does NOT know. A non-empty result is a bug:
86
+ * `describe_action_type` is advertising a key the platform does not store under
87
+ * that name. `slack_message.message` was one. The test over the whole catalog
88
+ * fails on any of these, so it cannot happen quietly again.
89
+ */
90
+ export declare function contractDrift(definition: ActionDefinition): string[];
91
+ /** Contract fields whose `type` is not in the vocabulary — a typo, not a new kind. */
92
+ export declare function contractVocabularyDrift(actionId: string): string[];
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The functions a Field Mapper `transform` may call — implementations AND the
3
+ * list, in one place.
4
+ *
5
+ * WHY THIS EXISTS. A Transform step's mapping has two slots, and they are not
6
+ * interchangeable:
7
+ *
8
+ * sourceField names a FIELD `{{Website}}`
9
+ * transform transforms the VALUE `uppercase(value)`
10
+ *
11
+ * Neither one evaluates an expression. `sourceField` is a literal path lookup,
12
+ * and `transform` is a single function call — so anything clever written in
13
+ * either resolves to nothing. Both failure modes were silent: a bad
14
+ * `sourceField` wrote an EMPTY value on every record, and an unknown
15
+ * `transform` function returned the source value unchanged, with a comment in
16
+ * the engine that said so out loud ("an unknown built-in falls through
17
+ * silently"). An agent hit the first one and blanked a field on every customer
18
+ * a live flow synced.
19
+ *
20
+ * Shipping the list without the implementations would just move the drift: the
21
+ * UI's FX sidebar already advertises 99 functions while the engine's mapping
22
+ * executor accepted 10, and four of those ten were not in the sidebar at all.
23
+ * So this module owns BOTH. One import, nothing to keep in step.
24
+ *
25
+ * ADDING ONE: put it in `MAPPING_FUNCTIONS` with a `MAPPING_FUNCTION_SPECS`
26
+ * entry. The spec is what the editor lists and what an agent is shown, so a
27
+ * function without one is invisible and effectively does not exist.
28
+ */
29
+ /** A value-transform. `value` is the resolved source; the rest are literals. */
30
+ export type MappingFunction = (...args: unknown[]) => unknown;
31
+ export interface MappingFunctionSpec {
32
+ name: string;
33
+ /** How it is written, for the picker and for an agent's tool description. */
34
+ signature: string;
35
+ description: string;
36
+ example: string;
37
+ }
38
+ export declare const MAPPING_FUNCTIONS: Record<string, MappingFunction>;
39
+ export declare const MAPPING_FUNCTION_SPECS: readonly MappingFunctionSpec[];
40
+ /** Custom functions are written by the workspace and resolved at run time. */
41
+ export declare const USER_FUNCTION_PREFIX = "user.";
42
+ /**
43
+ * Is this a function a mapping may call?
44
+ *
45
+ * `user.*` is accepted on name alone — the workspace's own functions are
46
+ * resolved from a registry at run time, and this module cannot know them.
47
+ */
48
+ export declare function isMappingFunction(name: string): boolean;
49
+ /**
50
+ * The function a `transform` calls, or `null` when it is not a call at all.
51
+ *
52
+ * Mirrors the engine's parsing exactly, including the back-compat shim: a BARE
53
+ * name (`uppercase`) means `uppercase(value)`, because older saved mappings
54
+ * persisted just the name.
55
+ */
56
+ export declare function mappingTransformFunctionName(transform: string): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octaviaflow/flow-rules",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "The flow action catalog and the rules engine behind Flow Doctor \u2014 one definition of what a step accepts and what makes a flow valid, shared by the editor and the server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",