@aglyn/plugins-logic 1.0.0-beta.231 → 1.0.0-beta.233

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-logic",
3
- "version": "1.0.0-beta.231",
3
+ "version": "1.0.0-beta.233",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,13 +25,13 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.231",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.231",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.231",
31
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.231",
32
- "@aglyn/shared-util-timestamp": "1.0.0-beta.231",
33
- "@aglyn/tenant-data-admin": "1.0.0-beta.231",
34
- "@aglyn/tenant-feature-instance": "1.0.0-beta.231",
28
+ "@aglyn/aglyn": "1.0.0-beta.233",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.233",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.233",
31
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.233",
32
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.233",
33
+ "@aglyn/tenant-data-admin": "1.0.0-beta.233",
34
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.233",
35
35
  "@swc/helpers": "0.5.23"
36
36
  },
37
37
  "peerDependencies": {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The logic plugin's CONSOLE-ONLY server declarations (AGL-3616), named under
19
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
20
+ * console's boot. The tenant runtime registers none of it: only the console
21
+ * runs AI jobs (AGL-3026), and a pre-authorized writer has no business in
22
+ * the process that serves the public internet.
23
+ *
24
+ * - The `variable` and `function` draft writers on the core's resource-drafts
25
+ * seam (`server/logic-drafts.ts`): what an AI build — or any plugin making
26
+ * a site's logic from a brief — writes a variable or a function through,
27
+ * under this plugin's rules.
28
+ * - The `variable` operation an AI build plans (`server/variable-ai-capability.ts`),
29
+ * which the `variable` writer executes. A function is the AI plugin's own
30
+ * operation, because writing one takes a model; its `logic` step writes it
31
+ * through the `function` writer registered here.
32
+ *
33
+ * Registering again replaces this plugin's own.
34
+ */
35
+ export declare function registerLogicConsoleServerDeclarations(): void;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // The seams' own modules, not the `@aglyn/aglyn/server` barrel: boot needs
17
+ // two registries, not the whole server surface.
18
+ import { registerPluginResourceDraftWriter } from "@aglyn/aglyn/plugin-manager/plugin-resource-drafts";
19
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
20
+ import { checkFunctionDraftContent, checkVariableDraftContent, FUNCTION_DRAFT_RESOURCE, VARIABLE_DRAFT_RESOURCE } from "./server/logic-draft-content.js";
21
+ import { registerVariableAiCapability } from "./server/variable-ai-capability.js";
22
+ /**
23
+ * A writer whose store half loads with its first call: `check` is pure and
24
+ * answers from the light content module, and `refusal`, `read` and `write`
25
+ * bring the writer module and the Admin SDK with them.
26
+ */ function lazyWriter(check, load) {
27
+ return {
28
+ check,
29
+ refusal: async (context)=>(await load()).refusal(context),
30
+ read: async (context)=>(await load()).read(context),
31
+ write: async (request)=>(await load()).write(request)
32
+ };
33
+ }
34
+ /**
35
+ * The logic plugin's CONSOLE-ONLY server declarations (AGL-3616), named under
36
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
37
+ * console's boot. The tenant runtime registers none of it: only the console
38
+ * runs AI jobs (AGL-3026), and a pre-authorized writer has no business in
39
+ * the process that serves the public internet.
40
+ *
41
+ * - The `variable` and `function` draft writers on the core's resource-drafts
42
+ * seam (`server/logic-drafts.ts`): what an AI build — or any plugin making
43
+ * a site's logic from a brief — writes a variable or a function through,
44
+ * under this plugin's rules.
45
+ * - The `variable` operation an AI build plans (`server/variable-ai-capability.ts`),
46
+ * which the `variable` writer executes. A function is the AI plugin's own
47
+ * operation, because writing one takes a model; its `logic` step writes it
48
+ * through the `function` writer registered here.
49
+ *
50
+ * Registering again replaces this plugin's own.
51
+ */ export function registerLogicConsoleServerDeclarations() {
52
+ registerPluginResourceDraftWriter(VARIABLE_DRAFT_RESOURCE, lazyWriter((content)=>checkVariableDraftContent(content), async ()=>(await import("./server/logic-drafts.js")).variableDraftWriter), {
53
+ pluginId: BUNDLE_ID
54
+ });
55
+ registerPluginResourceDraftWriter(FUNCTION_DRAFT_RESOURCE, lazyWriter((content)=>checkFunctionDraftContent(content), async ()=>(await import("./server/logic-drafts.js")).functionDraftWriter), {
56
+ pluginId: BUNDLE_ID
57
+ });
58
+ registerVariableAiCapability();
59
+ }
60
+
61
+ //# sourceMappingURL=declarations.console-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/logic/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The seams' own modules, not the `@aglyn/aglyn/server` barrel: boot needs\n// two registries, not the whole server surface.\nimport {\n registerPluginResourceDraftWriter,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport {\n checkFunctionDraftContent,\n checkVariableDraftContent,\n FUNCTION_DRAFT_RESOURCE,\n VARIABLE_DRAFT_RESOURCE,\n} from './server/logic-draft-content'\nimport { registerVariableAiCapability } from './server/variable-ai-capability'\n\n/**\n * A writer whose store half loads with its first call: `check` is pure and\n * answers from the light content module, and `refusal`, `read` and `write`\n * bring the writer module and the Admin SDK with them.\n */\nfunction lazyWriter(\n check: PluginResourceDraftWriter['check'],\n load: () => Promise<PluginResourceDraftWriter>,\n): PluginResourceDraftWriter {\n return {\n check,\n refusal: async (context) => (await load()).refusal(context),\n read: async (context) => (await load()).read(context),\n write: async (request) => (await load()).write(request),\n }\n}\n\n/**\n * The logic plugin's CONSOLE-ONLY server declarations (AGL-3616), named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it: only the console\n * runs AI jobs (AGL-3026), and a pre-authorized writer has no business in\n * the process that serves the public internet.\n *\n * - The `variable` and `function` draft writers on the core's resource-drafts\n * seam (`server/logic-drafts.ts`): what an AI build — or any plugin making\n * a site's logic from a brief — writes a variable or a function through,\n * under this plugin's rules.\n * - The `variable` operation an AI build plans (`server/variable-ai-capability.ts`),\n * which the `variable` writer executes. A function is the AI plugin's own\n * operation, because writing one takes a model; its `logic` step writes it\n * through the `function` writer registered here.\n *\n * Registering again replaces this plugin's own.\n */\nexport function registerLogicConsoleServerDeclarations(): void {\n registerPluginResourceDraftWriter(\n VARIABLE_DRAFT_RESOURCE,\n lazyWriter(\n (content) => checkVariableDraftContent(content),\n async () => (await import('./server/logic-drafts')).variableDraftWriter,\n ),\n { pluginId: BUNDLE_ID },\n )\n registerPluginResourceDraftWriter(\n FUNCTION_DRAFT_RESOURCE,\n lazyWriter(\n (content) => checkFunctionDraftContent(content),\n async () => (await import('./server/logic-drafts')).functionDraftWriter,\n ),\n { pluginId: BUNDLE_ID },\n )\n registerVariableAiCapability()\n}\n"],"names":["registerPluginResourceDraftWriter","BUNDLE_ID","checkFunctionDraftContent","checkVariableDraftContent","FUNCTION_DRAFT_RESOURCE","VARIABLE_DRAFT_RESOURCE","registerVariableAiCapability","lazyWriter","check","load","refusal","context","read","write","request","registerLogicConsoleServerDeclarations","content","variableDraftWriter","pluginId","functionDraftWriter"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,gDAAgD;AAChD,SACEA,iCAAiC,QAE5B,qDAAoD;AAC3D,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,uBAAuB,EACvBC,uBAAuB,QAClB,kCAA8B;AACrC,SAASC,4BAA4B,QAAQ,qCAAiC;AAE9E;;;;CAIC,GACD,SAASC,WACPC,KAAyC,EACzCC,IAA8C;IAE9C,OAAO;QACLD;QACAE,SAAS,OAAOC,UAAY,AAAC,CAAA,MAAMF,MAAK,EAAGC,OAAO,CAACC;QACnDC,MAAM,OAAOD,UAAY,AAAC,CAAA,MAAMF,MAAK,EAAGG,IAAI,CAACD;QAC7CE,OAAO,OAAOC,UAAY,AAAC,CAAA,MAAML,MAAK,EAAGI,KAAK,CAACC;IACjD;AACF;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASC;IACdf,kCACEK,yBACAE,WACE,CAACS,UAAYb,0BAA0Ba,UACvC,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,2BAAuB,EAAGC,mBAAmB,GAEzE;QAAEC,UAAUjB;IAAU;IAExBD,kCACEI,yBACAG,WACE,CAACS,UAAYd,0BAA0Bc,UACvC,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,2BAAuB,EAAGG,mBAAmB,GAEzE;QAAED,UAAUjB;IAAU;IAExBK;AACF"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type HostFunction } from '@aglyn/aglyn/app-utils/functions';
18
+ import { type HostVariableType } from '@aglyn/aglyn/app-utils/variables';
19
+ import type { PluginDraftCheck } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
20
+ /**
21
+ * WHAT A VARIABLE OR A FUNCTION ANOTHER PLUGIN ASKS FOR MUST BE (AGL-3616).
22
+ *
23
+ * The pure half of this plugin's `variable` and `function` draft writers
24
+ * (`logic-drafts.ts`): the content a caller sends, read into the document the
25
+ * Variables and Functions cards save, or every problem that stops it. No I/O
26
+ * and no Admin SDK, so the writers' synchronous `check` is answered from a
27
+ * module the console's boot can load without the server half.
28
+ *
29
+ * The rules are the editors', held where the editors leave a gap a person
30
+ * would never fill in:
31
+ *
32
+ * - A NAME is the binding grammar both cards and the AI proposals use
33
+ * (`VARIABLE_NAME_PATTERN`): a letter or `_`, then letters, digits or `_`,
34
+ * at most 40. Its uniqueness is the cards' — case-insensitive, among the
35
+ * site's live records — and needs the store, so the writer asks it.
36
+ * - A VARIABLE's value is written AS STORED, the string the Variables editor
37
+ * keeps for its type and `formatVariableValue` renders: digits; `true` or
38
+ * `false`; a date; `HH:MM`; a JSON object for a dictionary; a JSON list
39
+ * for a collection.
40
+ * - A FUNCTION is the shape the Functions card saves and the evaluator runs:
41
+ * typed parameters and locals with distinct names, if/then/otherwise
42
+ * operations whose every expression parses in the evaluator's grammar
43
+ * (`expressionSyntaxError`), assignments only to its own names, and a
44
+ * return value that is one of them. Which SITE variables it reads is
45
+ * reported, not judged: they live in the store, and the AI step that
46
+ * writes functions holds a definition to them and runs it once first.
47
+ */
48
+ /** The resource names the writers are registered under. */
49
+ export declare const VARIABLE_DRAFT_RESOURCE = "variable";
50
+ export declare const FUNCTION_DRAFT_RESOURCE = "function";
51
+ /** The longest name a binding reads, from `VARIABLE_NAME_PATTERN`. */
52
+ export declare const LOGIC_DRAFT_NAME_MAX = 40;
53
+ /** The longest variable value a draft keeps; the AI proposals' own bound. */
54
+ export declare const VARIABLE_DRAFT_VALUE_MAX = 2000;
55
+ /** The most parameters and locals a function draft keeps. */
56
+ export declare const FUNCTION_DRAFT_PARAMETERS_MAX = 20;
57
+ export declare const FUNCTION_DRAFT_LOCALS_MAX = 40;
58
+ /** The longest expression, label or default a function draft keeps. */
59
+ export declare const FUNCTION_DRAFT_EXPRESSION_MAX = 400;
60
+ export declare const FUNCTION_DRAFT_LABEL_MAX = 80;
61
+ /** The most choices one parameter offers. */
62
+ export declare const FUNCTION_DRAFT_OPTIONS_MAX = 20;
63
+ /** Every type the Variables editor offers. */
64
+ export declare const VARIABLE_DRAFT_TYPES: readonly HostVariableType[];
65
+ /** Whether a name is one a binding can read. */
66
+ export declare function logicDraftNameProblem(noun: 'variable' | 'function', name: string): string | null;
67
+ /** The case-insensitive key two names collide under, as both cards compare them. */
68
+ export declare function logicNameKey(name: string): string;
69
+ /** The variable as the Variables card saves it: never computed, so no workflow. */
70
+ export interface VariableDraftDocument {
71
+ name: string;
72
+ type: HostVariableType;
73
+ value: string;
74
+ workflowId: '';
75
+ workflowName: '';
76
+ }
77
+ export type LogicDraftRead<T> = {
78
+ ok: true;
79
+ value: T;
80
+ } | {
81
+ ok: false;
82
+ problems: string[];
83
+ };
84
+ /** What is wrong with a value for its type, as the editor stores it; `null` when nothing is. */
85
+ export declare function variableDraftValueProblem(type: HostVariableType, value: string): string | null;
86
+ /** The content as the variable writer stores it, or every problem that stops it. */
87
+ export declare function readVariableDraftContent(content: Readonly<Record<string, unknown>>): LogicDraftRead<VariableDraftDocument>;
88
+ /** Whether content is a variable this plugin would store, with what it says about it. Pure. */
89
+ export declare function checkVariableDraftContent(content: Readonly<Record<string, unknown>>): PluginDraftCheck;
90
+ /** What the writer reports about a variable: its type. */
91
+ export declare function variableDraftFacts(variable: Partial<VariableDraftDocument>): Readonly<Record<string, unknown>>;
92
+ /** The function as the Functions card saves it. */
93
+ export type FunctionDraftDocument = HostFunction & {
94
+ returnValue: string;
95
+ };
96
+ /** The content as the function writer stores it, or every problem that stops it. */
97
+ export declare function readFunctionDraftContent(content: Readonly<Record<string, unknown>>): LogicDraftRead<FunctionDraftDocument>;
98
+ /** What the writer reports about a function: its size, and the site variables it reads. */
99
+ export declare function functionDraftFacts(definition: Partial<HostFunction>): Readonly<Record<string, unknown>>;
100
+ /** Whether content is a function this plugin would store, with what it says about it. Pure. */
101
+ export declare function checkFunctionDraftContent(content: Readonly<Record<string, unknown>>): PluginDraftCheck;
@@ -0,0 +1,339 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { expressionSyntaxError, FUNCTION_MAX_OPERATIONS, functionReferencedNames } from "@aglyn/aglyn/app-utils/functions";
18
+ import { VARIABLE_NAME_PATTERN } from "@aglyn/aglyn/app-utils/variables";
19
+ /**
20
+ * WHAT A VARIABLE OR A FUNCTION ANOTHER PLUGIN ASKS FOR MUST BE (AGL-3616).
21
+ *
22
+ * The pure half of this plugin's `variable` and `function` draft writers
23
+ * (`logic-drafts.ts`): the content a caller sends, read into the document the
24
+ * Variables and Functions cards save, or every problem that stops it. No I/O
25
+ * and no Admin SDK, so the writers' synchronous `check` is answered from a
26
+ * module the console's boot can load without the server half.
27
+ *
28
+ * The rules are the editors', held where the editors leave a gap a person
29
+ * would never fill in:
30
+ *
31
+ * - A NAME is the binding grammar both cards and the AI proposals use
32
+ * (`VARIABLE_NAME_PATTERN`): a letter or `_`, then letters, digits or `_`,
33
+ * at most 40. Its uniqueness is the cards' — case-insensitive, among the
34
+ * site's live records — and needs the store, so the writer asks it.
35
+ * - A VARIABLE's value is written AS STORED, the string the Variables editor
36
+ * keeps for its type and `formatVariableValue` renders: digits; `true` or
37
+ * `false`; a date; `HH:MM`; a JSON object for a dictionary; a JSON list
38
+ * for a collection.
39
+ * - A FUNCTION is the shape the Functions card saves and the evaluator runs:
40
+ * typed parameters and locals with distinct names, if/then/otherwise
41
+ * operations whose every expression parses in the evaluator's grammar
42
+ * (`expressionSyntaxError`), assignments only to its own names, and a
43
+ * return value that is one of them. Which SITE variables it reads is
44
+ * reported, not judged: they live in the store, and the AI step that
45
+ * writes functions holds a definition to them and runs it once first.
46
+ */ /** The resource names the writers are registered under. */ export const VARIABLE_DRAFT_RESOURCE = 'variable';
47
+ export const FUNCTION_DRAFT_RESOURCE = 'function';
48
+ /** The longest name a binding reads, from `VARIABLE_NAME_PATTERN`. */ export const LOGIC_DRAFT_NAME_MAX = 40;
49
+ /** The longest variable value a draft keeps; the AI proposals' own bound. */ export const VARIABLE_DRAFT_VALUE_MAX = 2000;
50
+ /** The most parameters and locals a function draft keeps. */ export const FUNCTION_DRAFT_PARAMETERS_MAX = 20;
51
+ export const FUNCTION_DRAFT_LOCALS_MAX = 40;
52
+ /** The longest expression, label or default a function draft keeps. */ export const FUNCTION_DRAFT_EXPRESSION_MAX = 400;
53
+ export const FUNCTION_DRAFT_LABEL_MAX = 80;
54
+ /** The most choices one parameter offers. */ export const FUNCTION_DRAFT_OPTIONS_MAX = 20;
55
+ /** Every type the Variables editor offers. */ export const VARIABLE_DRAFT_TYPES = [
56
+ 'text',
57
+ 'number',
58
+ 'boolean',
59
+ 'date',
60
+ 'time',
61
+ 'dictionary',
62
+ 'collection'
63
+ ];
64
+ const VALUE_TYPES = [
65
+ 'number',
66
+ 'text',
67
+ 'boolean'
68
+ ];
69
+ const COMPARATORS = [
70
+ '==',
71
+ '!=',
72
+ '<',
73
+ '<=',
74
+ '>',
75
+ '>='
76
+ ];
77
+ /** Markup no editor field is for: a value or an expression holding it is refused. */ const MARKUP = /<\s*\/?\s*(script|iframe|style|object|embed)\b|javascript:/i;
78
+ /** `HH:MM`, or `HH:MM:SS`, as a time input stores one. */ const TIME = /^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/;
79
+ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
80
+ const text = (value)=>typeof value === 'string' ? value.trim() : '';
81
+ /** Whether a name is one a binding can read. */ export function logicDraftNameProblem(noun, name) {
82
+ if (!name) return `The ${noun} needs a name`;
83
+ return VARIABLE_NAME_PATTERN.test(name) ? null : `The ${noun} name must start with a letter or _, then letters, digits or _; at most ${LOGIC_DRAFT_NAME_MAX} characters`;
84
+ }
85
+ /** The case-insensitive key two names collide under, as both cards compare them. */ export function logicNameKey(name) {
86
+ return name.trim().toLowerCase();
87
+ }
88
+ /** What is wrong with a value for its type, as the editor stores it; `null` when nothing is. */ export function variableDraftValueProblem(type, value) {
89
+ if (value.length > VARIABLE_DRAFT_VALUE_MAX) {
90
+ return `The value is longer than ${VARIABLE_DRAFT_VALUE_MAX} characters`;
91
+ }
92
+ if (MARKUP.test(value)) return 'The value holds markup';
93
+ switch(type){
94
+ case 'number':
95
+ return value.trim() !== '' && Number.isFinite(Number(value)) ? null : 'A number variable’s value is a number, such as 49 or 0.5';
96
+ case 'boolean':
97
+ return value === 'true' || value === 'false' ? null : 'A boolean variable’s value is true or false';
98
+ case 'date':
99
+ return value.trim() !== '' && !Number.isNaN(new Date(value).getTime()) ? null : 'A date variable’s value is a date, such as 2026-10-01';
100
+ case 'time':
101
+ return TIME.test(value) ? null : 'A time variable’s value is HH:MM, such as 09:30';
102
+ case 'dictionary':
103
+ case 'collection':
104
+ {
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(value);
108
+ } catch (unused) {
109
+ parsed = undefined;
110
+ }
111
+ if (type === 'collection') {
112
+ return Array.isArray(parsed) ? null : 'A collection’s value is a JSON list, such as ["red","green"]';
113
+ }
114
+ return isRecord(parsed) ? null : 'A dictionary’s value is a JSON object, such as {"starter":19,"pro":49}';
115
+ }
116
+ default:
117
+ return null;
118
+ }
119
+ }
120
+ /** The content as the variable writer stores it, or every problem that stops it. */ export function readVariableDraftContent(content) {
121
+ var _content_type, _content_value;
122
+ const problems = [];
123
+ const name = text(content['name']);
124
+ const nameProblem = logicDraftNameProblem('variable', name);
125
+ if (nameProblem) problems.push(nameProblem);
126
+ const type = (_content_type = content['type']) != null ? _content_type : 'text';
127
+ if (!VARIABLE_DRAFT_TYPES.includes(type)) {
128
+ problems.push(`A variable’s type is one of ${VARIABLE_DRAFT_TYPES.join(', ')}`);
129
+ }
130
+ const rawValue = (_content_value = content['value']) != null ? _content_value : '';
131
+ if (typeof rawValue !== 'string') problems.push('The value is text, written as the editor stores it');
132
+ else if (VARIABLE_DRAFT_TYPES.includes(type)) {
133
+ const valueProblem = variableDraftValueProblem(type, rawValue);
134
+ if (valueProblem) problems.push(valueProblem);
135
+ }
136
+ if (problems.length) return {
137
+ ok: false,
138
+ problems
139
+ };
140
+ return {
141
+ ok: true,
142
+ value: {
143
+ name,
144
+ type: type,
145
+ value: rawValue,
146
+ workflowId: '',
147
+ workflowName: ''
148
+ }
149
+ };
150
+ }
151
+ /** Whether content is a variable this plugin would store, with what it says about it. Pure. */ export function checkVariableDraftContent(content) {
152
+ const read = readVariableDraftContent(content);
153
+ if (read.ok === false) return read;
154
+ return {
155
+ ok: true,
156
+ facts: variableDraftFacts(read.value)
157
+ };
158
+ }
159
+ /** What the writer reports about a variable: its type. */ export function variableDraftFacts(variable) {
160
+ var _variable_type;
161
+ return {
162
+ type: (_variable_type = variable.type) != null ? _variable_type : 'text'
163
+ };
164
+ }
165
+ /** The content as the function writer stores it, or every problem that stops it. */ export function readFunctionDraftContent(content) {
166
+ const problems = [];
167
+ const name = text(content['name']);
168
+ const nameProblem = logicDraftNameProblem('function', name);
169
+ if (nameProblem) problems.push(nameProblem);
170
+ const own = new Set();
171
+ const ownName = (raw, what)=>{
172
+ const value = text(raw);
173
+ if (!VARIABLE_NAME_PATTERN.test(value)) {
174
+ problems.push(`A ${what} name must start with a letter or _, then letters, digits or _`);
175
+ } else if (own.has(value)) {
176
+ problems.push(`The name ${value} is used twice; each parameter and local needs its own`);
177
+ }
178
+ own.add(value);
179
+ return value;
180
+ };
181
+ const typeOf = (raw)=>{
182
+ if (VALUE_TYPES.includes(raw)) return raw;
183
+ problems.push('A parameter or local’s type is number, text or boolean');
184
+ return 'number';
185
+ };
186
+ const list = (key, noun, max, min = 0)=>{
187
+ var _content_key;
188
+ const raw = (_content_key = content[key]) != null ? _content_key : [];
189
+ if (!Array.isArray(raw) || raw.some((entry)=>!isRecord(entry))) {
190
+ problems.push(`The ${noun} are a list`);
191
+ return [];
192
+ }
193
+ if (raw.length < min || raw.length > max) {
194
+ problems.push(min ? `A function has between ${min} and ${max} ${noun}` : `A function has at most ${max} ${noun}`);
195
+ }
196
+ return raw.slice(0, max);
197
+ };
198
+ const label = (raw, where)=>{
199
+ const value = text(raw);
200
+ if (value.length > FUNCTION_DRAFT_LABEL_MAX) problems.push(`${where} is longer than ${FUNCTION_DRAFT_LABEL_MAX} characters`);
201
+ if (MARKUP.test(value)) problems.push(`${where} holds markup`);
202
+ return value;
203
+ };
204
+ const parameters = list('parameters', 'parameters', FUNCTION_DRAFT_PARAMETERS_MAX).map((raw, index)=>{
205
+ const where = `Parameter ${index + 1}`;
206
+ const parameter = {
207
+ name: ownName(raw['name'], 'parameter'),
208
+ type: typeOf(raw['type']),
209
+ required: raw['required'] === true
210
+ };
211
+ const shown = label(raw['label'], `${where}'s label`);
212
+ if (shown) parameter.label = shown;
213
+ const start = label(raw['defaultValue'], `${where}'s starting value`);
214
+ if (start) parameter.defaultValue = start;
215
+ if (raw['options'] !== undefined) {
216
+ const options = Array.isArray(raw['options']) ? raw['options'] : null;
217
+ if (!options || options.some((option)=>!isRecord(option) || !text(option['value']))) {
218
+ problems.push(`${where}'s choices each have a value`);
219
+ } else if (options.length > FUNCTION_DRAFT_OPTIONS_MAX) {
220
+ problems.push(`${where} offers at most ${FUNCTION_DRAFT_OPTIONS_MAX} choices`);
221
+ } else if (options.length) {
222
+ parameter.options = options.map((option)=>{
223
+ const value = label(option['value'], `${where}'s choice`);
224
+ const read = label(option['label'], `${where}'s choice`);
225
+ return read && read !== value ? {
226
+ value,
227
+ label: read
228
+ } : {
229
+ value
230
+ };
231
+ });
232
+ }
233
+ }
234
+ return parameter;
235
+ });
236
+ const variables = list('variables', 'locals', FUNCTION_DRAFT_LOCALS_MAX).map((raw)=>({
237
+ name: ownName(raw['name'], 'local'),
238
+ type: typeOf(raw['type'])
239
+ }));
240
+ const expression = (raw, where)=>{
241
+ const value = text(raw);
242
+ if (!value) {
243
+ problems.push(`${where} is empty`);
244
+ return value;
245
+ }
246
+ if (value.length > FUNCTION_DRAFT_EXPRESSION_MAX || MARKUP.test(value)) {
247
+ problems.push(`${where} is not an expression the function can run`);
248
+ return value;
249
+ }
250
+ const syntax = expressionSyntaxError(value);
251
+ if (syntax) problems.push(`${where} cannot be read: ${syntax}`);
252
+ return value;
253
+ };
254
+ let assignments = 0;
255
+ const sets = (raw, where)=>{
256
+ if (raw !== undefined && !Array.isArray(raw)) {
257
+ problems.push(`${where} is a list of assignments`);
258
+ return [];
259
+ }
260
+ return (raw != null ? raw : []).map((set, index)=>{
261
+ const at = `${where}, assignment ${index + 1}`;
262
+ const entry = isRecord(set) ? set : {};
263
+ assignments += 1;
264
+ const target = text(entry['set']);
265
+ if (!own.has(target)) problems.push(`${at} sets ${target || 'nothing'}, which is not one of the function's parameters or locals`);
266
+ const workflow = text(entry['workflow']);
267
+ // A value a workflow computes needs no expression; the editor keeps one anyway.
268
+ const written = workflow && !text(entry['expression']) ? '' : expression(entry['expression'], at);
269
+ return _extends({
270
+ set: target,
271
+ expression: written
272
+ }, workflow ? {
273
+ workflow
274
+ } : {});
275
+ });
276
+ };
277
+ const operations = list('operations', 'operations', FUNCTION_MAX_OPERATIONS, 1).map((raw, index)=>{
278
+ const where = `Operation ${index + 1}`;
279
+ const condition = isRecord(raw['if']) ? raw['if'] : {};
280
+ const comparator = condition['comparator'];
281
+ if (!COMPARATORS.includes(comparator)) {
282
+ problems.push(`${where}'s condition compares with one of ${COMPARATORS.join(' ')}`);
283
+ }
284
+ return {
285
+ if: {
286
+ left: expression(condition['left'], `${where}'s condition (left)`),
287
+ comparator: COMPARATORS.includes(comparator) ? comparator : '==',
288
+ right: expression(condition['right'], `${where}'s condition (right)`)
289
+ },
290
+ then: sets(raw['then'], `${where} (then)`),
291
+ otherwise: sets(raw['otherwise'], `${where} (otherwise)`)
292
+ };
293
+ });
294
+ if (assignments > FUNCTION_MAX_OPERATIONS) {
295
+ problems.push(`A function makes at most ${FUNCTION_MAX_OPERATIONS} assignments`);
296
+ }
297
+ const returnValue = text(content['returnValue']);
298
+ if (!own.has(returnValue)) problems.push('The return value names one of the function’s own parameters or locals');
299
+ if (problems.length) return {
300
+ ok: false,
301
+ problems: [
302
+ ...new Set(problems)
303
+ ]
304
+ };
305
+ return {
306
+ ok: true,
307
+ value: {
308
+ name,
309
+ parameters,
310
+ variables,
311
+ operations,
312
+ returnValue
313
+ }
314
+ };
315
+ }
316
+ /** What the writer reports about a function: its size, and the site variables it reads. */ export function functionDraftFacts(definition) {
317
+ var _ref, _ref1, _definition_name, _definition_parameters, _definition_variables, _definition_operations;
318
+ var _definition_parameters1, _definition_operations1;
319
+ return {
320
+ parameters: (_ref = (_definition_parameters1 = definition.parameters) == null ? void 0 : _definition_parameters1.length) != null ? _ref : 0,
321
+ operations: (_ref1 = (_definition_operations1 = definition.operations) == null ? void 0 : _definition_operations1.length) != null ? _ref1 : 0,
322
+ reads: functionReferencedNames({
323
+ name: (_definition_name = definition.name) != null ? _definition_name : '',
324
+ parameters: (_definition_parameters = definition.parameters) != null ? _definition_parameters : [],
325
+ variables: (_definition_variables = definition.variables) != null ? _definition_variables : [],
326
+ operations: (_definition_operations = definition.operations) != null ? _definition_operations : []
327
+ })
328
+ };
329
+ }
330
+ /** Whether content is a function this plugin would store, with what it says about it. Pure. */ export function checkFunctionDraftContent(content) {
331
+ const read = readFunctionDraftContent(content);
332
+ if (read.ok === false) return read;
333
+ return {
334
+ ok: true,
335
+ facts: functionDraftFacts(read.value)
336
+ };
337
+ }
338
+
339
+ //# sourceMappingURL=logic-draft-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/server/logic-draft-content.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n expressionSyntaxError,\n FUNCTION_MAX_OPERATIONS,\n functionReferencedNames,\n type FunctionComparator,\n type FunctionValueType,\n type HostFunction,\n type HostFunctionParameter,\n} from '@aglyn/aglyn/app-utils/functions'\nimport { type HostVariableType, VARIABLE_NAME_PATTERN } from '@aglyn/aglyn/app-utils/variables'\nimport type { PluginDraftCheck } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\n\n/**\n * WHAT A VARIABLE OR A FUNCTION ANOTHER PLUGIN ASKS FOR MUST BE (AGL-3616).\n *\n * The pure half of this plugin's `variable` and `function` draft writers\n * (`logic-drafts.ts`): the content a caller sends, read into the document the\n * Variables and Functions cards save, or every problem that stops it. No I/O\n * and no Admin SDK, so the writers' synchronous `check` is answered from a\n * module the console's boot can load without the server half.\n *\n * The rules are the editors', held where the editors leave a gap a person\n * would never fill in:\n *\n * - A NAME is the binding grammar both cards and the AI proposals use\n * (`VARIABLE_NAME_PATTERN`): a letter or `_`, then letters, digits or `_`,\n * at most 40. Its uniqueness is the cards' — case-insensitive, among the\n * site's live records — and needs the store, so the writer asks it.\n * - A VARIABLE's value is written AS STORED, the string the Variables editor\n * keeps for its type and `formatVariableValue` renders: digits; `true` or\n * `false`; a date; `HH:MM`; a JSON object for a dictionary; a JSON list\n * for a collection.\n * - A FUNCTION is the shape the Functions card saves and the evaluator runs:\n * typed parameters and locals with distinct names, if/then/otherwise\n * operations whose every expression parses in the evaluator's grammar\n * (`expressionSyntaxError`), assignments only to its own names, and a\n * return value that is one of them. Which SITE variables it reads is\n * reported, not judged: they live in the store, and the AI step that\n * writes functions holds a definition to them and runs it once first.\n */\n\n/** The resource names the writers are registered under. */\nexport const VARIABLE_DRAFT_RESOURCE = 'variable'\nexport const FUNCTION_DRAFT_RESOURCE = 'function'\n\n/** The longest name a binding reads, from `VARIABLE_NAME_PATTERN`. */\nexport const LOGIC_DRAFT_NAME_MAX = 40\n/** The longest variable value a draft keeps; the AI proposals' own bound. */\nexport const VARIABLE_DRAFT_VALUE_MAX = 2_000\n/** The most parameters and locals a function draft keeps. */\nexport const FUNCTION_DRAFT_PARAMETERS_MAX = 20\nexport const FUNCTION_DRAFT_LOCALS_MAX = 40\n/** The longest expression, label or default a function draft keeps. */\nexport const FUNCTION_DRAFT_EXPRESSION_MAX = 400\nexport const FUNCTION_DRAFT_LABEL_MAX = 80\n/** The most choices one parameter offers. */\nexport const FUNCTION_DRAFT_OPTIONS_MAX = 20\n\n/** Every type the Variables editor offers. */\nexport const VARIABLE_DRAFT_TYPES: readonly HostVariableType[] = [\n 'text',\n 'number',\n 'boolean',\n 'date',\n 'time',\n 'dictionary',\n 'collection',\n]\n\nconst VALUE_TYPES: readonly FunctionValueType[] = ['number', 'text', 'boolean']\nconst COMPARATORS: readonly FunctionComparator[] = ['==', '!=', '<', '<=', '>', '>=']\n\n/** Markup no editor field is for: a value or an expression holding it is refused. */\nconst MARKUP = /<\\s*\\/?\\s*(script|iframe|style|object|embed)\\b|javascript:/i\n\n/** `HH:MM`, or `HH:MM:SS`, as a time input stores one. */\nconst TIME = /^([01]\\d|2[0-3]):[0-5]\\d(:[0-5]\\d)?$/\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** Whether a name is one a binding can read. */\nexport function logicDraftNameProblem(noun: 'variable' | 'function', name: string): string | null {\n if (!name) return `The ${noun} needs a name`\n return VARIABLE_NAME_PATTERN.test(name)\n ? null\n : `The ${noun} name must start with a letter or _, then letters, digits or _; at most ${LOGIC_DRAFT_NAME_MAX} characters`\n}\n\n/** The case-insensitive key two names collide under, as both cards compare them. */\nexport function logicNameKey(name: string): string {\n return name.trim().toLowerCase()\n}\n\n// ── Variables ─────────────────────────────────────────────────────────────\n\n/** The variable as the Variables card saves it: never computed, so no workflow. */\nexport interface VariableDraftDocument {\n name: string\n type: HostVariableType\n value: string\n workflowId: ''\n workflowName: ''\n}\n\nexport type LogicDraftRead<T> = { ok: true; value: T } | { ok: false; problems: string[] }\n\n/** What is wrong with a value for its type, as the editor stores it; `null` when nothing is. */\nexport function variableDraftValueProblem(type: HostVariableType, value: string): string | null {\n if (value.length > VARIABLE_DRAFT_VALUE_MAX) {\n return `The value is longer than ${VARIABLE_DRAFT_VALUE_MAX} characters`\n }\n if (MARKUP.test(value)) return 'The value holds markup'\n switch (type) {\n case 'number':\n return value.trim() !== '' && Number.isFinite(Number(value)) ? null : 'A number variable’s value is a number, such as 49 or 0.5'\n case 'boolean':\n return value === 'true' || value === 'false' ? null : 'A boolean variable’s value is true or false'\n case 'date':\n return value.trim() !== '' && !Number.isNaN(new Date(value).getTime())\n ? null\n : 'A date variable’s value is a date, such as 2026-10-01'\n case 'time':\n return TIME.test(value) ? null : 'A time variable’s value is HH:MM, such as 09:30'\n case 'dictionary':\n case 'collection': {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n parsed = undefined\n }\n if (type === 'collection') {\n return Array.isArray(parsed) ? null : 'A collection’s value is a JSON list, such as [\"red\",\"green\"]'\n }\n return isRecord(parsed) ? null : 'A dictionary’s value is a JSON object, such as {\"starter\":19,\"pro\":49}'\n }\n default:\n return null\n }\n}\n\n/** The content as the variable writer stores it, or every problem that stops it. */\nexport function readVariableDraftContent(content: Readonly<Record<string, unknown>>): LogicDraftRead<VariableDraftDocument> {\n const problems: string[] = []\n const name = text(content['name'])\n const nameProblem = logicDraftNameProblem('variable', name)\n if (nameProblem) problems.push(nameProblem)\n const type = content['type'] ?? 'text'\n if (!(VARIABLE_DRAFT_TYPES as readonly unknown[]).includes(type)) {\n problems.push(`A variable’s type is one of ${VARIABLE_DRAFT_TYPES.join(', ')}`)\n }\n const rawValue = content['value'] ?? ''\n if (typeof rawValue !== 'string') problems.push('The value is text, written as the editor stores it')\n else if ((VARIABLE_DRAFT_TYPES as readonly unknown[]).includes(type)) {\n const valueProblem = variableDraftValueProblem(type as HostVariableType, rawValue)\n if (valueProblem) problems.push(valueProblem)\n }\n if (problems.length) return { ok: false, problems }\n return {\n ok: true,\n value: { name, type: type as HostVariableType, value: rawValue as string, workflowId: '', workflowName: '' },\n }\n}\n\n/** Whether content is a variable this plugin would store, with what it says about it. Pure. */\nexport function checkVariableDraftContent(content: Readonly<Record<string, unknown>>): PluginDraftCheck {\n const read = readVariableDraftContent(content)\n if (read.ok === false) return read\n return { ok: true, facts: variableDraftFacts(read.value) }\n}\n\n/** What the writer reports about a variable: its type. */\nexport function variableDraftFacts(variable: Partial<VariableDraftDocument>): Readonly<Record<string, unknown>> {\n return { type: variable.type ?? 'text' }\n}\n\n// ── Functions ─────────────────────────────────────────────────────────────\n\n/** The function as the Functions card saves it. */\nexport type FunctionDraftDocument = HostFunction & { returnValue: string }\n\n/** The content as the function writer stores it, or every problem that stops it. */\nexport function readFunctionDraftContent(content: Readonly<Record<string, unknown>>): LogicDraftRead<FunctionDraftDocument> {\n const problems: string[] = []\n const name = text(content['name'])\n const nameProblem = logicDraftNameProblem('function', name)\n if (nameProblem) problems.push(nameProblem)\n\n const own = new Set<string>()\n const ownName = (raw: unknown, what: string): string => {\n const value = text(raw)\n if (!VARIABLE_NAME_PATTERN.test(value)) {\n problems.push(`A ${what} name must start with a letter or _, then letters, digits or _`)\n } else if (own.has(value)) {\n problems.push(`The name ${value} is used twice; each parameter and local needs its own`)\n }\n own.add(value)\n return value\n }\n const typeOf = (raw: unknown): FunctionValueType => {\n if ((VALUE_TYPES as readonly unknown[]).includes(raw)) return raw as FunctionValueType\n problems.push('A parameter or local’s type is number, text or boolean')\n return 'number'\n }\n const list = (key: string, noun: string, max: number, min = 0): Record<string, unknown>[] => {\n const raw = content[key] ?? []\n if (!Array.isArray(raw) || raw.some((entry) => !isRecord(entry))) {\n problems.push(`The ${noun} are a list`)\n return []\n }\n if (raw.length < min || raw.length > max) {\n problems.push(min ? `A function has between ${min} and ${max} ${noun}` : `A function has at most ${max} ${noun}`)\n }\n return (raw as Record<string, unknown>[]).slice(0, max)\n }\n const label = (raw: unknown, where: string): string => {\n const value = text(raw)\n if (value.length > FUNCTION_DRAFT_LABEL_MAX) problems.push(`${where} is longer than ${FUNCTION_DRAFT_LABEL_MAX} characters`)\n if (MARKUP.test(value)) problems.push(`${where} holds markup`)\n return value\n }\n\n const parameters = list('parameters', 'parameters', FUNCTION_DRAFT_PARAMETERS_MAX).map((raw, index) => {\n const where = `Parameter ${index + 1}`\n const parameter: HostFunctionParameter = {\n name: ownName(raw['name'], 'parameter'),\n type: typeOf(raw['type']),\n required: raw['required'] === true,\n }\n const shown = label(raw['label'], `${where}'s label`)\n if (shown) parameter.label = shown\n const start = label(raw['defaultValue'], `${where}'s starting value`)\n if (start) parameter.defaultValue = start\n if (raw['options'] !== undefined) {\n const options = Array.isArray(raw['options']) ? raw['options'] : null\n if (!options || options.some((option) => !isRecord(option) || !text(option['value']))) {\n problems.push(`${where}'s choices each have a value`)\n } else if (options.length > FUNCTION_DRAFT_OPTIONS_MAX) {\n problems.push(`${where} offers at most ${FUNCTION_DRAFT_OPTIONS_MAX} choices`)\n } else if (options.length) {\n parameter.options = (options as Record<string, unknown>[]).map((option) => {\n const value = label(option['value'], `${where}'s choice`)\n const read = label(option['label'], `${where}'s choice`)\n return read && read !== value ? { value, label: read } : { value }\n })\n }\n }\n return parameter\n })\n const variables = list('variables', 'locals', FUNCTION_DRAFT_LOCALS_MAX).map((raw) => ({\n name: ownName(raw['name'], 'local'),\n type: typeOf(raw['type']),\n }))\n\n const expression = (raw: unknown, where: string): string => {\n const value = text(raw)\n if (!value) {\n problems.push(`${where} is empty`)\n return value\n }\n if (value.length > FUNCTION_DRAFT_EXPRESSION_MAX || MARKUP.test(value)) {\n problems.push(`${where} is not an expression the function can run`)\n return value\n }\n const syntax = expressionSyntaxError(value)\n if (syntax) problems.push(`${where} cannot be read: ${syntax}`)\n return value\n }\n let assignments = 0\n const sets = (raw: unknown, where: string): HostFunction['operations'][number]['then'] => {\n if (raw !== undefined && !Array.isArray(raw)) {\n problems.push(`${where} is a list of assignments`)\n return []\n }\n return ((raw ?? []) as unknown[]).map((set, index) => {\n const at = `${where}, assignment ${index + 1}`\n const entry = isRecord(set) ? set : {}\n assignments += 1\n const target = text(entry['set'])\n if (!own.has(target)) problems.push(`${at} sets ${target || 'nothing'}, which is not one of the function's parameters or locals`)\n const workflow = text(entry['workflow'])\n // A value a workflow computes needs no expression; the editor keeps one anyway.\n const written = workflow && !text(entry['expression']) ? '' : expression(entry['expression'], at)\n return { set: target, expression: written, ...(workflow ? { workflow } : {}) }\n })\n }\n const operations = list('operations', 'operations', FUNCTION_MAX_OPERATIONS, 1).map((raw, index) => {\n const where = `Operation ${index + 1}`\n const condition = isRecord(raw['if']) ? raw['if'] : {}\n const comparator = condition['comparator']\n if (!(COMPARATORS as readonly unknown[]).includes(comparator)) {\n problems.push(`${where}'s condition compares with one of ${COMPARATORS.join(' ')}`)\n }\n return {\n if: {\n left: expression(condition['left'], `${where}'s condition (left)`),\n comparator: ((COMPARATORS as readonly unknown[]).includes(comparator) ? comparator : '==') as FunctionComparator,\n right: expression(condition['right'], `${where}'s condition (right)`),\n },\n then: sets(raw['then'], `${where} (then)`),\n otherwise: sets(raw['otherwise'], `${where} (otherwise)`),\n }\n })\n if (assignments > FUNCTION_MAX_OPERATIONS) {\n problems.push(`A function makes at most ${FUNCTION_MAX_OPERATIONS} assignments`)\n }\n const returnValue = text(content['returnValue'])\n if (!own.has(returnValue)) problems.push('The return value names one of the function’s own parameters or locals')\n\n if (problems.length) return { ok: false, problems: [...new Set(problems)] }\n return { ok: true, value: { name, parameters, variables, operations, returnValue } }\n}\n\n/** What the writer reports about a function: its size, and the site variables it reads. */\nexport function functionDraftFacts(definition: Partial<HostFunction>): Readonly<Record<string, unknown>> {\n return {\n parameters: definition.parameters?.length ?? 0,\n operations: definition.operations?.length ?? 0,\n reads: functionReferencedNames({\n name: definition.name ?? '',\n parameters: definition.parameters ?? [],\n variables: definition.variables ?? [],\n operations: definition.operations ?? [],\n }),\n }\n}\n\n/** Whether content is a function this plugin would store, with what it says about it. Pure. */\nexport function checkFunctionDraftContent(content: Readonly<Record<string, unknown>>): PluginDraftCheck {\n const read = readFunctionDraftContent(content)\n if (read.ok === false) return read\n return { ok: true, facts: functionDraftFacts(read.value) }\n}\n"],"names":["expressionSyntaxError","FUNCTION_MAX_OPERATIONS","functionReferencedNames","VARIABLE_NAME_PATTERN","VARIABLE_DRAFT_RESOURCE","FUNCTION_DRAFT_RESOURCE","LOGIC_DRAFT_NAME_MAX","VARIABLE_DRAFT_VALUE_MAX","FUNCTION_DRAFT_PARAMETERS_MAX","FUNCTION_DRAFT_LOCALS_MAX","FUNCTION_DRAFT_EXPRESSION_MAX","FUNCTION_DRAFT_LABEL_MAX","FUNCTION_DRAFT_OPTIONS_MAX","VARIABLE_DRAFT_TYPES","VALUE_TYPES","COMPARATORS","MARKUP","TIME","isRecord","value","Array","isArray","text","trim","logicDraftNameProblem","noun","name","test","logicNameKey","toLowerCase","variableDraftValueProblem","type","length","Number","isFinite","isNaN","Date","getTime","parsed","JSON","parse","undefined","readVariableDraftContent","content","problems","nameProblem","push","includes","join","rawValue","valueProblem","ok","workflowId","workflowName","checkVariableDraftContent","read","facts","variableDraftFacts","variable","readFunctionDraftContent","own","Set","ownName","raw","what","has","add","typeOf","list","key","max","min","some","entry","slice","label","where","parameters","map","index","parameter","required","shown","start","defaultValue","options","option","variables","expression","syntax","assignments","sets","set","at","target","workflow","written","operations","condition","comparator","if","left","right","then","otherwise","returnValue","functionDraftFacts","definition","reads","checkFunctionDraftContent"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
18
+ /**
19
+ * A VARIABLE OR A FUNCTION ANOTHER PLUGIN ASKS FOR (AGL-3616).
20
+ *
21
+ * The draft writers this plugin registers for the `variable` and `function`
22
+ * resources on the core's resource-drafts seam. An AI build that sets a site
23
+ * up from a brief asks for one by name and gets this plugin's rules:
24
+ *
25
+ * - THE DOCUMENT is the one the Variables and Functions cards create through
26
+ * `/api/hosts/resources`: the fields the plugin declares for the kind in
27
+ * `plugins.config.json`, stamped `createdAt`, `updatedAt` and `createdBy`.
28
+ * A variable is never computed here (`workflowId` and `workflowName` are
29
+ * empty, as the card writes a plain one).
30
+ * - THE ROLE is a member who may write the site's content, refused in the
31
+ * resources route's words. THE ROOM is `variablesPerHost` or
32
+ * `functionsPerHost`, counted as that route counts it — every document in
33
+ * the collection — inside the transaction that creates. Logic carries no
34
+ * plan feature of its own: the Free plan has three variables and one
35
+ * function.
36
+ * - THE NAME is held to the binding grammar (`logic-draft-content.ts`) and
37
+ * must be free: no LIVE record of the kind on the site carries it, compared
38
+ * case-insensitively as both cards compare it. A taken name is REFUSED,
39
+ * never overwritten and never renumbered — a binding names what it reads,
40
+ * so a variable quietly called `price_2` is one no page asked for.
41
+ *
42
+ * ## Draft by default, without a draft state
43
+ *
44
+ * Neither resource has one: a variable or a function is live the moment it
45
+ * exists. It is still a draft in every sense the seam means, because a NEW,
46
+ * UNREFERENCED variable or function changes nothing a visitor sees. A page
47
+ * shows a variable only where a binding names its id (`{{var:id}}`) and runs
48
+ * a function only where a token or a Function Widget names it, and nothing
49
+ * names a document that did not exist a moment ago. What a person reviews is
50
+ * the record itself, on the Logic page, before anything binds it.
51
+ *
52
+ * Asked again under the same id, the writer reports the record it already
53
+ * wrote and writes nothing. Nothing is published, and the writer touches the
54
+ * one new document.
55
+ */
56
+ type Firestore = FirebaseFirestore.Firestore;
57
+ /** The resources route's refusal for a member who may not write the site. */
58
+ export declare const LOGIC_DRAFT_ROLE_REFUSAL = "Editing requires the editor role";
59
+ /** The resources route's refusal at the plan's allowance. */
60
+ export declare function logicDraftLimitRefusal(noun: 'variable' | 'function', limit: number): string;
61
+ /** The refusal for a name a live record of the kind already carries. */
62
+ export declare function logicDraftNameTaken(noun: 'variable' | 'function', name: string): string;
63
+ export interface LogicDraftWriterDeps {
64
+ /** The Admin SDK handle; specs hand in a double. */
65
+ firestore?: () => Firestore;
66
+ }
67
+ /** The `variable` writer. */
68
+ export declare function createVariableDraftWriter(deps?: LogicDraftWriterDeps): PluginResourceDraftWriter;
69
+ /** The `function` writer. */
70
+ export declare function createFunctionDraftWriter(deps?: LogicDraftWriterDeps): PluginResourceDraftWriter;
71
+ export declare const variableDraftWriter: PluginResourceDraftWriter;
72
+ export declare const functionDraftWriter: PluginResourceDraftWriter;
73
+ export {};
@@ -0,0 +1,168 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
18
+ import { checkQuota } from "@aglyn/aglyn/app-utils/plan-entitlements";
19
+ import { firebaseAdmin } from "@aglyn/tenant-data-admin";
20
+ import { checkFunctionDraftContent, checkVariableDraftContent, functionDraftFacts, logicNameKey, readFunctionDraftContent, readVariableDraftContent, variableDraftFacts } from "./logic-draft-content.js";
21
+ /** The resources route's refusal for a member who may not write the site. */ export const LOGIC_DRAFT_ROLE_REFUSAL = 'Editing requires the editor role';
22
+ /** The resources route's refusal at the plan's allowance. */ export function logicDraftLimitRefusal(noun, limit) {
23
+ return `Your plan includes ${limit} ${limit === 1 ? noun : `${noun}s`} — upgrade in Billing for more`;
24
+ }
25
+ /** The refusal for a name a live record of the kind already carries. */ export function logicDraftNameTaken(noun, name) {
26
+ return `This site already has a ${noun} named ${name}. Choose another name.`;
27
+ }
28
+ function roleRefusal(host, uid) {
29
+ var _host_get;
30
+ const role = ((_host_get = host.get('memberRoles')) != null ? _host_get : {})[uid];
31
+ return hostRoleCanWrite(role) ? null : {
32
+ status: 403,
33
+ error: LOGIC_DRAFT_ROLE_REFUSAL
34
+ };
35
+ }
36
+ function createLogicDraftWriter(kind, deps) {
37
+ var _deps_firestore;
38
+ const firestore = (_deps_firestore = deps.firestore) != null ? _deps_firestore : ()=>firebaseAdmin.app().firestore();
39
+ const roomRefusal = (org, used)=>{
40
+ const quota = checkQuota(org, kind.quota, used);
41
+ return quota.allowed ? null : {
42
+ status: 403,
43
+ error: logicDraftLimitRefusal(kind.noun, quota.limit)
44
+ };
45
+ };
46
+ const recordOf = (snapshot)=>{
47
+ var _snapshot_data, _data_name;
48
+ const data = (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {};
49
+ return {
50
+ id: snapshot.id,
51
+ name: String((_data_name = data.name) != null ? _data_name : ''),
52
+ versionId: null,
53
+ facts: kind.facts(data)
54
+ };
55
+ };
56
+ return {
57
+ refusal: async (context)=>{
58
+ const hostRef = firestore().collection('hosts').doc(context.hostId);
59
+ const host = await hostRef.get();
60
+ if (!host.exists) return {
61
+ status: 404,
62
+ error: 'Unknown site'
63
+ };
64
+ const role = roleRefusal(host, context.uid);
65
+ if (role) return role;
66
+ const used = (await hostRef.collection(kind.collection).count().get()).data().count;
67
+ return roomRefusal(context.org, Number(used) || 0);
68
+ },
69
+ check: (content)=>kind.check(content),
70
+ read: async ({ hostId, id })=>{
71
+ const snapshot = await firestore().collection('hosts').doc(hostId).collection(kind.collection).doc(id).get();
72
+ return snapshot.exists ? recordOf(snapshot) : null;
73
+ },
74
+ write: async (request)=>{
75
+ // The content's name is the one a binding reads; the request's stands
76
+ // in only where the content carries none.
77
+ const named = typeof request.content['name'] === 'string' && request.content['name'].trim();
78
+ const read = kind.read(named ? request.content : _extends({}, request.content, {
79
+ name: request.name
80
+ }));
81
+ if (read.ok === false) return {
82
+ ok: false,
83
+ status: 400,
84
+ error: read.problems[0]
85
+ };
86
+ const document = read.value;
87
+ const db = firestore();
88
+ const hostRef = db.collection('hosts').doc(request.hostId);
89
+ const records = hostRef.collection(kind.collection);
90
+ const ref = records.doc(request.id);
91
+ return db.runTransaction(async (tx)=>{
92
+ // Every read before any write, which Firestore requires.
93
+ const [host, existing] = await Promise.all([
94
+ tx.get(hostRef),
95
+ tx.get(ref)
96
+ ]);
97
+ if (!host.exists) return {
98
+ ok: false,
99
+ status: 404,
100
+ error: 'Unknown site'
101
+ };
102
+ // Asked again under the same id: the record it already wrote.
103
+ if (existing.exists) return _extends({
104
+ ok: true,
105
+ replayed: true
106
+ }, recordOf(existing));
107
+ const role = roleRefusal(host, request.uid);
108
+ if (role) return _extends({
109
+ ok: false
110
+ }, role);
111
+ // One projected read answers both: the route counts every document
112
+ // for the allowance, and the cards compare names among the live ones.
113
+ const rows = (await tx.get(records.select('name', 'deletedAt'))).docs;
114
+ const room = roomRefusal(request.org, rows.length);
115
+ if (room) return _extends({
116
+ ok: false
117
+ }, room);
118
+ const key = logicNameKey(document.name);
119
+ const taken = rows.some((row)=>{
120
+ var _row_get;
121
+ return row.get('deletedAt') == null && logicNameKey(String((_row_get = row.get('name')) != null ? _row_get : '')) === key;
122
+ });
123
+ if (taken) return {
124
+ ok: false,
125
+ status: 409,
126
+ error: logicDraftNameTaken(kind.noun, document.name)
127
+ };
128
+ tx.create(ref, _extends({}, document, {
129
+ createdAt: request.now,
130
+ updatedAt: request.now,
131
+ createdBy: request.uid
132
+ }));
133
+ return {
134
+ ok: true,
135
+ replayed: false,
136
+ id: request.id,
137
+ name: document.name,
138
+ versionId: null,
139
+ facts: kind.facts(document)
140
+ };
141
+ });
142
+ }
143
+ };
144
+ }
145
+ /** The `variable` writer. */ export function createVariableDraftWriter(deps = {}) {
146
+ return createLogicDraftWriter({
147
+ noun: 'variable',
148
+ collection: 'variables',
149
+ quota: 'variablesPerHost',
150
+ read: readVariableDraftContent,
151
+ check: checkVariableDraftContent,
152
+ facts: variableDraftFacts
153
+ }, deps);
154
+ }
155
+ /** The `function` writer. */ export function createFunctionDraftWriter(deps = {}) {
156
+ return createLogicDraftWriter({
157
+ noun: 'function',
158
+ collection: 'functions',
159
+ quota: 'functionsPerHost',
160
+ read: readFunctionDraftContent,
161
+ check: checkFunctionDraftContent,
162
+ facts: functionDraftFacts
163
+ }, deps);
164
+ }
165
+ export const variableDraftWriter = createVariableDraftWriter();
166
+ export const functionDraftWriter = createFunctionDraftWriter();
167
+
168
+ //# sourceMappingURL=logic-drafts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/server/logic-drafts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type {\n PluginDraftCheck,\n PluginDraftContext,\n PluginDraftRecord,\n PluginDraftRefusal,\n PluginDraftWrite,\n PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n checkFunctionDraftContent,\n checkVariableDraftContent,\n functionDraftFacts,\n type LogicDraftRead,\n logicNameKey,\n readFunctionDraftContent,\n readVariableDraftContent,\n variableDraftFacts,\n} from './logic-draft-content'\n\n/**\n * A VARIABLE OR A FUNCTION ANOTHER PLUGIN ASKS FOR (AGL-3616).\n *\n * The draft writers this plugin registers for the `variable` and `function`\n * resources on the core's resource-drafts seam. An AI build that sets a site\n * up from a brief asks for one by name and gets this plugin's rules:\n *\n * - THE DOCUMENT is the one the Variables and Functions cards create through\n * `/api/hosts/resources`: the fields the plugin declares for the kind in\n * `plugins.config.json`, stamped `createdAt`, `updatedAt` and `createdBy`.\n * A variable is never computed here (`workflowId` and `workflowName` are\n * empty, as the card writes a plain one).\n * - THE ROLE is a member who may write the site's content, refused in the\n * resources route's words. THE ROOM is `variablesPerHost` or\n * `functionsPerHost`, counted as that route counts it — every document in\n * the collection — inside the transaction that creates. Logic carries no\n * plan feature of its own: the Free plan has three variables and one\n * function.\n * - THE NAME is held to the binding grammar (`logic-draft-content.ts`) and\n * must be free: no LIVE record of the kind on the site carries it, compared\n * case-insensitively as both cards compare it. A taken name is REFUSED,\n * never overwritten and never renumbered — a binding names what it reads,\n * so a variable quietly called `price_2` is one no page asked for.\n *\n * ## Draft by default, without a draft state\n *\n * Neither resource has one: a variable or a function is live the moment it\n * exists. It is still a draft in every sense the seam means, because a NEW,\n * UNREFERENCED variable or function changes nothing a visitor sees. A page\n * shows a variable only where a binding names its id (`{{var:id}}`) and runs\n * a function only where a token or a Function Widget names it, and nothing\n * names a document that did not exist a moment ago. What a person reviews is\n * the record itself, on the Logic page, before anything binds it.\n *\n * Asked again under the same id, the writer reports the record it already\n * wrote and writes nothing. Nothing is published, and the writer touches the\n * one new document.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The resources route's refusal for a member who may not write the site. */\nexport const LOGIC_DRAFT_ROLE_REFUSAL = 'Editing requires the editor role'\n\n/** The resources route's refusal at the plan's allowance. */\nexport function logicDraftLimitRefusal(noun: 'variable' | 'function', limit: number): string {\n return `Your plan includes ${limit} ${limit === 1 ? noun : `${noun}s`} — upgrade in Billing for more`\n}\n\n/** The refusal for a name a live record of the kind already carries. */\nexport function logicDraftNameTaken(noun: 'variable' | 'function', name: string): string {\n return `This site already has a ${noun} named ${name}. Choose another name.`\n}\n\n/** How one kind differs from the other. */\ninterface LogicDraftKind<T extends { name: string }> {\n noun: 'variable' | 'function'\n collection: 'variables' | 'functions'\n quota: 'variablesPerHost' | 'functionsPerHost'\n read: (content: Readonly<Record<string, unknown>>) => LogicDraftRead<T>\n check: (content: Readonly<Record<string, unknown>>) => PluginDraftCheck\n facts: (document: Partial<T>) => Readonly<Record<string, unknown>>\n}\n\nexport interface LogicDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n}\n\nfunction roleRefusal(host: FirebaseFirestore.DocumentSnapshot, uid: string): PluginDraftRefusal | null {\n const role = (host.get('memberRoles') ?? {})[uid]\n return hostRoleCanWrite(role) ? null : { status: 403, error: LOGIC_DRAFT_ROLE_REFUSAL }\n}\n\nfunction createLogicDraftWriter<T extends { name: string }>(\n kind: LogicDraftKind<T>,\n deps: LogicDraftWriterDeps,\n): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const roomRefusal = (org: PluginDraftContext['org'], used: number): PluginDraftRefusal | null => {\n const quota = checkQuota(org as never, kind.quota, used)\n return quota.allowed ? null : { status: 403, error: logicDraftLimitRefusal(kind.noun, quota.limit) }\n }\n const recordOf = (snapshot: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord => {\n const data = (snapshot.data() ?? {}) as Partial<T>\n return { id: snapshot.id, name: String(data.name ?? ''), versionId: null, facts: kind.facts(data) }\n }\n return {\n refusal: async (context) => {\n const hostRef = firestore().collection('hosts').doc(context.hostId)\n const host = await hostRef.get()\n if (!host.exists) return { status: 404, error: 'Unknown site' }\n const role = roleRefusal(host, context.uid)\n if (role) return role\n const used = (await hostRef.collection(kind.collection).count().get()).data().count\n return roomRefusal(context.org, Number(used) || 0)\n },\n\n check: (content) => kind.check(content),\n\n read: async ({ hostId, id }) => {\n const snapshot = await firestore().collection('hosts').doc(hostId).collection(kind.collection).doc(id).get()\n return snapshot.exists ? recordOf(snapshot) : null\n },\n\n write: async (request): Promise<PluginDraftWrite> => {\n // The content's name is the one a binding reads; the request's stands\n // in only where the content carries none.\n const named = typeof request.content['name'] === 'string' && request.content['name'].trim()\n const read = kind.read(named ? request.content : { ...request.content, name: request.name })\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n const document = read.value\n const db = firestore()\n const hostRef = db.collection('hosts').doc(request.hostId)\n const records = hostRef.collection(kind.collection)\n const ref = records.doc(request.id)\n return db.runTransaction(async (tx): Promise<PluginDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing] = await Promise.all([tx.get(hostRef), tx.get(ref)])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n // Asked again under the same id: the record it already wrote.\n if (existing.exists) return { ok: true, replayed: true, ...recordOf(existing) }\n const role = roleRefusal(host, request.uid)\n if (role) return { ok: false, ...role }\n // One projected read answers both: the route counts every document\n // for the allowance, and the cards compare names among the live ones.\n const rows = (await tx.get(records.select('name', 'deletedAt'))).docs\n const room = roomRefusal(request.org, rows.length)\n if (room) return { ok: false, ...room }\n const key = logicNameKey(document.name)\n const taken = rows.some(\n (row) => row.get('deletedAt') == null && logicNameKey(String(row.get('name') ?? '')) === key,\n )\n if (taken) return { ok: false, status: 409, error: logicDraftNameTaken(kind.noun, document.name) }\n tx.create(ref, {\n ...document,\n createdAt: request.now,\n updatedAt: request.now,\n createdBy: request.uid,\n })\n return {\n ok: true,\n replayed: false,\n id: request.id,\n name: document.name,\n versionId: null,\n facts: kind.facts(document),\n }\n })\n },\n }\n}\n\n/** The `variable` writer. */\nexport function createVariableDraftWriter(deps: LogicDraftWriterDeps = {}): PluginResourceDraftWriter {\n return createLogicDraftWriter(\n {\n noun: 'variable',\n collection: 'variables',\n quota: 'variablesPerHost',\n read: readVariableDraftContent,\n check: checkVariableDraftContent,\n facts: variableDraftFacts,\n },\n deps,\n )\n}\n\n/** The `function` writer. */\nexport function createFunctionDraftWriter(deps: LogicDraftWriterDeps = {}): PluginResourceDraftWriter {\n return createLogicDraftWriter(\n {\n noun: 'function',\n collection: 'functions',\n quota: 'functionsPerHost',\n read: readFunctionDraftContent,\n check: checkFunctionDraftContent,\n facts: functionDraftFacts,\n },\n deps,\n )\n}\n\nexport const variableDraftWriter = createVariableDraftWriter()\nexport const functionDraftWriter = createFunctionDraftWriter()\n"],"names":["hostRoleCanWrite","checkQuota","firebaseAdmin","checkFunctionDraftContent","checkVariableDraftContent","functionDraftFacts","logicNameKey","readFunctionDraftContent","readVariableDraftContent","variableDraftFacts","LOGIC_DRAFT_ROLE_REFUSAL","logicDraftLimitRefusal","noun","limit","logicDraftNameTaken","name","roleRefusal","host","uid","role","get","status","error","createLogicDraftWriter","kind","deps","firestore","app","roomRefusal","org","used","quota","allowed","recordOf","snapshot","data","id","String","versionId","facts","refusal","context","hostRef","collection","doc","hostId","exists","count","Number","check","content","read","write","request","named","trim","ok","problems","document","value","db","records","ref","runTransaction","tx","existing","Promise","all","replayed","rows","select","docs","room","length","key","taken","some","row","create","createdAt","now","updatedAt","createdBy","createVariableDraftWriter","createFunctionDraftWriter","variableDraftWriter","functionDraftWriter"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PluginAiCapability, type PluginAiCapabilityArgs } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities';
18
+ /**
19
+ * WHAT AN AI BUILD CAN MAKE IN LOGIC (AGL-3616): a site variable.
20
+ *
21
+ * The capability the AI plugin's build planner offers for "keep my hourly
22
+ * rate in one place". Its item is written by this plugin's `variable` draft
23
+ * writer, so every rule is the writer's: the member's role, the plan's
24
+ * `variablesPerHost` allowance, the binding grammar for the name, a name no
25
+ * live variable carries, and a value in its type's stored form. No model
26
+ * runs: the planner fills the three arguments, and they ARE the variable.
27
+ *
28
+ * A variable needs no plan feature — the Free plan has three — so a Free
29
+ * workspace's build may make one. No page block depends on it: a page shows
30
+ * a variable through a binding a person places, and a build places none, so a
31
+ * new variable changes nothing a visitor sees until someone binds it.
32
+ *
33
+ * A function is not offered here: writing one takes a model, so it is the AI
34
+ * plugin's own operation, run by its `logic` step and written through this
35
+ * plugin's `function` writer.
36
+ */
37
+ /** The operation's name in a build plan. */
38
+ export declare const VARIABLE_AI_OP = "variable";
39
+ /** The writer's content for an item: its three arguments, as they are. */
40
+ export declare function variableDraftContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>>;
41
+ export declare const variableAiCapability: PluginAiCapability;
42
+ /**
43
+ * Registers the capability; the console's server declarations call it beside
44
+ * the writers, since only the console runs AI jobs (AGL-3026). Idempotent.
45
+ */
46
+ export declare function registerVariableAiCapability(): void;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { registerPluginAiCapability } from "@aglyn/aglyn/plugin-manager/plugin-ai-capabilities";
17
+ import { BUNDLE_ID } from "../constants/bundle-common.js";
18
+ import { LOGIC_DRAFT_NAME_MAX, VARIABLE_DRAFT_RESOURCE, VARIABLE_DRAFT_TYPES, VARIABLE_DRAFT_VALUE_MAX } from "./logic-draft-content.js";
19
+ /**
20
+ * WHAT AN AI BUILD CAN MAKE IN LOGIC (AGL-3616): a site variable.
21
+ *
22
+ * The capability the AI plugin's build planner offers for "keep my hourly
23
+ * rate in one place". Its item is written by this plugin's `variable` draft
24
+ * writer, so every rule is the writer's: the member's role, the plan's
25
+ * `variablesPerHost` allowance, the binding grammar for the name, a name no
26
+ * live variable carries, and a value in its type's stored form. No model
27
+ * runs: the planner fills the three arguments, and they ARE the variable.
28
+ *
29
+ * A variable needs no plan feature — the Free plan has three — so a Free
30
+ * workspace's build may make one. No page block depends on it: a page shows
31
+ * a variable through a binding a person places, and a build places none, so a
32
+ * new variable changes nothing a visitor sees until someone binds it.
33
+ *
34
+ * A function is not offered here: writing one takes a model, so it is the AI
35
+ * plugin's own operation, run by its `logic` step and written through this
36
+ * plugin's `function` writer.
37
+ */ /** The operation's name in a build plan. */ export const VARIABLE_AI_OP = 'variable';
38
+ /** The writer's content for an item: its three arguments, as they are. */ export function variableDraftContentFromArgs(args) {
39
+ return Object.fromEntries([
40
+ 'name',
41
+ 'type',
42
+ 'value'
43
+ ].filter((key)=>args[key] !== undefined).map((key)=>[
44
+ key,
45
+ args[key]
46
+ ]));
47
+ }
48
+ export const variableAiCapability = {
49
+ op: VARIABLE_AI_OP,
50
+ noun: 'site variable',
51
+ where: 'Logic → Variables',
52
+ intents: [
53
+ 'site variables: one value — a price, a phone number, an opening date — kept in one place for pages to show'
54
+ ],
55
+ argsSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ name: {
59
+ type: 'string',
60
+ description: 'The name a binding reads: a letter or _, then letters, digits or _, e.g. "hourly_rate". Not a name the site already has.',
61
+ maxLength: LOGIC_DRAFT_NAME_MAX
62
+ },
63
+ type: {
64
+ type: 'string',
65
+ description: 'What the value is.',
66
+ enum: VARIABLE_DRAFT_TYPES
67
+ },
68
+ value: {
69
+ type: 'string',
70
+ description: 'The value as stored: digits for number; true or false; YYYY-MM-DD for date; HH:MM for time; a JSON object for dictionary; a JSON list for collection.',
71
+ maxLength: VARIABLE_DRAFT_VALUE_MAX
72
+ }
73
+ },
74
+ required: [
75
+ 'name',
76
+ 'type',
77
+ 'value'
78
+ ],
79
+ additionalProperties: false
80
+ },
81
+ maxPerPlan: 6,
82
+ // Free includes three variables (`variablesPerHost: 3`), and Logic no feature.
83
+ freeAllowed: true,
84
+ quota: 'variablesPerHost',
85
+ draftResource: VARIABLE_DRAFT_RESOURCE,
86
+ // Written without a model: the planner already filled the arguments.
87
+ estimateCredits: ()=>0,
88
+ degrade: 'omit',
89
+ draftContent: (item)=>variableDraftContentFromArgs(item.args)
90
+ };
91
+ /**
92
+ * Registers the capability; the console's server declarations call it beside
93
+ * the writers, since only the console runs AI jobs (AGL-3026). Idempotent.
94
+ */ export function registerVariableAiCapability() {
95
+ registerPluginAiCapability(variableAiCapability, {
96
+ pluginId: BUNDLE_ID
97
+ });
98
+ }
99
+
100
+ //# sourceMappingURL=variable-ai-capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/server/variable-ai-capability.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n registerPluginAiCapability,\n type PluginAiCapability,\n type PluginAiCapabilityArgs,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n LOGIC_DRAFT_NAME_MAX,\n VARIABLE_DRAFT_RESOURCE,\n VARIABLE_DRAFT_TYPES,\n VARIABLE_DRAFT_VALUE_MAX,\n} from './logic-draft-content'\n\n/**\n * WHAT AN AI BUILD CAN MAKE IN LOGIC (AGL-3616): a site variable.\n *\n * The capability the AI plugin's build planner offers for \"keep my hourly\n * rate in one place\". Its item is written by this plugin's `variable` draft\n * writer, so every rule is the writer's: the member's role, the plan's\n * `variablesPerHost` allowance, the binding grammar for the name, a name no\n * live variable carries, and a value in its type's stored form. No model\n * runs: the planner fills the three arguments, and they ARE the variable.\n *\n * A variable needs no plan feature — the Free plan has three — so a Free\n * workspace's build may make one. No page block depends on it: a page shows\n * a variable through a binding a person places, and a build places none, so a\n * new variable changes nothing a visitor sees until someone binds it.\n *\n * A function is not offered here: writing one takes a model, so it is the AI\n * plugin's own operation, run by its `logic` step and written through this\n * plugin's `function` writer.\n */\n\n/** The operation's name in a build plan. */\nexport const VARIABLE_AI_OP = 'variable'\n\n/** The writer's content for an item: its three arguments, as they are. */\nexport function variableDraftContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>> {\n return Object.fromEntries(\n ['name', 'type', 'value'].filter((key) => args[key] !== undefined).map((key) => [key, args[key]]),\n )\n}\n\nexport const variableAiCapability: PluginAiCapability = {\n op: VARIABLE_AI_OP,\n noun: 'site variable',\n where: 'Logic → Variables',\n intents: [\n 'site variables: one value — a price, a phone number, an opening date — kept in one place for pages to show',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description:\n 'The name a binding reads: a letter or _, then letters, digits or _, e.g. \"hourly_rate\". Not a name the site already has.',\n maxLength: LOGIC_DRAFT_NAME_MAX,\n },\n type: {\n type: 'string',\n description: 'What the value is.',\n enum: VARIABLE_DRAFT_TYPES,\n },\n value: {\n type: 'string',\n description:\n 'The value as stored: digits for number; true or false; YYYY-MM-DD for date; HH:MM for time; a JSON object for dictionary; a JSON list for collection.',\n maxLength: VARIABLE_DRAFT_VALUE_MAX,\n },\n },\n required: ['name', 'type', 'value'],\n additionalProperties: false,\n },\n maxPerPlan: 6,\n // Free includes three variables (`variablesPerHost: 3`), and Logic no feature.\n freeAllowed: true,\n quota: 'variablesPerHost',\n draftResource: VARIABLE_DRAFT_RESOURCE,\n // Written without a model: the planner already filled the arguments.\n estimateCredits: () => 0,\n degrade: 'omit',\n draftContent: (item) => variableDraftContentFromArgs(item.args),\n}\n\n/**\n * Registers the capability; the console's server declarations call it beside\n * the writers, since only the console runs AI jobs (AGL-3026). Idempotent.\n */\nexport function registerVariableAiCapability(): void {\n registerPluginAiCapability(variableAiCapability, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerPluginAiCapability","BUNDLE_ID","LOGIC_DRAFT_NAME_MAX","VARIABLE_DRAFT_RESOURCE","VARIABLE_DRAFT_TYPES","VARIABLE_DRAFT_VALUE_MAX","VARIABLE_AI_OP","variableDraftContentFromArgs","args","Object","fromEntries","filter","key","undefined","map","variableAiCapability","op","noun","where","intents","argsSchema","type","properties","name","description","maxLength","enum","value","required","additionalProperties","maxPerPlan","freeAllowed","quota","draftResource","estimateCredits","degrade","draftContent","item","registerVariableAiCapability","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,0BAA0B,QAGrB,qDAAoD;AAC3D,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SACEC,oBAAoB,EACpBC,uBAAuB,EACvBC,oBAAoB,EACpBC,wBAAwB,QACnB,2BAAuB;AAE9B;;;;;;;;;;;;;;;;;;CAkBC,GAED,0CAA0C,GAC1C,OAAO,MAAMC,iBAAiB,WAAU;AAExC,wEAAwE,GACxE,OAAO,SAASC,6BAA6BC,IAA4B;IACvE,OAAOC,OAAOC,WAAW,CACvB;QAAC;QAAQ;QAAQ;KAAQ,CAACC,MAAM,CAAC,CAACC,MAAQJ,IAAI,CAACI,IAAI,KAAKC,WAAWC,GAAG,CAAC,CAACF,MAAQ;YAACA;YAAKJ,IAAI,CAACI,IAAI;SAAC;AAEpG;AAEA,OAAO,MAAMG,uBAA2C;IACtDC,IAAIV;IACJW,MAAM;IACNC,OAAO;IACPC,SAAS;QACP;KACD;IACDC,YAAY;QACVC,MAAM;QACNC,YAAY;YACVC,MAAM;gBACJF,MAAM;gBACNG,aACE;gBACFC,WAAWvB;YACb;YACAmB,MAAM;gBACJA,MAAM;gBACNG,aAAa;gBACbE,MAAMtB;YACR;YACAuB,OAAO;gBACLN,MAAM;gBACNG,aACE;gBACFC,WAAWpB;YACb;QACF;QACAuB,UAAU;YAAC;YAAQ;YAAQ;SAAQ;QACnCC,sBAAsB;IACxB;IACAC,YAAY;IACZ,+EAA+E;IAC/EC,aAAa;IACbC,OAAO;IACPC,eAAe9B;IACf,qEAAqE;IACrE+B,iBAAiB,IAAM;IACvBC,SAAS;IACTC,cAAc,CAACC,OAAS9B,6BAA6B8B,KAAK7B,IAAI;AAChE,EAAC;AAED;;;CAGC,GACD,OAAO,SAAS8B;IACdtC,2BAA2Be,sBAAsB;QAAEwB,UAAUtC;IAAU;AACzE"}