@sparkerp/plugin-sdk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/bundle/blocks.json +12229 -0
- package/bundle/catalog.json +32475 -0
- package/bundle/docs/applications/hcm/employee-profile.md +144 -0
- package/bundle/docs/concepts/artifact-types.md +80 -0
- package/bundle/docs/concepts/glossary.md +124 -0
- package/bundle/docs/concepts/overview.md +88 -0
- package/bundle/docs/concepts/page-data-flow.md +84 -0
- package/bundle/docs/concepts/plugin-lifecycle.md +81 -0
- package/bundle/docs/concepts/sdk-modes.md +71 -0
- package/bundle/docs/docs.json +109 -0
- package/bundle/docs/guides/add-a-cadence-job.md +134 -0
- package/bundle/docs/guides/add-a-compliance-job.md +139 -0
- package/bundle/docs/guides/add-a-create-edit-form.md +167 -0
- package/bundle/docs/guides/add-a-custom-block.md +225 -0
- package/bundle/docs/guides/add-a-data-provider.md +216 -0
- package/bundle/docs/guides/add-a-kpi.md +154 -0
- package/bundle/docs/guides/add-a-reminder-job.md +181 -0
- package/bundle/docs/guides/add-an-aggregation-job.md +153 -0
- package/bundle/docs/guides/add-an-approval-workflow.md +222 -0
- package/bundle/docs/guides/add-an-entity.md +170 -0
- package/bundle/docs/guides/add-business-rules.md +147 -0
- package/bundle/docs/guides/add-i18n.md +121 -0
- package/bundle/docs/guides/add-menus.md +149 -0
- package/bundle/docs/guides/build-a-code-plugin.md +141 -0
- package/bundle/docs/guides/build-a-page.md +213 -0
- package/bundle/docs/guides/build-a-polyglot-service-plugin.md +201 -0
- package/bundle/docs/guides/build-a-tenant-extension-service.md +149 -0
- package/bundle/docs/guides/create-a-plugin.md +172 -0
- package/bundle/docs/guides/expose-a-plugin-operation-as-an-ai-tool.md +114 -0
- package/bundle/docs/guides/index.md +53 -0
- package/bundle/docs/guides/publish-and-upgrade.md +152 -0
- package/bundle/docs/guides/responsive-plugin.md +117 -0
- package/bundle/docs/guides/seed-data-on-install.md +127 -0
- package/bundle/docs/guides/set-up-the-sdk.md +120 -0
- package/bundle/docs/guides/use-the-mcp-server.md +104 -0
- package/bundle/docs/guides/validate-and-test.md +119 -0
- package/bundle/docs/guides/wire-a-pages-data.md +153 -0
- package/bundle/docs/quickstart/index.md +118 -0
- package/bundle/docs/recipes/condition-based-compliance-flag.md +70 -0
- package/bundle/docs/recipes/cross-plugin-offboarding-fanout.md +68 -0
- package/bundle/docs/recipes/cross-row-aggregation.md +67 -0
- package/bundle/docs/recipes/entity-with-approval-workflow.md +72 -0
- package/bundle/docs/recipes/index.md +40 -0
- package/bundle/docs/recipes/install-lifecycle-data-seeding.md +76 -0
- package/bundle/docs/recipes/kpi-dashboard-page.md +92 -0
- package/bundle/docs/recipes/master-detail-page.md +84 -0
- package/bundle/docs/recipes/n-days-before-reminder.md +72 -0
- package/bundle/docs/recipes/per-row-document-generator.md +65 -0
- package/bundle/docs/recipes/related-record-lookup-field.md +74 -0
- package/bundle/docs/recipes/resolved-reference-grid-column.md +63 -0
- package/bundle/docs/recipes/scheduled-record-generation.md +68 -0
- package/bundle/docs/reference/action.md +18 -0
- package/bundle/docs/reference/block-definition.md +35 -0
- package/bundle/docs/reference/block-instance.md +33 -0
- package/bundle/docs/reference/composite-pack-manifest.md +33 -0
- package/bundle/docs/reference/connector-definition.md +36 -0
- package/bundle/docs/reference/data-service-definition.md +22 -0
- package/bundle/docs/reference/data-view-definition.md +26 -0
- package/bundle/docs/reference/entity-aggregation-config.md +38 -0
- package/bundle/docs/reference/entity-cadence-config.md +33 -0
- package/bundle/docs/reference/entity-compliance-config.md +37 -0
- package/bundle/docs/reference/entity-cross-plugin-action-config.md +37 -0
- package/bundle/docs/reference/entity-definition.md +18 -0
- package/bundle/docs/reference/entity-document-generator-config.md +35 -0
- package/bundle/docs/reference/entity-rule-definition.md +29 -0
- package/bundle/docs/reference/entity-status-date-sweep-config.md +27 -0
- package/bundle/docs/reference/form.md +35 -0
- package/bundle/docs/reference/index.md +37 -0
- package/bundle/docs/reference/menu.md +26 -0
- package/bundle/docs/reference/mobile-nav.md +22 -0
- package/bundle/docs/reference/page-template.md +29 -0
- package/bundle/docs/reference/page.md +44 -0
- package/bundle/docs/reference/plugin-manifest.md +54 -0
- package/bundle/docs/reference/plugin-seed-data.md +21 -0
- package/bundle/docs/reference/print-template-definition.md +22 -0
- package/bundle/docs/reference/provider-definition.md +22 -0
- package/bundle/docs/reference/report-definition.md +22 -0
- package/bundle/docs/reference/rule.md +30 -0
- package/bundle/docs/reference/workflow-definition.md +28 -0
- package/bundle/docs/troubleshooting/grid-shows-zero-rows.md +82 -0
- package/bundle/docs/troubleshooting/job-never-runs.md +89 -0
- package/bundle/docs/troubleshooting/known-gotchas.md +193 -0
- package/bundle/docs/troubleshooting/publishing-errors.md +119 -0
- package/bundle/docs/tutorial/01-create-the-plugin.md +157 -0
- package/bundle/docs/tutorial/02-category-entity-and-seed.md +94 -0
- package/bundle/docs/tutorial/03-equipment-entity.md +121 -0
- package/bundle/docs/tutorial/04-equipment-register-page.md +153 -0
- package/bundle/docs/tutorial/05-checkout-entity-and-page.md +116 -0
- package/bundle/docs/tutorial/06-checkout-approval-workflow.md +139 -0
- package/bundle/docs/tutorial/07-return-due-reminder-job.md +129 -0
- package/bundle/docs/tutorial/08-menus-i18n-publish.md +123 -0
- package/bundle/docs/tutorial/index.md +72 -0
- package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-aggregation-configs.json +18 -0
- package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cadence-configs.json +9 -0
- package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-compliance-configs.json +74 -0
- package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cross-plugin-configs.json +9 -0
- package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-document-configs.json +9 -0
- package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-sweep-configs.json +260 -0
- package/bundle/examples/backend/modules/hcm-compensation/spk-assembly/metadata/entities/hcm_salary_planning_entry.json +41 -0
- package/bundle/examples/backend/modules/hcm-compensation/spk-assembly/metadata/page/salary-planning.json +1 -0
- package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/page/promotions.json +10944 -0
- package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.movement.json +44 -0
- package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.promotion-acknowledge.json +19 -0
- package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.resignation.json +29 -0
- package/bundle/examples/example-patterns.mjs +94 -0
- package/bundle/manifest.json +14 -0
- package/bundle/schemas/action.schema.json +102 -0
- package/bundle/schemas/block-definition.schema.json +222 -0
- package/bundle/schemas/block-instance.schema.json +139 -0
- package/bundle/schemas/composite-pack-manifest.schema.json +160 -0
- package/bundle/schemas/connector-definition.schema.json +29 -0
- package/bundle/schemas/data-service-definition.schema.json +64 -0
- package/bundle/schemas/data-view-definition.schema.json +148 -0
- package/bundle/schemas/entity-aggregation-config.schema.json +32 -0
- package/bundle/schemas/entity-cadence-config.schema.json +27 -0
- package/bundle/schemas/entity-compliance-config.schema.json +31 -0
- package/bundle/schemas/entity-cross-plugin-action-config.schema.json +31 -0
- package/bundle/schemas/entity-definition.schema.json +126 -0
- package/bundle/schemas/entity-document-generator-config.schema.json +29 -0
- package/bundle/schemas/entity-rule-definition.schema.json +75 -0
- package/bundle/schemas/entity-status-date-sweep-config.schema.json +21 -0
- package/bundle/schemas/form.schema.json +281 -0
- package/bundle/schemas/menu.schema.json +106 -0
- package/bundle/schemas/mobile-nav.schema.json +53 -0
- package/bundle/schemas/page-template.schema.json +46 -0
- package/bundle/schemas/page.schema.json +292 -0
- package/bundle/schemas/plugin-manifest.schema.json +139 -0
- package/bundle/schemas/plugin-seed-data.schema.json +24 -0
- package/bundle/schemas/print-template-definition.schema.json +90 -0
- package/bundle/schemas/provider-definition.schema.json +27 -0
- package/bundle/schemas/report-definition.schema.json +96 -0
- package/bundle/schemas/rule.schema.json +82 -0
- package/bundle/schemas/workflow-definition.schema.json +42 -0
- package/bundle/validators/block-engine.mjs +23222 -0
- package/bundle/validators/page-engine.mjs +21645 -0
- package/erp-cli/authoring-root.mjs +344 -0
- package/erp-cli/erp.mjs +4065 -0
- package/erp-cli/example-patterns.mjs +94 -0
- package/erp-mcp-server/index.mjs +818 -0
- package/package.json +27 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://erp.platform/schema/entity-compliance-config.schema.json",
|
|
4
|
+
"title": "entity_compliance_config row",
|
|
5
|
+
"description": "One config row for the generic engine-entity.compliance-sweep job (EntityComplianceSweepJob, Batch 3, 2026-09-10): evaluate a rule-engine boolean expression against every row of source_entity and either flag the offending row (set_field/set_value) or raise a deduped escalation row (escalation_entity). Reuses the platform ExpressionEvaluator (same MVEL evaluator as cross-field validation / Formula fields). Ship via metadata/seed-data/*.json. See ai/patterns/generic-compliance-sweep-job.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["compliance_code", "source_entity", "condition_expr", "active"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"compliance_code": { "type": "string", "description": "Stable identifier / natural key for idempotent seeding." },
|
|
11
|
+
"source_entity": { "type": "string", "description": "Entity evaluated row-by-row." },
|
|
12
|
+
"status_field": { "type": "string", "default": "status" },
|
|
13
|
+
"when_status_in": { "type": "string", "description": "Optional comma-separated status pre-filter." },
|
|
14
|
+
"filter_field": { "type": "string", "description": "Optional single-column equality pre-filter." },
|
|
15
|
+
"filter_value": { "type": "string" },
|
|
16
|
+
"related_count_entity": { "type": "string", "description": "Optional: count child rows of this entity whose related_count_key_field equals the source row's id, and bind the result as `related_count` in the expression context." },
|
|
17
|
+
"related_count_key_field": { "type": "string", "description": "The FK column on related_count_entity that points back to the source row's id." },
|
|
18
|
+
"related_count_status_field": { "type": "string", "default": "status" },
|
|
19
|
+
"related_count_status_in": { "type": "string", "description": "Optional comma-separated status filter for the related count." },
|
|
20
|
+
"condition_expr": { "type": "string", "description": "MVEL boolean expression evaluated against each row's fields (+ `now`, + `related_count`). Whitelisted functions only (daysBetween, now, etc.). True => apply the outcome." },
|
|
21
|
+
"set_field": { "type": "string", "description": "Flag outcome: column on the source row to write when the condition is true (idempotent — skipped if already equal to set_value)." },
|
|
22
|
+
"set_value": { "type": "string", "description": "Flag outcome: value written into set_field." },
|
|
23
|
+
"escalation_entity": { "type": "string", "description": "Escalation outcome: create one row in this separate entity per violating source row, deduped by escalation_dedupe_field = source id." },
|
|
24
|
+
"escalation_dedupe_field": { "type": "string", "description": "Column on escalation_entity that holds the source row id (used for dedupe)." },
|
|
25
|
+
"escalation_timestamp_field": { "type": "string", "description": "Optional column on escalation_entity that receives `now` on create." },
|
|
26
|
+
"field_map_json": { "type": "object", "description": "Escalation outcome: {targetField: sourceField} — copies source row values into the new escalation row." },
|
|
27
|
+
"escalation_template_json": { "type": "object", "description": "Escalation outcome: static fields set on every created escalation row." },
|
|
28
|
+
"seeded_by": { "type": "string" },
|
|
29
|
+
"active": { "type": "boolean" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://erp.platform/schema/entity-cross-plugin-action-config.schema.json",
|
|
4
|
+
"title": "entity_cross_plugin_action_config row",
|
|
5
|
+
"description": "One config row for the generic engine-entity.cross-plugin-action job (EntityCrossPluginActionJob, Batch 5+6, 2026-09-10): a row of trigger_entity entering a triggering status fans an action out onto every related_entity row keyed back to it - creating a row in create_entity and/or updating the related row itself - deduped so re-runs are safe. trigger_entity may be owned by another plugin (e.g. employee); no cross-plugin HTTP, both are Entity-Engine entities in the same tenant. Ship via metadata/seed-data/*.json. See ai/patterns/generic-cross-plugin-action-job.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["action_code", "trigger_entity", "related_entity", "related_match_field", "active"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"action_code": { "type": "string", "description": "Stable identifier / natural key for idempotent seeding." },
|
|
11
|
+
"trigger_entity": { "type": "string", "description": "Entity A - the record whose status change triggers the fan-out (often owned by another plugin, e.g. employee)." },
|
|
12
|
+
"trigger_status_field": { "type": "string", "default": "status" },
|
|
13
|
+
"trigger_status_in": { "type": "string", "description": "Comma-separated statuses that make a trigger row eligible (e.g. TERMINATED,SEPARATED). Blank = every row." },
|
|
14
|
+
"trigger_date_field": { "type": "string", "description": "Optional date gate column on the trigger row (e.g. separation_date)." },
|
|
15
|
+
"trigger_date_compare_op": { "type": "string", "enum": ["lte", "lt", "gte", "gt"], "default": "lte" },
|
|
16
|
+
"trigger_day_offset": { "type": "integer", "default": 0, "description": "Compare trigger_date_field against now + this many days." },
|
|
17
|
+
"trigger_key_field": { "type": "string", "default": "id", "description": "Field on the trigger row whose value matches related rows (usually id -> employee id)." },
|
|
18
|
+
"related_entity": { "type": "string", "description": "Entity B - the rows acted on." },
|
|
19
|
+
"related_match_field": { "type": "string", "description": "FK column on related_entity that equals the trigger row's key (e.g. employee_id)." },
|
|
20
|
+
"related_status_field": { "type": "string", "default": "status" },
|
|
21
|
+
"related_status_in": { "type": "string", "description": "Comma-separated statuses a related row must be in to be acted on (e.g. ALLOCATED,ACKNOWLEDGED,IN_USE)." },
|
|
22
|
+
"create_entity": { "type": "string", "description": "Create-outcome: entity a row is created in per matched related row (e.g. hcm_asset_return). Omit to only update the related row." },
|
|
23
|
+
"create_dedupe_field": { "type": "string", "description": "Numeric (long) column on create_entity holding the related row's id - dedupes re-runs. MUST be numeric, not text (EntityRecordService binds numeric-looking filter values as bigint)." },
|
|
24
|
+
"create_template_json": { "type": "object", "description": "Static fields set on every created row. String values may contain ${date} ${ts} ${uuid} ${relatedId} ${triggerKey} - substituted per created row so a required-unique column (e.g. return_number) never collides." },
|
|
25
|
+
"create_field_map_json": { "type": "object", "description": "{targetField: relatedField} - copies related row values into the created row." },
|
|
26
|
+
"related_set_field": { "type": "string", "description": "Update-outcome: column on the related row to write (e.g. status). Idempotent - skipped when already equal to related_set_value." },
|
|
27
|
+
"related_set_value": { "type": "string", "description": "Value written into related_set_field (e.g. RETURN_PENDING)." },
|
|
28
|
+
"seeded_by": { "type": "string" },
|
|
29
|
+
"active": { "type": "boolean" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://erp.internal/schema/entity-definition.schema.json",
|
|
4
|
+
"title": "EntityDefinition",
|
|
5
|
+
"description": "One tenant-authored entity (table) definition understood by engine-entity's EntityDdlGenerator. Mirrors the Java record com.erp.platform.engine.entity.EntityDefinition plus its child EntityFieldDefinition/EntityRelationshipDefinition rows. See ai/patterns/entity-engine-real-module.md for the authoring recipe. Two real, distinct on-the-wire shapes exist and both validate here (FIXED 2026-09-03 — the schema previously modeled ONLY shape A, so `erp schema validate --schema entity-definition` FAILED on every already-shipped spk-assembly/metadata/entities/*.json file in this repo, a real, previously-undisclosed bug found live authoring hcm-learning's Course Catalog batch): (A) the flat POST /api/v1/entities request body (fields added separately via POST .../fields) — entity properties top-level, no `fields` array required; (B) the packaged install-time artifact PluginEntityInstaller actually reads from spk-assembly/metadata/entities/*.json — a `{\"entity\": {...same entity properties...}, \"fields\": [...]}` wrapper, fields inline. dataType is accepted case-insensitively (both the UPPERCASE enum below and the lowercase spelling — e.g. \"text\"/\"TEXT\" — every real shipped file on disk actually uses, confirmed live).",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{ "$ref": "#/definitions/flatEntity" },
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["entity", "fields"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"entity": { "$ref": "#/definitions/entityProps" },
|
|
13
|
+
"fields": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/field" } },
|
|
14
|
+
"relationships": { "type": "array", "items": { "$ref": "#/definitions/relationship" } }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"definitions": {
|
|
19
|
+
"entityProps": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["name", "label"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"name": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"pattern": "^[a-z][a-z0-9_]*$",
|
|
26
|
+
"description": "Logical entity name; becomes the physical table name unless tableName is set."
|
|
27
|
+
},
|
|
28
|
+
"tableName": { "type": "string", "description": "Physical table name override; defaults to name." },
|
|
29
|
+
"label": { "type": "string", "description": "entity_definition.label is a jsonb column (server-side `cast(? as jsonb)`) — this string must itself BE valid JSON, e.g. an i18n object \"{\\\"en\\\":\\\"Announcement\\\"}\", not a bare display string like \"Announcement\" (that fails install with 'invalid input syntax for type json'; found live authoring hcm-communications). Same requirement applies to flatEntity.label and field.label below." },
|
|
30
|
+
"description": { "type": "string" },
|
|
31
|
+
"category": { "type": "string" },
|
|
32
|
+
"icon": { "type": "string" },
|
|
33
|
+
"color": { "type": "string" },
|
|
34
|
+
"cacheable": { "type": "boolean", "default": false },
|
|
35
|
+
"fullTextSearch": { "type": "boolean", "default": false },
|
|
36
|
+
"softDeleteEnabled": { "type": "boolean", "default": false },
|
|
37
|
+
"status": { "type": "string", "enum": ["DRAFT", "PUBLISHED", "DEPRECATED"], "default": "DRAFT" },
|
|
38
|
+
"pkStrategy": { "type": "string", "enum": ["IDENTITY", "SEQUENCE", "identity", "sequence"], "default": "IDENTITY" },
|
|
39
|
+
"pkSequenceName": { "type": "string" },
|
|
40
|
+
"schemaName": { "type": "string", "description": "App-owned Postgres schema this entity's table routes to — see feedback-entity-engine-tables-must-route-to-app-schema." },
|
|
41
|
+
"designerMetadata": { "type": "string" }
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"flatEntity": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"required": ["name", "label", "fields"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"name": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
|
|
49
|
+
"tableName": { "type": "string" },
|
|
50
|
+
"label": { "type": "string", "description": "See entityProps.label above — same jsonb-encoding requirement (this flatEntity shape is normally the live POST-body path, where the server may accept a plain string; the packaged spk-assembly artifact path does not)." },
|
|
51
|
+
"description": { "type": "string" },
|
|
52
|
+
"category": { "type": "string" },
|
|
53
|
+
"icon": { "type": "string" },
|
|
54
|
+
"color": { "type": "string" },
|
|
55
|
+
"cacheable": { "type": "boolean", "default": false },
|
|
56
|
+
"fullTextSearch": { "type": "boolean", "default": false },
|
|
57
|
+
"softDeleteEnabled": { "type": "boolean", "default": false },
|
|
58
|
+
"status": { "type": "string", "enum": ["DRAFT", "PUBLISHED", "DEPRECATED"], "default": "DRAFT" },
|
|
59
|
+
"pkStrategy": { "type": "string", "enum": ["IDENTITY", "SEQUENCE"], "default": "IDENTITY" },
|
|
60
|
+
"pkSequenceName": { "type": "string" },
|
|
61
|
+
"schemaName": { "type": "string" },
|
|
62
|
+
"fields": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/field" } },
|
|
63
|
+
"relationships": { "type": "array", "items": { "$ref": "#/definitions/relationship" } }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"field": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"required": ["fieldName", "label", "dataType"],
|
|
69
|
+
"properties": {
|
|
70
|
+
"fieldName": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
|
|
71
|
+
"label": { "type": "string", "description": "entity_field_definition.label is a jsonb column too — pass a JSON-encoded string, e.g. \"\\\"Code\\\"\" (a JSON string literal, with escaped quotes) not a bare \"Code\". See entityProps.label above for the same requirement and the real install error it throws when violated." },
|
|
72
|
+
"description": { "type": "string" },
|
|
73
|
+
"dataType": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": [
|
|
76
|
+
"TEXT", "NUMBER", "INTEGER", "BOOLEAN", "DATE", "DATETIME", "JSON", "REFERENCE", "FORMULA", "ENUM",
|
|
77
|
+
"text", "number", "integer", "long", "decimal", "currency", "boolean", "date", "datetime", "json", "reference", "formula", "enum", "string", "email", "file", "image", "uuid", "binary", "time"
|
|
78
|
+
],
|
|
79
|
+
"description": "The real EntityDataType Java enum (STRING/TEXT/INTEGER/LONG/DECIMAL/CURRENCY/BOOLEAN/DATE/DATETIME/TIME/JSON/ENUM/EMAIL/FILE/IMAGE/UUID/BINARY) accepts its value case-insensitively at install time; every real shipped spk-assembly/metadata/entities/*.json file on disk today spells it lowercase."
|
|
80
|
+
},
|
|
81
|
+
"typeParams": {
|
|
82
|
+
"description": "Type-specific config — either a JSON object, or (as every real shipped file on disk actually authors it) that same object pre-serialized as a JSON string, e.g. \"{\\\"length\\\": 50}\" / \"{\\\"enumValues\\\": [\\\"A\\\",\\\"B\\\"]}\".",
|
|
83
|
+
"oneOf": [{ "type": "object" }, { "type": "string" }]
|
|
84
|
+
},
|
|
85
|
+
"nullable": { "type": "boolean", "default": true },
|
|
86
|
+
"required": { "type": "boolean", "default": false },
|
|
87
|
+
"defaultValue": { "type": "string" },
|
|
88
|
+
"displayOrder": { "type": "integer" },
|
|
89
|
+
"isPrimaryKey": { "type": "boolean", "default": false },
|
|
90
|
+
"isUnique": { "type": "boolean", "default": false },
|
|
91
|
+
"isIndexed": { "type": "boolean", "default": false },
|
|
92
|
+
"isReadOnly": { "type": "boolean", "default": false },
|
|
93
|
+
"isHidden": { "type": "boolean", "default": false },
|
|
94
|
+
"isDisplayField": { "type": "boolean", "default": false },
|
|
95
|
+
"flags": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"description": "e.g. { \"formula\": \"<mvel expression>\" } — a formula field gets no physical column, computed on read. ALSO (added 2026-09-03): { \"reference\": { \"entityType\": \"Employee\" } } on a foreign-key-shaped field (e.g. employee_id) turns on automatic id->display-name resolution — no Java, no per-page join. Once set (via this file or PUT /api/v1/entities/{id}/fields/{fieldId}), BOTH GET .../records/query (the grid endpoint) and GET .../records/{id} (single-record read) automatically add a sibling \"<fieldName>_label\" key to every row/record where that id resolves (e.g. employee_id:1, employee_id_label:\"Aria Chen\") — bind a grid column or detail-view field to \"<fieldName>_label\" instead of the raw id. Batched: one resolve call per distinct reference-flagged field per page load, never per row. entityType must be a REGISTERED provider — \"Employee\" is the only one today (backed by hcm-employee's own batch-names endpoint via engine-entity's ReferenceResolutionService); an unregistered entityType simply produces no \"_label\" key, never an error. See erp examples search \"employee_id_label\" for a real shipped instance (hcm-compensation's hcm_salary_planning_entry/hcm_salary_review/hcm_merit_increase entities + their page grid columns/detail bindings)."
|
|
98
|
+
},
|
|
99
|
+
"roleRules": {
|
|
100
|
+
"type": "array",
|
|
101
|
+
"description": "Mirrors @erp/form-engine's FieldRoleRule[].",
|
|
102
|
+
"items": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"required": ["role"],
|
|
105
|
+
"properties": {
|
|
106
|
+
"role": { "type": "string" },
|
|
107
|
+
"required": { "type": "boolean" },
|
|
108
|
+
"readOnly": { "type": "boolean" },
|
|
109
|
+
"hidden": { "type": "boolean" }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"relationship": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"required": ["name", "type", "targetEntity"],
|
|
118
|
+
"properties": {
|
|
119
|
+
"name": { "type": "string" },
|
|
120
|
+
"type": { "type": "string", "enum": ["ONE_TO_MANY", "MANY_TO_ONE", "MANY_TO_MANY"] },
|
|
121
|
+
"targetEntity": { "type": "string" },
|
|
122
|
+
"foreignKeyField": { "type": "string" }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://erp.platform/schema/entity-document-generator-config.schema.json",
|
|
4
|
+
"title": "entity_document_generator_config row",
|
|
5
|
+
"description": "One config row for the generic engine-entity.document-generator job (EntityDocumentGeneratorJob, Batch 5+6, 2026-09-10): generate a document artifact per eligible source_entity row via the platform FileService, store it in a DMS cabinet, and stamp the file id onto target_file_field. Deduped by 'target field already populated'. NOTE: the platform print-template render path (engine-print-export PlaywrightPdfRenderer) cannot be driven headless against a real business record - it only renders synthetic sample data via the Studio app - so this job emits a structured-data artifact (json/csv). template_ref is reserved for when real-record print binding ships. Ship via metadata/seed-data/*.json. See ai/patterns/generic-document-generator-job.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["document_code", "source_entity", "target_file_field", "active"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"document_code": { "type": "string", "description": "Stable identifier / natural key for idempotent seeding." },
|
|
11
|
+
"source_entity": { "type": "string", "description": "Entity whose rows get a document." },
|
|
12
|
+
"status_field": { "type": "string", "default": "status" },
|
|
13
|
+
"when_status_in": { "type": "string", "description": "Comma-separated statuses a row must be in to be eligible (e.g. COMPLETED for an audit report). Blank = every row." },
|
|
14
|
+
"filter_field": { "type": "string", "description": "Optional single-column equality pre-filter." },
|
|
15
|
+
"filter_value": { "type": "string" },
|
|
16
|
+
"target_file_field": { "type": "string", "description": "Text column on the source row that receives the generated file id. Its non-blank state IS the dedupe marker." },
|
|
17
|
+
"format": { "type": "string", "enum": ["json", "csv"], "default": "json" },
|
|
18
|
+
"cabinet_id": { "type": "integer", "description": "DMS cabinet id (engine-file) the artifact is stored in. Give this OR cabinet_name." },
|
|
19
|
+
"cabinet_name": { "type": "string", "description": "Portable alternative to cabinet_id: the job resolves a cabinet by this name for the tenant, creating it if absent. Preferred for seed-data rows (no hardcoded id)." },
|
|
20
|
+
"file_name_template": { "type": "string", "description": "Generated file name. Supports ${id} ${date} ${ts} ${uuid} and ${field:<name>}. Default: <document_code>-${id}-${date}.<ext>." },
|
|
21
|
+
"include_fields": { "type": "string", "description": "Comma-separated source fields to include in the artifact. Blank = all fields." },
|
|
22
|
+
"child_entity": { "type": "string", "description": "Optional child entity whose rows (matched by child_match_field == source id) are embedded (json) alongside the record." },
|
|
23
|
+
"child_match_field": { "type": "string", "description": "FK column on child_entity pointing at the source row's id." },
|
|
24
|
+
"template_ref": { "type": "string", "description": "Reserved: a print-template id, for the future render_mode:template branch. Not used today (see description)." },
|
|
25
|
+
"regenerate": { "type": "boolean", "default": false, "description": "When true, regenerate even if target_file_field is already populated." },
|
|
26
|
+
"seeded_by": { "type": "string" },
|
|
27
|
+
"active": { "type": "boolean" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://erp.internal/schema/entity-rule-definition.schema.json",
|
|
4
|
+
"title": "EntityRuleDefinition",
|
|
5
|
+
"description": "One declarative entity-lifecycle rule, generic across any engine-entity-designed entity. Mirrors com.erp.platform.engine.rule.EntityRuleDefinition. NOT the same as the older Studio 'Business Rule' tier (event-name + single-expression) — see that record's own Javadoc. See ai/patterns/business-rule-engine.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["entityType", "name", "triggerEvent", "conditions", "actions"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"entityType": { "type": "string", "description": "The EntityDefinition.name this rule fires against." },
|
|
10
|
+
"name": { "type": "string" },
|
|
11
|
+
"description": { "type": "string" },
|
|
12
|
+
"triggerEvent": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": ["BEFORE_CREATE", "AFTER_CREATE", "BEFORE_UPDATE", "AFTER_UPDATE", "BEFORE_DELETE", "AFTER_DELETE"]
|
|
15
|
+
},
|
|
16
|
+
"priority": { "type": "integer", "default": 0, "description": "Lower runs first when multiple rules match the same trigger." },
|
|
17
|
+
"active": { "type": "boolean", "default": true },
|
|
18
|
+
"conditions": { "$ref": "#/definitions/condition" },
|
|
19
|
+
"actions": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"minItems": 1,
|
|
22
|
+
"items": { "$ref": "#/definitions/action" }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"definitions": {
|
|
26
|
+
"condition": {
|
|
27
|
+
"description": "A composable all/any/not condition tree evaluated against the entity record's fields, or a single leaf comparison.",
|
|
28
|
+
"oneOf": [
|
|
29
|
+
{
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"all": { "type": "array", "items": { "$ref": "#/definitions/condition" } }
|
|
33
|
+
},
|
|
34
|
+
"required": ["all"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"any": { "type": "array", "items": { "$ref": "#/definitions/condition" } }
|
|
40
|
+
},
|
|
41
|
+
"required": ["any"]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"not": { "$ref": "#/definitions/condition" }
|
|
47
|
+
},
|
|
48
|
+
"required": ["not"]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"type": "object",
|
|
52
|
+
"required": ["field", "op"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"field": { "type": "string" },
|
|
55
|
+
"op": { "type": "string", "enum": ["eq", "neq", "gt", "gte", "lt", "lte", "contains", "in", "is_null", "is_not_null"], "description": "Mirrors EntityRuleConditionEvaluator exactly (see ai/patterns/business-rule-engine.md). is_null/is_not_null take no `value`." },
|
|
56
|
+
"value": { "description": "Required for all ops except is_null/is_not_null." }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"action": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": ["type"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"type": { "type": "string", "enum": ["REJECT", "SET_VALUE", "EXECUTE_SERVICE", "START_WORKFLOW"] },
|
|
66
|
+
"message": { "type": "string", "description": "REJECT only — the error surfaced to the caller." },
|
|
67
|
+
"field": { "type": "string", "description": "SET_VALUE only — target field name." },
|
|
68
|
+
"value": { "description": "SET_VALUE only — literal or expression result to assign." },
|
|
69
|
+
"handlerName": { "type": "string", "description": "EXECUTE_SERVICE only — a registered ActionHandler bean name." },
|
|
70
|
+
"params": { "type": "object", "description": "EXECUTE_SERVICE/START_WORKFLOW only — extra args passed to the handler/workflow." },
|
|
71
|
+
"workflowName": { "type": "string", "description": "START_WORKFLOW only." }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://erp.platform/schema/entity-status-date-sweep-config.schema.json",
|
|
4
|
+
"title": "entity_status_date_sweep_config row",
|
|
5
|
+
"description": "One config row for the generic engine-entity.status-date-sweep job (EntityStatusDateSweepJob). Ship these via metadata/seed-data/*.json (see plugin-seed-data.schema.json). See ai/patterns/generic-entity-status-date-sweep.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["entity_name", "when_status_in", "date_field", "set_status_to", "active"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"entity_name": { "type": "string", "description": "Entity Engine entity to sweep, e.g. hcm_asset." },
|
|
11
|
+
"status_field": { "type": "string", "default": "status", "description": "Column holding the gating status." },
|
|
12
|
+
"when_status_in": { "type": "string", "description": "Comma-separated statuses a row must currently be in to be eligible." },
|
|
13
|
+
"date_field": { "type": "string", "description": "Date/datetime column compared against now (+day_offset)." },
|
|
14
|
+
"compare_op": { "type": "string", "enum": ["lte", "lt", "gte", "gt"], "default": "lte" },
|
|
15
|
+
"day_offset": { "type": "integer", "default": 0, "description": "Compare date_field against now + day_offset days (negative shifts earlier). Enables N-days-before reminders. Added 2026-09." },
|
|
16
|
+
"set_field": { "type": "string", "description": "Field to WRITE when a row matches; defaults to status_field. Use a distinct flag field (e.g. warranty_alert_30d) so a reminder never corrupts the lifecycle enum." },
|
|
17
|
+
"set_status_to": { "type": "string", "description": "Value written into set_field on match (e.g. \"true\" for a boolean flag, or a new status enum value)." },
|
|
18
|
+
"seeded_by": { "type": "string", "description": "Provenance stamp (the plugin id). Set automatically by PluginDataSeedInstaller from the seed file's \"source\"." },
|
|
19
|
+
"active": { "type": "boolean" }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://erp-platform.dev/schemas/form-definition.schema.json",
|
|
4
|
+
"title": "Form definition contract v1",
|
|
5
|
+
"description": "The machine-readable contract a form ships (W1-03) - Fields/Sections/Tabs/Layout/Validations/Rules/Events/Permissions, freezing the gap-audit additions: a child-table field kind for repeatable rows (gap 1), computed/formula fields (gap 6), section/tab-level rule targets (gap 3), a cascading-options rule output (gap 4), an async validation kind (gap 5), and a lifecycle-hook catalog (gap 2). Cross-references (sectionId/tabId/field-name existence, rollup targets) are checked by validateFormDefinition, not by this schema alone - the same division of labor as block-definition.schema.json. Permissions reuse the Phase 5 block-instance-config.schema.json shape verbatim (gap 8), never a new model.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"$defs": {
|
|
8
|
+
"identifier": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"minLength": 1,
|
|
11
|
+
"maxLength": 128,
|
|
12
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
13
|
+
},
|
|
14
|
+
"i18nKey": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"minLength": 1,
|
|
17
|
+
"maxLength": 256
|
|
18
|
+
},
|
|
19
|
+
"permissionSurface": {
|
|
20
|
+
"description": "The Phase 5 W2-05 visible/enabled/masked hook, reused verbatim (gap 8) - never a new permission model.",
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"visible": { "type": "boolean" },
|
|
24
|
+
"enabled": { "type": "boolean" },
|
|
25
|
+
"masked": { "type": "boolean" }
|
|
26
|
+
},
|
|
27
|
+
"required": ["visible", "enabled", "masked"],
|
|
28
|
+
"additionalProperties": false
|
|
29
|
+
},
|
|
30
|
+
"boundKey": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"scope": { "enum": ["application", "tenant", "user", "session", "page", "form", "block"] },
|
|
34
|
+
"scopeKey": { "type": "string", "maxLength": 128 },
|
|
35
|
+
"key": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$" }
|
|
36
|
+
},
|
|
37
|
+
"required": ["scope", "key"],
|
|
38
|
+
"additionalProperties": false
|
|
39
|
+
},
|
|
40
|
+
"fieldValue": {
|
|
41
|
+
"oneOf": [
|
|
42
|
+
{ "type": "object", "properties": { "source": { "const": "static" }, "value": {} }, "required": ["source", "value"], "additionalProperties": false },
|
|
43
|
+
{ "type": "object", "properties": { "source": { "const": "expression" }, "expression": { "type": "string", "minLength": 1, "maxLength": 2000 } }, "required": ["source", "expression"], "additionalProperties": false },
|
|
44
|
+
{ "type": "object", "properties": { "source": { "const": "binding" }, "binding": { "$ref": "#/$defs/boundKey" } }, "required": ["source", "binding"], "additionalProperties": false }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"selectOption": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": { "value": {}, "labelKey": { "$ref": "#/$defs/i18nKey" } },
|
|
50
|
+
"required": ["value", "labelKey"],
|
|
51
|
+
"additionalProperties": false
|
|
52
|
+
},
|
|
53
|
+
"fieldConstraints": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"properties": {
|
|
56
|
+
"min": { "type": "number" },
|
|
57
|
+
"max": { "type": "number" },
|
|
58
|
+
"maxLength": { "type": "integer", "minimum": 1 },
|
|
59
|
+
"pattern": { "type": "string", "maxLength": 500 },
|
|
60
|
+
"options": { "type": "array", "items": { "$ref": "#/$defs/selectOption" } },
|
|
61
|
+
"currencyCode": { "description": "ISO 4217 code a currency field formats with (taxonomy v2, 2026-07-16) - presentation only.", "type": "string", "pattern": "^[A-Z]{3}$" }
|
|
62
|
+
},
|
|
63
|
+
"additionalProperties": false
|
|
64
|
+
},
|
|
65
|
+
"fieldDesigner": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"group": { "$ref": "#/$defs/identifier" },
|
|
69
|
+
"icon": { "type": "string", "maxLength": 64 },
|
|
70
|
+
"helpTextKey": { "$ref": "#/$defs/i18nKey" }
|
|
71
|
+
},
|
|
72
|
+
"required": ["group"],
|
|
73
|
+
"additionalProperties": false
|
|
74
|
+
},
|
|
75
|
+
"computedSpec": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": { "expression": { "type": "string", "minLength": 1, "maxLength": 2000 } },
|
|
78
|
+
"required": ["expression"],
|
|
79
|
+
"additionalProperties": false
|
|
80
|
+
},
|
|
81
|
+
"fetchFromBinding": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"sourceBinding": { "$ref": "#/$defs/boundKey" },
|
|
85
|
+
"fieldMap": { "type": "object", "additionalProperties": { "type": "string" } }
|
|
86
|
+
},
|
|
87
|
+
"required": ["sourceBinding", "fieldMap"],
|
|
88
|
+
"additionalProperties": false
|
|
89
|
+
},
|
|
90
|
+
"rollupBinding": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"properties": {
|
|
93
|
+
"targetField": { "$ref": "#/$defs/identifier" },
|
|
94
|
+
"expression": { "type": "string", "minLength": 1, "maxLength": 2000 }
|
|
95
|
+
},
|
|
96
|
+
"required": ["targetField", "expression"],
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
},
|
|
99
|
+
"childTableSpec": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"properties": {
|
|
102
|
+
"rowFields": { "type": "array", "items": { "$ref": "#/$defs/fieldSpec" }, "minItems": 1 },
|
|
103
|
+
"rowValidations": { "type": "array", "items": { "$ref": "#/$defs/validationSpec" } },
|
|
104
|
+
"minRows": { "type": "integer", "minimum": 0 },
|
|
105
|
+
"maxRows": { "type": "integer", "minimum": 1 },
|
|
106
|
+
"rowPermissions": { "$ref": "#/$defs/permissionSurface" },
|
|
107
|
+
"fetchFrom": { "$ref": "#/$defs/fetchFromBinding" },
|
|
108
|
+
"rollup": { "$ref": "#/$defs/rollupBinding" }
|
|
109
|
+
},
|
|
110
|
+
"required": ["rowFields", "rowPermissions"],
|
|
111
|
+
"additionalProperties": false
|
|
112
|
+
},
|
|
113
|
+
"fieldSpec": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"properties": {
|
|
116
|
+
"name": { "$ref": "#/$defs/identifier" },
|
|
117
|
+
"kind": { "enum": ["text", "number", "date", "time", "datetime", "select", "radio", "multi-select", "checkbox", "currency", "computed", "child-table"] },
|
|
118
|
+
"labelKey": { "$ref": "#/$defs/i18nKey" },
|
|
119
|
+
"required": { "type": "boolean" },
|
|
120
|
+
"translatable": { "type": "boolean" },
|
|
121
|
+
"blockType": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
122
|
+
"value": { "$ref": "#/$defs/fieldValue" },
|
|
123
|
+
"constraints": { "$ref": "#/$defs/fieldConstraints" },
|
|
124
|
+
"sectionId": { "$ref": "#/$defs/identifier" },
|
|
125
|
+
"tabId": { "$ref": "#/$defs/identifier" },
|
|
126
|
+
"colSpan": { "description": "How many of the owning section's columns this field spans (multi-column layout, 2026-07-16 audit).", "type": "integer", "minimum": 1, "maximum": 4 },
|
|
127
|
+
"designer": { "$ref": "#/$defs/fieldDesigner" },
|
|
128
|
+
"computed": { "$ref": "#/$defs/computedSpec" },
|
|
129
|
+
"childTable": { "$ref": "#/$defs/childTableSpec" },
|
|
130
|
+
"permissions": { "$ref": "#/$defs/permissionSurface" },
|
|
131
|
+
"style": { "description": "React CSSProperties-shaped style bag (2026-07-24), the same shape @erp/block-engine's own BlockInstanceConfig.style already uses.", "type": "object", "additionalProperties": { "type": ["string", "number"] } },
|
|
132
|
+
"className": { "type": "string" }
|
|
133
|
+
},
|
|
134
|
+
"required": ["name", "kind", "labelKey"],
|
|
135
|
+
"additionalProperties": false
|
|
136
|
+
},
|
|
137
|
+
"sectionSpec": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"properties": {
|
|
140
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
141
|
+
"titleKey": { "$ref": "#/$defs/i18nKey" },
|
|
142
|
+
"fields": { "type": "array", "items": { "$ref": "#/$defs/identifier" } },
|
|
143
|
+
"columns": { "description": "Multi-column layout (2026-07-16 audit, additive): fields lay out across this many columns; absent = single column; collapses to one column at xs (mobile-first).", "type": "integer", "minimum": 1, "maximum": 4 }
|
|
144
|
+
},
|
|
145
|
+
"required": ["id", "titleKey", "fields"],
|
|
146
|
+
"additionalProperties": false
|
|
147
|
+
},
|
|
148
|
+
"tabSpec": {
|
|
149
|
+
"type": "object",
|
|
150
|
+
"properties": {
|
|
151
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
152
|
+
"titleKey": { "$ref": "#/$defs/i18nKey" },
|
|
153
|
+
"sectionIds": { "type": "array", "items": { "$ref": "#/$defs/identifier" } }
|
|
154
|
+
},
|
|
155
|
+
"required": ["id", "titleKey", "sectionIds"],
|
|
156
|
+
"additionalProperties": false
|
|
157
|
+
},
|
|
158
|
+
"actionDefinition": {
|
|
159
|
+
"description": "A Phase 2 action definition - opaque here (action-engine's own schema is the contract), just structurally present.",
|
|
160
|
+
"type": "object"
|
|
161
|
+
},
|
|
162
|
+
"validationSpec": {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"properties": {
|
|
165
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
166
|
+
"scope": { "enum": ["field", "cross-field", "form"] },
|
|
167
|
+
"field": { "$ref": "#/$defs/identifier" },
|
|
168
|
+
"fields": { "type": "array", "items": { "$ref": "#/$defs/identifier" } },
|
|
169
|
+
"trigger": { "enum": ["onChange", "onBlur", "beforeSave"] },
|
|
170
|
+
"kind": { "enum": ["expression", "async"] },
|
|
171
|
+
"expression": { "type": "string", "maxLength": 2000 },
|
|
172
|
+
"actionChain": { "type": "array", "items": { "$ref": "#/$defs/actionDefinition" } },
|
|
173
|
+
"messageKey": { "$ref": "#/$defs/i18nKey" }
|
|
174
|
+
},
|
|
175
|
+
"required": ["id", "scope", "trigger", "kind", "messageKey"],
|
|
176
|
+
"additionalProperties": false
|
|
177
|
+
},
|
|
178
|
+
"ruleBindingSpec": {
|
|
179
|
+
"type": "object",
|
|
180
|
+
"properties": {
|
|
181
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
182
|
+
"trigger": { "$ref": "#/$defs/identifier" },
|
|
183
|
+
"targetKind": { "enum": ["field", "section", "tab"] },
|
|
184
|
+
"targetId": { "$ref": "#/$defs/identifier" },
|
|
185
|
+
"output": { "enum": ["visibility", "required", "readonly", "options"] },
|
|
186
|
+
"condition": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
|
187
|
+
"resetOnChange": { "type": "boolean" }
|
|
188
|
+
},
|
|
189
|
+
"required": ["id", "trigger", "targetKind", "targetId", "output", "condition"],
|
|
190
|
+
"additionalProperties": false
|
|
191
|
+
},
|
|
192
|
+
"formEventBinding": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"properties": {
|
|
195
|
+
"hook": { "enum": ["onLoad", "beforeSave", "afterSave", "onCancel"] },
|
|
196
|
+
"actions": { "type": "array", "items": { "$ref": "#/$defs/actionDefinition" }, "minItems": 1 }
|
|
197
|
+
},
|
|
198
|
+
"required": ["hook", "actions"],
|
|
199
|
+
"additionalProperties": false
|
|
200
|
+
},
|
|
201
|
+
"canvasPosition": {
|
|
202
|
+
"description": "Freeform placement envelope (2026-07-24, mirrors @erp/block-engine's own canvas position shape) - authored data, not derived from flow layout.",
|
|
203
|
+
"type": "object",
|
|
204
|
+
"properties": {
|
|
205
|
+
"x": { "type": "number" },
|
|
206
|
+
"y": { "type": "number" },
|
|
207
|
+
"width": { "type": "number" },
|
|
208
|
+
"height": { "type": "number" },
|
|
209
|
+
"zIndex": { "type": "number" },
|
|
210
|
+
"hidden": { "type": "boolean" }
|
|
211
|
+
},
|
|
212
|
+
"required": ["x", "y", "width", "height"],
|
|
213
|
+
"additionalProperties": false
|
|
214
|
+
},
|
|
215
|
+
"canvasPlacement": {
|
|
216
|
+
"description": "One freeform-placed field in a form's own `canvas` (2026-07-24, the same 'Free canvas' mode @erp/block-engine and @erp/page-engine already have) - `field` names an entry already declared in `fields`, a placement layer, not a duplicate content tree.",
|
|
217
|
+
"type": "object",
|
|
218
|
+
"properties": {
|
|
219
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
220
|
+
"field": { "$ref": "#/$defs/identifier" },
|
|
221
|
+
"position": { "$ref": "#/$defs/canvasPosition" },
|
|
222
|
+
"locked": { "type": "boolean" }
|
|
223
|
+
},
|
|
224
|
+
"required": ["id", "field", "position"],
|
|
225
|
+
"additionalProperties": false
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"properties": {
|
|
229
|
+
"contractVersion": { "const": 1 },
|
|
230
|
+
"id": {
|
|
231
|
+
"description": "Dot-namespaced form id, e.g. hr.employee-lookup.",
|
|
232
|
+
"type": "string",
|
|
233
|
+
"minLength": 1,
|
|
234
|
+
"maxLength": 128,
|
|
235
|
+
"pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
|
|
236
|
+
},
|
|
237
|
+
"version": { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$" },
|
|
238
|
+
"publisher": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
239
|
+
"fields": { "type": "array", "items": { "$ref": "#/$defs/fieldSpec" } },
|
|
240
|
+
"sections": { "type": "array", "items": { "$ref": "#/$defs/sectionSpec" } },
|
|
241
|
+
"tabs": { "type": "array", "items": { "$ref": "#/$defs/tabSpec" } },
|
|
242
|
+
"layout": {
|
|
243
|
+
"type": "object",
|
|
244
|
+
"properties": { "order": { "type": "array", "items": { "$ref": "#/$defs/identifier" } } },
|
|
245
|
+
"required": ["order"],
|
|
246
|
+
"additionalProperties": false
|
|
247
|
+
},
|
|
248
|
+
"canvas": { "type": "array", "items": { "$ref": "#/$defs/canvasPlacement" } },
|
|
249
|
+
"validations": { "type": "array", "items": { "$ref": "#/$defs/validationSpec" } },
|
|
250
|
+
"rules": { "type": "array", "items": { "$ref": "#/$defs/ruleBindingSpec" } },
|
|
251
|
+
"events": { "type": "array", "items": { "$ref": "#/$defs/formEventBinding" } },
|
|
252
|
+
"permissions": { "$ref": "#/$defs/permissionSurface" },
|
|
253
|
+
"designer": {
|
|
254
|
+
"type": "object",
|
|
255
|
+
"properties": {
|
|
256
|
+
"displayNameKey": { "$ref": "#/$defs/i18nKey" },
|
|
257
|
+
"descriptionKey": { "$ref": "#/$defs/i18nKey" },
|
|
258
|
+
"icon": { "type": "string", "minLength": 1, "maxLength": 64 },
|
|
259
|
+
"propertyGroups": {
|
|
260
|
+
"type": "array",
|
|
261
|
+
"items": {
|
|
262
|
+
"type": "object",
|
|
263
|
+
"properties": { "id": { "$ref": "#/$defs/identifier" }, "titleKey": { "$ref": "#/$defs/i18nKey" } },
|
|
264
|
+
"required": ["id", "titleKey"],
|
|
265
|
+
"additionalProperties": false
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"preview": {
|
|
269
|
+
"type": "object",
|
|
270
|
+
"properties": { "kind": { "const": "form" }, "sampleKey": { "$ref": "#/$defs/i18nKey" } },
|
|
271
|
+
"required": ["kind"],
|
|
272
|
+
"additionalProperties": false
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"required": ["displayNameKey", "descriptionKey", "icon", "propertyGroups", "preview"],
|
|
276
|
+
"additionalProperties": false
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
"required": ["contractVersion", "id", "version", "publisher", "fields", "sections", "tabs", "layout", "validations", "rules", "events", "permissions", "designer"],
|
|
280
|
+
"additionalProperties": false
|
|
281
|
+
}
|