@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,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "employee.movement",
|
|
3
|
+
"description": "Four-stage approval (Manager -> HR -> conditional Finance -> conditional Executive) for an Employee Internal Movement. Finance is only visited when the movement actually changes cost center or business unit (costCenterChanged||businessUnitChanged, computed server-side in MovementService.submit() from which hcm_employee_movement_detail rows exist). Executive is only visited when the movement is high-impact (highImpact = movement_type='composite' with 3+ changed attributes in this v1 — a destination-position-grade-jump variant is deferred, Movement has no position/grade concept the way Promotion does). All three booleans (costCenterChanged/businessUnitChanged/highImpact) are ALWAYS supplied explicitly (never omitted) via extraContext at startInstance time so the condition evaluator never has to treat a missing field as false. hr can go straight to executive (skipping finance) when highImpact is true but neither cost center nor business unit changed; when both are true, the movement visits hr -> finance -> executive in sequence. Empty-match at any stage completes the instance, same rule proven by employee.transfer.json/employee.promotion.json.",
|
|
4
|
+
"stagesJson": "[\"manager\", \"hr\", \"finance\", \"executive\"]",
|
|
5
|
+
"tasksJson": "[{\"taskKey\": \"manager-approve\", \"stage\": \"manager\", \"taskType\": \"approval\", \"kind\": \"human\", \"payload\": {\"approvalObject\": \"employee.movement.manager\", \"amount\": 0, \"dueInSeconds\": 259200}}, {\"taskKey\": \"hr-approve\", \"stage\": \"hr\", \"taskType\": \"approval\", \"kind\": \"human\", \"payload\": {\"approvalObject\": \"employee.movement.hr\", \"amount\": 0, \"dueInSeconds\": 259200}}, {\"taskKey\": \"finance-approve\", \"stage\": \"finance\", \"taskType\": \"approval\", \"kind\": \"human\", \"payload\": {\"approvalObject\": \"employee.movement.finance\", \"amount\": 0, \"dueInSeconds\": 259200}}, {\"taskKey\": \"executive-approve\", \"stage\": \"executive\", \"taskType\": \"approval\", \"kind\": \"human\", \"payload\": {\"approvalObject\": \"employee.movement.executive\", \"amount\": 0, \"dueInSeconds\": 259200}}]",
|
|
6
|
+
"transitionsJson": "[{\"fromStage\": \"manager\", \"toStage\": \"hr\", \"condition\": {\"field\": \"decision\", \"op\": \"eq\", \"value\": \"approved\"}}, {\"fromStage\": \"hr\", \"toStage\": \"finance\", \"condition\": {\"any\": [{\"field\": \"costCenterChanged\", \"op\": \"eq\", \"value\": true}, {\"field\": \"businessUnitChanged\", \"op\": \"eq\", \"value\": true}]}}, {\"fromStage\": \"hr\", \"toStage\": \"executive\", \"condition\": {\"all\": [{\"field\": \"costCenterChanged\", \"op\": \"eq\", \"value\": false}, {\"field\": \"businessUnitChanged\", \"op\": \"eq\", \"value\": false}, {\"field\": \"highImpact\", \"op\": \"eq\", \"value\": true}]}}, {\"fromStage\": \"finance\", \"toStage\": \"executive\", \"condition\": {\"field\": \"highImpact\", \"op\": \"eq\", \"value\": true}}]",
|
|
7
|
+
"approversJson": "{}",
|
|
8
|
+
"slasJson": "[]",
|
|
9
|
+
"escalationsJson": "[]",
|
|
10
|
+
"notificationsJson": "[]",
|
|
11
|
+
"metadataJson": "{}",
|
|
12
|
+
"approvalPermissions": [
|
|
13
|
+
{
|
|
14
|
+
"roleCode": "HR_MANAGER",
|
|
15
|
+
"approvalObject": "employee.movement.manager",
|
|
16
|
+
"maxAmount": null
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"roleCode": "HR_ADMINISTRATOR",
|
|
20
|
+
"approvalObject": "employee.movement.manager",
|
|
21
|
+
"maxAmount": null
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"roleCode": "HR_MANAGER",
|
|
25
|
+
"approvalObject": "employee.movement.hr",
|
|
26
|
+
"maxAmount": null
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"roleCode": "HR_ADMINISTRATOR",
|
|
30
|
+
"approvalObject": "employee.movement.hr",
|
|
31
|
+
"maxAmount": null
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"roleCode": "HR_ADMINISTRATOR",
|
|
35
|
+
"approvalObject": "employee.movement.finance",
|
|
36
|
+
"maxAmount": null
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"roleCode": "HR_ADMINISTRATOR",
|
|
40
|
+
"approvalObject": "employee.movement.executive",
|
|
41
|
+
"maxAmount": null
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "employee.promotion-acknowledge",
|
|
3
|
+
"description": "Single-approval (self-decide) - Employee Acknowledges Promotion Letter. Same self-decide pattern as prejoining.mark-ready/joining.complete: the same actor (the promoted employee) who is being asked to acknowledge is the one whose EMPLOYEE role grants the decide permission - PromotionRestContribution's POST /{id}/acknowledge calls changeRequests.submit(...) then immediately decideHumanTask(...) in the same request.",
|
|
4
|
+
"stagesJson": "[\"only\"]",
|
|
5
|
+
"tasksJson": "[{\"taskKey\": \"approve\", \"stage\": \"only\", \"taskType\": \"approval\", \"kind\": \"human\", \"payload\": {\"approvalObject\": \"employee.promotion-acknowledge\", \"amount\": 0, \"dueInSeconds\": 259200}}]",
|
|
6
|
+
"transitionsJson": "[]",
|
|
7
|
+
"approversJson": "{}",
|
|
8
|
+
"slasJson": "[]",
|
|
9
|
+
"escalationsJson": "[]",
|
|
10
|
+
"notificationsJson": "[]",
|
|
11
|
+
"metadataJson": "{}",
|
|
12
|
+
"approvalPermissions": [
|
|
13
|
+
{
|
|
14
|
+
"roleCode": "EMPLOYEE",
|
|
15
|
+
"approvalObject": "employee.promotion-acknowledge",
|
|
16
|
+
"maxAmount": null
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "employee.resignation",
|
|
3
|
+
"description": "Two-stage approval (Manager -> HR) for an Employee Resignation. manager->hr is unconditional (decision approved) - Resignation doesn't have Transfer/Promotion/Movement's finance/IT/executive branching triggers; the spec's own optional Department Head stage is folded into the HR stage (disclosed trim, no distinct Dept-Head role/data exists in this plugin to gate a 3rd stage on). Empty-match at hr completes the instance, same rule proven by employee.transfer.json/employee.movement.json. Send Back reuses the exact same REST-layer convention already built and proven for Movement (sent_back status + POST /{id}/resubmit, not a 3rd engine decision value) - not a real 3-way engine decision.",
|
|
4
|
+
"stagesJson": "[\"manager\", \"hr\"]",
|
|
5
|
+
"tasksJson": "[{\"taskKey\": \"manager-approve\", \"stage\": \"manager\", \"taskType\": \"approval\", \"kind\": \"human\", \"payload\": {\"approvalObject\": \"employee.resignation.manager\", \"amount\": 0, \"dueInSeconds\": 259200}}, {\"taskKey\": \"hr-approve\", \"stage\": \"hr\", \"taskType\": \"approval\", \"kind\": \"human\", \"payload\": {\"approvalObject\": \"employee.resignation.hr\", \"amount\": 0, \"dueInSeconds\": 259200}}]",
|
|
6
|
+
"transitionsJson": "[{\"fromStage\": \"manager\", \"toStage\": \"hr\", \"condition\": {\"field\": \"decision\", \"op\": \"eq\", \"value\": \"approved\"}}]",
|
|
7
|
+
"approversJson": "{}",
|
|
8
|
+
"slasJson": "[]",
|
|
9
|
+
"escalationsJson": "[]",
|
|
10
|
+
"notificationsJson": "[]",
|
|
11
|
+
"metadataJson": "{}",
|
|
12
|
+
"approvalPermissions": [
|
|
13
|
+
{
|
|
14
|
+
"roleCode": "HR_MANAGER",
|
|
15
|
+
"approvalObject": "employee.resignation.manager",
|
|
16
|
+
"maxAmount": null
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"roleCode": "HR_ADMINISTRATOR",
|
|
20
|
+
"approvalObject": "employee.resignation.manager",
|
|
21
|
+
"maxAmount": null
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"roleCode": "HR_ADMINISTRATOR",
|
|
25
|
+
"approvalObject": "employee.resignation.hr",
|
|
26
|
+
"maxAmount": null
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The curated, named-SHAPE example catalog shared by `erp examples patterns`
|
|
3
|
+
* (tools/erp-cli/erp.mjs) and the MCP server's `erp_examples_patterns` tool
|
|
4
|
+
* (tools/erp-mcp-server/index.mjs) — a single source of truth so a developer
|
|
5
|
+
* driving purely through the CLI and one driving purely through an MCP
|
|
6
|
+
* client see the identical catalog, never two copies that can drift. Pure
|
|
7
|
+
* data, no side effects, safe to import from either entrypoint (unlike
|
|
8
|
+
* importing erp.mjs itself, which runs its own CLI main() at import time).
|
|
9
|
+
*/
|
|
10
|
+
export const EXAMPLE_PATTERNS = {
|
|
11
|
+
workflow: [
|
|
12
|
+
{
|
|
13
|
+
name: "single-stage-self-decide",
|
|
14
|
+
description: "One actor both requests and immediately decides — models what would otherwise be a bare permission-gated direct action as a real, auditable, later-upgradable workflow instance. stagesJson: [\"only\"], transitionsJson: [] (no transitions needed, the one task's own decision ends the instance), approvalPermissions has exactly the role(s) that would have held the old direct-action permission. Caller: submit() then immediately decideHumanTask() in the same request.",
|
|
15
|
+
file: "backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.promotion-acknowledge.json",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "multi-stage-linear",
|
|
19
|
+
description: "A fixed sequence of real approvers, no branching. stagesJson: [\"manager\", \"hr\"], one transition (manager->hr) gated on {field:\"decision\",op:\"eq\",value:\"approved\"}, no transition out of the last stage — reaching it with no match completes the instance.",
|
|
20
|
+
file: "backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.resignation.json",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "multi-stage-conditional-branching",
|
|
24
|
+
description: "Stage routing depends on server-computed booleans passed via extraContext at startInstance (never omitted, so the condition evaluator never treats a missing field as false). transitionsJson conditions use {field,op,value} or {any:[...]}/{all:[...]} composition; a stage can be skipped entirely (hr->finance only if costCenterChanged||businessUnitChanged; hr->executive directly if neither changed but highImpact did). Empty-match at any stage completes the instance.",
|
|
25
|
+
file: "backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.movement.json",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
page: [
|
|
29
|
+
{
|
|
30
|
+
name: "related-record-lookup-field",
|
|
31
|
+
description: "2026-09-03, real gap found and fixed (a module built entirely by AI agents shipped every foreign-key field — employee_id, course_id, provider_id, etc. — as a bare text/number input the user had to type a raw numeric id into, module-wide, because this pattern was undiscoverable). ANY page field whose value is another entity's id must be a `core.lookup` block (or `core.select`/`core.autocomplete` for a small, static or already-loaded option set), never a plain text/number input. `core.lookup`'s properties: `value` (binding to the field), `recordType` (documentation-only label, e.g. \"Employee\"), `optionsSourceKey` (the REAL routing key — the `name` of a published Data Service artifact, spk-assembly/metadata/data_service/*.json, that this lookup calls server-side with a `search` parameter and expects back `{value,label}` rows). `committed` event fires with `event.new` = the picked record's id. If the target entity doesn't already have a search-shaped Data Service, author one first (`erp examples search employee-search --kind data-service` and `--kind data-view` show the real recipe: a `dataView` source + a `contains` filter on `${param.search}` + one required `search` parameter) — never skip straight to a text input because no Data Service exists yet.",
|
|
32
|
+
file: "backend/modules/hcm-employee/spk-assembly/metadata/page/promotions.json",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "resolved-reference-grid-column",
|
|
36
|
+
description: "2026-09-03, real gap found and fixed (a JSON-first module's grids showed a raw FK id — employee_id — in a `core.grid` column even though the SAME page's create dialog already used `core.lookup` for that field; the grid has no field-level lookup concept, only a `name`/`type`/`headerKey`/`renderAs` column spec). Do NOT bind a grid column or a detail-view value to a raw *_id field — check the entity's own field metadata first (`erp schema pull entity-definition` -> `properties.fields.items.properties.flags`'s own description covers this) for `flags.reference.entityType`; if present, GET .../records/query and GET .../records/{id} already return a sibling `<fieldName>_label` key (e.g. `employee_id` -> `employee_id_label`) with zero extra code — bind the column/value to that key instead. If the field ISN'T marked yet: set `\"flags\":\"{\\\"reference\\\":{\\\"entityType\\\":\\\"Employee\\\"}}\"` on it via `PUT /api/v1/entities/{id}/fields/{fieldId}` (entityType must be a REGISTERED provider — \"Employee\" is the only one shipped today) and the `_label` key appears automatically on the next read, batched once per page load, never per row.",
|
|
37
|
+
file: "backend/modules/hcm-compensation/spk-assembly/metadata/page/salary-planning.json",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
jobs: [
|
|
41
|
+
{
|
|
42
|
+
name: "n-days-before-reminder-sweep",
|
|
43
|
+
description: "2026-09-10. A 'remind me N days before <date>' background job needs ZERO Java — it is one entity_status_date_sweep_config row (engine-entity.status-date-sweep) with `day_offset` set. e.g. {entity_name:'hcm_asset', when_status_in:'AVAILABLE,ASSIGNED,IN_USE', date_field:'warranty_expiry_date', compare_op:'lte', day_offset:30, set_field:'warranty_alert_30d', set_status_to:'true', active:true} fires when warranty_expiry_date <= now+30d. Use a distinct flag field per window (30d/14d/7d) so windows are independently queryable and the reminder never touches the lifecycle `status` enum. Ship the rows via metadata/seed-data/*.json so they travel with the plugin. Schema: `erp_get_schema entity-status-date-sweep-config`. Doc: ai/patterns/generic-entity-status-date-sweep.md.",
|
|
44
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-sweep-configs.json",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "cross-row-aggregation-into-summary-entity",
|
|
48
|
+
description: "2026-09-10. A 'reconcile/roll-up count or total across many rows and store the result' job = one entity_aggregation_config row (engine-entity.aggregation-sweep), no Java. e.g. count hcm_asset grouped by status into a summary entity: {sweep_code:'asset-inventory-reconciliation', source_entity:'hcm_asset', aggregation:'count', group_by_field:'status', target_entity:'hcm_asset_reconciliation', target_key_field:'metric_key', target_key_prefix:'asset_status:', target_value_field:'metric_value', target_timestamp_field:'last_reconciled_at', active:true}. aggregation is count|sum|avg|min|max (last four over agg_field). Use target_key_prefix so several configs can share one summary entity. Schema: `erp_get_schema entity-aggregation-config`. Doc: ai/patterns/generic-aggregation-and-cadence-jobs.md.",
|
|
49
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-aggregation-configs.json",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "scheduled-record-generation-cadence",
|
|
53
|
+
description: "2026-09-10. A 'create new records on a schedule' job = one entity_cadence_config row (engine-entity.cadence-generator), no Java. STANDALONE (one row per tick): {cadence_code:'asset-audit-schedule', target_entity:'hcm_asset_audit', interval_days:90, dedupe_target_field:'cadence_marker', template_json:{audit_number:'AUDIT-${date}-${ts}', audit_name:'Scheduled Audit ${date}', status:'DRAFT'}}. SOURCE-DRIVEN (one row per due source row): add source_entity + source_date_field + due_within_days + field_map_json + dedupe_target_field=<a column holding the source id>. template_json string values support ${date} ${ts} ${uuid} ${sourceId}. Schema: `erp_get_schema entity-cadence-config`.",
|
|
54
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cadence-configs.json",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "condition-based-compliance-flagging",
|
|
58
|
+
description: "2026-09-10 (Batch 3). A 'detect rows that violate a policy/compliance condition and flag them, or escalate them' job = one entity_compliance_config row (engine-entity.compliance-sweep), no Java. It evaluates a rule-engine boolean expression (the SAME platform ExpressionEvaluator cross-field validation and Formula fields use — whitelisted funcs only: daysBetween, now, etc.; no ternary/`in`/`^`) against each source row, with the row's own fields + `now` + optional `related_count` in scope. FLAG outcome: {compliance_code:'asset-assignment-compliance', source_entity:'hcm_asset', when_status_in:'ASSIGNED,IN_USE', condition_expr:'custody_acknowledged != true', set_field:'compliance_flag', set_value:'NON_COMPLIANT', active:true} (idempotent — skipped when the field already equals set_value). RELATED-COUNT (cross-record, e.g. license over-allocation): add related_count_entity + related_count_key_field (FK back to source id) + related_count_status_in, then use `related_count` in condition_expr (e.g. 'related_count > entitlement_quantity'). ESCALATION outcome (raise a row in a separate entity, deduped by source id): set escalation_entity + escalation_dedupe_field + field_map_json + escalation_template_json instead of set_field. Ship via metadata/seed-data/*.json + an AFTER_CREATE {type:EXECUTE_SERVICE, service:ensureEntityComplianceSweepJobRegistered} rule on entity_compliance_config. Schema: `erp_get_schema entity-compliance-config`. Doc: ai/patterns/generic-compliance-sweep-job.md.",
|
|
59
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-compliance-configs.json",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "target-key-field-id-rollup",
|
|
63
|
+
description: "2026-09-10 (Batch 5+6). To roll child line-items UP onto their own parent record (the true MaintenanceCostSyncJob shape — write synced_cost onto hcm_asset_maintenance keyed by its OWN id, not into a summary entity), set target_key_field:'id' on an entity_aggregation_config row: {sweep_code:'maintenance-cost-rollup', source_entity:'hcm_asset_maintenance_cost', aggregation:'sum', agg_field:'total_cost', group_by_field:'maintenance_id', target_entity:'hcm_asset_maintenance', target_key_field:'id', target_value_field:'synced_cost', target_timestamp_field:'cost_synced_at', active:true}. The bucket key (from group_by_field) IS the parent row's primary key; the fold is written straight onto that row (never a create). Leave target_key_prefix blank. Schema: `erp_get_schema entity-aggregation-config`. Doc: ai/patterns/generic-aggregation-and-cadence-jobs.md.",
|
|
64
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-aggregation-configs.json",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "cross-plugin-offboarding-fanout",
|
|
68
|
+
description: "2026-09-10 (Batch 5+6). 'When an employee is separated, auto-create asset return requests / license reclamation rows for everything they hold' = one entity_cross_plugin_action_config row (engine-entity.cross-plugin-action), no Java, no cross-plugin HTTP. {action_code:'asset-separation-return', trigger_entity:'employee', trigger_status_field:'employment_status', trigger_status_in:'TERMINATED,SEPARATED', trigger_key_field:'id', related_entity:'hcm_asset_allocation', related_match_field:'employee_id', related_status_in:'ALLOCATED,ACKNOWLEDGED,IN_USE', create_entity:'hcm_asset_return', create_dedupe_field:'allocation_id' (MUST be a numeric/long column), create_template_json:{status:'REQUESTED', reason:'EMPLOYEE_SEPARATION'}, create_field_map_json:{asset_id:'asset_id', employee_id:'employee_id'}, related_set_field:'status', related_set_value:'RETURN_PENDING', active:true}. The trigger entity can be owned by any plugin. Idempotent via the dedupe field. Schema: `erp_get_schema entity-cross-plugin-action-config`. Doc: ai/patterns/generic-cross-plugin-action-job.md.",
|
|
69
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cross-plugin-configs.json",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "per-row-document-artifact-generation",
|
|
73
|
+
description: "2026-09-10 (Batch 5+6). 'Generate an audit report / certificate document per completed record and stash the file' = one entity_document_generator_config row (engine-entity.document-generator), no Java. {document_code:'asset-audit-report', source_entity:'hcm_asset_audit', when_status_in:'COMPLETED', target_file_field:'report_file_ref_id' (a TEXT column — its non-blank state IS the dedupe marker), format:'json', cabinet_id:<a real DMS cabinet id>, child_entity:'hcm_asset_audit_item', child_match_field:'audit_id', file_name_template:'asset-audit-${id}-${date}.json', active:true}. LIMITATION: the platform print-template render path (engine-print-export PlaywrightPdfRenderer) only renders synthetic sample data via the Studio app — it can't be driven headless against a real record — so this job emits a structured-data artifact (json/csv), not a rendered PDF; template_ref is reserved for when real-record binding ships. Schema: `erp_get_schema entity-document-generator-config`. Doc: ai/patterns/generic-document-generator-job.md.",
|
|
74
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-document-configs.json",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "heuristic-predictive-maintenance",
|
|
78
|
+
description: "2026-09-10 (Batch 5). PredictiveMaintenanceJob with NO ML: there is no tabular AI-inference seam (engine-ai-provider is text-gen/embedding only), so predictive-maintenance risk is scored as a DETERMINISTIC HEURISTIC config of the existing generic primitives — an entity_aggregation_config counting recent maintenance events per asset (target_key_field:'id' onto hcm_asset.recent_maintenance_count) plus an entity_compliance_config flagging hcm_asset.maintenance_risk='HIGH' when condition_expr combines real signals already in the data (recent_maintenance_count >= 3, or age past X% of expected_life_months, or open_maintenance_count >= 2). A real model plugs in later via the AiModelProvider seam with an entity_prediction_config, config-shape-compatible. Doc: ai/patterns/generic-compliance-sweep-job.md + the Batch 5 note in hcm-assets IMPLEMENTATION-GAPS.md.",
|
|
79
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-predictive-configs.json",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "install-lifecycle-data-seeding",
|
|
83
|
+
description: "2026-09-10. To make Entity-Engine data rows (especially rows in the SHARED platform config entities entity_status_date_sweep_config / entity_aggregation_config / entity_cadence_config / workflow_reminder_config) travel with the .spk instead of a manual per-tenant POST: add metadata/seed-data/<name>.json = {entity, keyFields:[...natural key...], source:'<pluginId>', rows:[...]}. PluginDataSeedInstaller upserts these on every install/upgrade/auto-provision, idempotently by keyFields (matched row updated only if a value changed, else inserted — never duplicated). Also ship metadata/entities/*.json for any entity/column the rows need, and metadata/rules/*.json AFTER_CREATE {type:EXECUTE_SERVICE, service:ensure<Job>Registered} to auto-register the job on first row. Schema: `erp_get_schema plugin-seed-data`. Doc: ai/patterns/plugin-install-data-seeding.md.",
|
|
84
|
+
file: "backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-sweep-configs.json",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
entities: [
|
|
88
|
+
{
|
|
89
|
+
name: "reference-display-label-field",
|
|
90
|
+
description: "2026-09-03. Any entity field whose value is another entity's real record id (a `long`-typed FK, e.g. `employee_id`) should carry `\"flags\":\"{\\\"reference\\\":{\\\"entityType\\\":\\\"Employee\\\"}}\"` in its field definition — this is the ONE line that turns on automatic id->display-name resolution platform-wide, no Java, no hand-written join. Once set, both the generic grid query (`GET .../records/query`) and single-record read (`GET .../records/{id}`) automatically add a `\"<fieldName>_label\"` key to every row/record where the id resolves (batched: one resolve call per distinct reference-flagged field per page load, never per row) — see `resolved-reference-grid-column` under `--kind page` for the consuming side. `entityType` must be a REGISTERED provider (a plugin that exposes its own `batch-names` endpoint) — \"Employee\" (backed by hcm-employee) is the only one shipped today; an unregistered entityType just produces no `_label` key, never an error.",
|
|
91
|
+
file: "backend/modules/hcm-compensation/spk-assembly/metadata/entities/hcm_salary_planning_entry.json",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bundleVersion": "2026-09-14.1",
|
|
3
|
+
"platformVersion": "0.0.0",
|
|
4
|
+
"generatedAt": "2026-09-14T13:48:11.809Z",
|
|
5
|
+
"generatedBy": "erp bundle build (tools/erp-cli/erp.mjs bundleBuildCommand)",
|
|
6
|
+
"schemaCount": 27,
|
|
7
|
+
"docCount": 90,
|
|
8
|
+
"exampleFileCount": 14,
|
|
9
|
+
"blockCount": 124,
|
|
10
|
+
"catalogGeneratedAt": "2026-09-14T13:48:11.094Z",
|
|
11
|
+
"catalogEngineCount": 43,
|
|
12
|
+
"catalogContractUnitCount": 74,
|
|
13
|
+
"sdkMode": "packaged"
|
|
14
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://erp-platform.dev/schemas/action-definition/v1",
|
|
4
|
+
"title": "Action Definition",
|
|
5
|
+
"description": "The frozen Phase 2 Action contract (contract version 1). Shared verbatim between the TypeScript Action Engine and the Java ActionHandler side; Phase 9's workflow tasks invoke definitions conforming to exactly this schema. Published definitions are immutable — changes create a new version (SAP clean-core rule). See low-code-docs/adr/0007-action-execution-semantics.md for the semantics decisions behind retry/error/async/idempotency fields.",
|
|
6
|
+
"$ref": "#/$defs/actionDefinition",
|
|
7
|
+
"$defs": {
|
|
8
|
+
"actionDefinition": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["id", "type", "name"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"minLength": 1,
|
|
16
|
+
"description": "Unique identifier (stringified integer row id for persisted definitions — table ids are incremental integers; any unique string for inline chain children)."
|
|
17
|
+
},
|
|
18
|
+
"type": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"minLength": 1,
|
|
21
|
+
"pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$",
|
|
22
|
+
"description": "Action handler key resolved against the Action Registry (callApi, setValue, showField, validate, navigate, executePlugin, ...)."
|
|
23
|
+
},
|
|
24
|
+
"name": { "type": "string", "minLength": 1, "description": "Friendly display name." },
|
|
25
|
+
"description": { "type": "string" },
|
|
26
|
+
"enabled": { "type": "boolean", "default": true, "description": "Runtime enable/disable. A disabled action is skipped, not failed." },
|
|
27
|
+
"order": { "type": "integer", "default": 0, "description": "Execution sequence among siblings (ascending)." },
|
|
28
|
+
"timeout": {
|
|
29
|
+
"type": "integer",
|
|
30
|
+
"minimum": 1,
|
|
31
|
+
"description": "Maximum execution time in milliseconds. On expiry the in-flight work is actually cancelled (AbortSignal), not orphaned, and the failure is classified retryable with code 'timeout'."
|
|
32
|
+
},
|
|
33
|
+
"retry": {
|
|
34
|
+
"description": "Retry policy (ADR 0007 §1). A bare integer n is legacy shorthand for { maxAttempts: n+1, strategy: 'fixed', intervalMs: 1000 }. Only errors classified retryable (timeout, network, HTTP 408/429/5xx) consume extra attempts — logical/validation failures are never retried.",
|
|
35
|
+
"oneOf": [{ "type": "integer", "minimum": 0 }, { "$ref": "#/$defs/retryPolicy" }]
|
|
36
|
+
},
|
|
37
|
+
"async": {
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"default": false,
|
|
40
|
+
"description": "ADR 0007 §5: an async action does not block its next sibling; its output is visible only to its own onSuccess/onFailure chains; the enclosing chain execution still awaits all async branches before reporting completion (structured concurrency)."
|
|
41
|
+
},
|
|
42
|
+
"condition": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Phase 1 expression evaluated against the resolved scope chain before execution; false or Null skips the action (skipped, not failed)."
|
|
45
|
+
},
|
|
46
|
+
"config": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"description": "Handler-specific configuration. String values support ${...} variable resolution — a PLAIN DOTTED PATH LOOKUP ONLY (e.g. ${page.foo}, ${api.bar.baz}) against the resolved scope chain (page/event/output-of-prior-action, referenced by its own `output` name). It does NOT support: arithmetic/operators (${page.count+1} resolves the literal string \"count+1\" as a variable name and faults with 'Unresolved variable' — found live authoring hcm-communications; to get a value that reliably changes between calls, reuse a field a preceding callApi step already recomputed, e.g. a just-refreshed KPI count, not an increment expression); a 'system' scope (${system.date}/${system.now} are NOT resolvable anywhere, in config OR input — same live fault; there is no current-server-time binding available to a page action chain today). A whole-value expression (config value is exactly \"${expr}\", nothing else) preserves the resolved value's real type (number/boolean/null); embedding it inside a longer string always stringifies. MUST NOT contain inline secrets — outbound calls reference a named tenant-scoped connection via connectionRef (ADR 0007 §3); publish-time validation rejects secret-shaped keys with literal values.",
|
|
49
|
+
"properties": {
|
|
50
|
+
"connectionRef": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Name of a tenant-scoped connection (URL + credentials) resolved at execution time. The reference is stored; the credential never is. Full connector configuration is Phase 17."
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"additionalProperties": true
|
|
56
|
+
},
|
|
57
|
+
"input": { "type": "object", "description": "Runtime parameter mapping, ${...}-resolvable like config.", "additionalProperties": true },
|
|
58
|
+
"output": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Name under which the handler result is stored in the api scope — retrievable as ${api.<name>} (or bare ${<name>}) by subsequent chain steps."
|
|
61
|
+
},
|
|
62
|
+
"onSuccess": { "type": "array", "items": { "$ref": "#/$defs/actionDefinition" }, "description": "Child actions executed after success, ordered by 'order'." },
|
|
63
|
+
"onFailure": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": { "$ref": "#/$defs/actionDefinition" },
|
|
66
|
+
"description": "Child actions executed after final failure (retries exhausted). The ${error.*} scope (message, code, actionId, attempt, retryable — ADR 0007 §2) is resolvable inside this chain."
|
|
67
|
+
},
|
|
68
|
+
"onError": { "$ref": "#/$defs/errorStrategy" },
|
|
69
|
+
"compensation": {
|
|
70
|
+
"$ref": "#/$defs/actionDefinition",
|
|
71
|
+
"description": "ADR 0007 §4: the compensating action executed when onError is 'compensate'. Required at publish time if onError is 'compensate'."
|
|
72
|
+
},
|
|
73
|
+
"metadata": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {
|
|
76
|
+
"version": { "type": "string", "description": "Semantic version of this definition (informational; the persistence layer's integer version is authoritative)." },
|
|
77
|
+
"tags": { "type": "array", "items": { "type": "string" } },
|
|
78
|
+
"createdBy": { "type": "string" }
|
|
79
|
+
},
|
|
80
|
+
"additionalProperties": true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"retryPolicy": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"required": ["maxAttempts"],
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"properties": {
|
|
89
|
+
"maxAttempts": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Total attempts including the first. 1 = no retry." },
|
|
90
|
+
"strategy": { "type": "string", "enum": ["fixed", "exponential"], "default": "fixed" },
|
|
91
|
+
"intervalMs": { "type": "integer", "minimum": 0, "default": 1000, "description": "Delay before the first retry; exponential doubles it per attempt." },
|
|
92
|
+
"maxIntervalMs": { "type": "integer", "minimum": 0, "description": "Cap on the exponential backoff interval." }
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"errorStrategy": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"enum": ["continue", "stop", "retry", "compensate", "rollback", "ignore", "custom"],
|
|
98
|
+
"default": "stop",
|
|
99
|
+
"description": "Disposition after final failure (ADR 0007 §4): continue = next sibling runs, chain reports the failure; stop = chain halts (default); retry = explicit alias for the retry policy then stop; compensate = execute 'compensation' then stop; rollback = restore the chain-start scope snapshot then stop; ignore = failure swallowed, chain continues clean; custom = execute config.errorHandler (a registered handler type) then stop."
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://erp-platform.dev/schemas/block-definition.schema.json",
|
|
4
|
+
"title": "Block definition contract v1",
|
|
5
|
+
"description": "The machine-readable contract a block type ships (W1-03) - the artifact every platform studied has (PCF manifest, LWC js-meta.xml, Mendix widget XML, ServiceNow now-ui.json). Phase 6/7 definitions embed instance configs against it, Phase 10's palette and property panel render from it, Phase 8 plugins extend it, Phase 16's AI authors against it, Phase 21 publishes it. Identity carries the marketplace shape (type + semver + publisher) from day one.",
|
|
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
|
+
"description": "A translation-resolver key. Designer-facing and user-facing text is always a key, never a literal string (W5-03).",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"maxLength": 256
|
|
19
|
+
},
|
|
20
|
+
"propertyType": {
|
|
21
|
+
"enum": ["string", "number", "boolean", "date", "json"]
|
|
22
|
+
},
|
|
23
|
+
"valueSourceKind": {
|
|
24
|
+
"description": "The four ways anything in this platform computes a value (W1-02): a literal, a Phase 1 expression, a Phase 4 state binding, or a Phase 2 action chain (event handlers only).",
|
|
25
|
+
"enum": ["static", "expression", "binding", "action-chain"]
|
|
26
|
+
},
|
|
27
|
+
"selectOption": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"value": {},
|
|
31
|
+
"labelKey": { "$ref": "#/$defs/i18nKey" }
|
|
32
|
+
},
|
|
33
|
+
"required": ["value", "labelKey"],
|
|
34
|
+
"additionalProperties": false
|
|
35
|
+
},
|
|
36
|
+
"propertySpec": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"name": { "$ref": "#/$defs/identifier" },
|
|
40
|
+
"type": { "$ref": "#/$defs/propertyType" },
|
|
41
|
+
"description": { "type": "string", "maxLength": 500 },
|
|
42
|
+
"required": { "type": "boolean", "default": false },
|
|
43
|
+
"default": {},
|
|
44
|
+
"sources": {
|
|
45
|
+
"description": "Which value sources an instance config may use for this property. Event handlers are declared under events, so action-chain never appears here.",
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": { "enum": ["static", "expression", "binding"] },
|
|
48
|
+
"minItems": 1,
|
|
49
|
+
"uniqueItems": true
|
|
50
|
+
},
|
|
51
|
+
"constraints": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"min": { "type": "number" },
|
|
55
|
+
"max": { "type": "number" },
|
|
56
|
+
"precision": { "type": "integer", "minimum": 0, "maximum": 20 },
|
|
57
|
+
"maxLength": { "type": "integer", "minimum": 1 },
|
|
58
|
+
"pattern": { "type": "string", "maxLength": 500 },
|
|
59
|
+
"options": { "type": "array", "items": { "$ref": "#/$defs/selectOption" } }
|
|
60
|
+
},
|
|
61
|
+
"additionalProperties": false
|
|
62
|
+
},
|
|
63
|
+
"designer": {
|
|
64
|
+
"description": "Per-property designer metadata: which panel group it renders in and with which editor (LWC targetConfigs / Mendix propertyGroups precedent).",
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"group": { "$ref": "#/$defs/identifier" },
|
|
68
|
+
"editor": { "enum": ["text", "number", "switch", "select", "date", "expression", "binding", "json"] },
|
|
69
|
+
"labelKey": { "$ref": "#/$defs/i18nKey" }
|
|
70
|
+
},
|
|
71
|
+
"required": ["group", "editor", "labelKey"],
|
|
72
|
+
"additionalProperties": false
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"required": ["name", "type", "sources", "designer"],
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
},
|
|
78
|
+
"portSpec": {
|
|
79
|
+
"description": "An input or output port: named, typed data the block consumes/produces through the state engine.",
|
|
80
|
+
"type": "object",
|
|
81
|
+
"properties": {
|
|
82
|
+
"name": { "$ref": "#/$defs/identifier" },
|
|
83
|
+
"type": { "$ref": "#/$defs/propertyType" },
|
|
84
|
+
"description": { "type": "string", "maxLength": 500 }
|
|
85
|
+
},
|
|
86
|
+
"required": ["name", "type"],
|
|
87
|
+
"additionalProperties": false
|
|
88
|
+
},
|
|
89
|
+
"eventSpec": {
|
|
90
|
+
"description": "An emitted event. catalogType MUST name a Phase 3 event-catalog entry (past-tense, registered, never ad hoc - the ServiceNow catalogued-actions lesson).",
|
|
91
|
+
"type": "object",
|
|
92
|
+
"properties": {
|
|
93
|
+
"name": { "$ref": "#/$defs/identifier" },
|
|
94
|
+
"catalogType": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
95
|
+
"description": { "type": "string", "maxLength": 500 }
|
|
96
|
+
},
|
|
97
|
+
"required": ["name", "catalogType"],
|
|
98
|
+
"additionalProperties": false
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"properties": {
|
|
102
|
+
"contractVersion": { "const": 1 },
|
|
103
|
+
"type": {
|
|
104
|
+
"description": "Globally unique block-type id, dot-namespaced (e.g. core.text-input, acme-hr.org-chart).",
|
|
105
|
+
"type": "string",
|
|
106
|
+
"minLength": 1,
|
|
107
|
+
"maxLength": 128,
|
|
108
|
+
"pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
|
|
109
|
+
},
|
|
110
|
+
"version": {
|
|
111
|
+
"description": "Semver. Instances pin the version they were authored against; minor/patch upgrades apply automatically, major upgrades require an explicit migration (W1-05).",
|
|
112
|
+
"type": "string",
|
|
113
|
+
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$"
|
|
114
|
+
},
|
|
115
|
+
"publisher": {
|
|
116
|
+
"description": "The Phase 21 marketplace publisher id.",
|
|
117
|
+
"type": "string",
|
|
118
|
+
"minLength": 1,
|
|
119
|
+
"maxLength": 128
|
|
120
|
+
},
|
|
121
|
+
"inputClass": {
|
|
122
|
+
"description": "True for input-class blocks: the block inherits the validation-message surface (invalid state + message slot + aria-invalid/aria-describedby) from the base contract, which Phase 6's validation pipeline populates (round 2).",
|
|
123
|
+
"type": "boolean",
|
|
124
|
+
"default": false
|
|
125
|
+
},
|
|
126
|
+
"properties": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"items": { "$ref": "#/$defs/propertySpec" }
|
|
129
|
+
},
|
|
130
|
+
"inputs": { "type": "array", "items": { "$ref": "#/$defs/portSpec" } },
|
|
131
|
+
"outputs": { "type": "array", "items": { "$ref": "#/$defs/portSpec" } },
|
|
132
|
+
"events": { "type": "array", "items": { "$ref": "#/$defs/eventSpec" } },
|
|
133
|
+
"permissions": {
|
|
134
|
+
"description": "Which permission hook points the block honors (W2-05). Enforcement lands in Phase 11; the hooks are in the render path from day one.",
|
|
135
|
+
"type": "object",
|
|
136
|
+
"properties": {
|
|
137
|
+
"visible": { "type": "boolean" },
|
|
138
|
+
"enabled": { "type": "boolean" },
|
|
139
|
+
"masked": { "type": "boolean" }
|
|
140
|
+
},
|
|
141
|
+
"required": ["visible", "enabled", "masked"],
|
|
142
|
+
"additionalProperties": false
|
|
143
|
+
},
|
|
144
|
+
"a11y": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"properties": {
|
|
147
|
+
"role": { "type": "string", "minLength": 1, "maxLength": 64 },
|
|
148
|
+
"labelFrom": {
|
|
149
|
+
"description": "Where the accessible label comes from: property:<name> or i18n:<key>.",
|
|
150
|
+
"type": "string",
|
|
151
|
+
"pattern": "^(property|i18n):.+$"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"required": ["role", "labelFrom"],
|
|
155
|
+
"additionalProperties": false
|
|
156
|
+
},
|
|
157
|
+
"designer": {
|
|
158
|
+
"description": "Block-level designer metadata: what Phase 10's palette and property panel render for a block they have never seen (gap audit).",
|
|
159
|
+
"type": "object",
|
|
160
|
+
"properties": {
|
|
161
|
+
"displayNameKey": { "$ref": "#/$defs/i18nKey" },
|
|
162
|
+
"descriptionKey": { "$ref": "#/$defs/i18nKey" },
|
|
163
|
+
"icon": { "type": "string", "minLength": 1, "maxLength": 64 },
|
|
164
|
+
"category": { "enum": ["input", "display", "layout", "data", "action"] },
|
|
165
|
+
"propertyGroups": {
|
|
166
|
+
"type": "array",
|
|
167
|
+
"items": {
|
|
168
|
+
"type": "object",
|
|
169
|
+
"properties": {
|
|
170
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
171
|
+
"titleKey": { "$ref": "#/$defs/i18nKey" }
|
|
172
|
+
},
|
|
173
|
+
"required": ["id", "titleKey"],
|
|
174
|
+
"additionalProperties": false
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"allowedTargets": {
|
|
178
|
+
"description": "Where an instance of this block may be placed (LWC targets precedent).",
|
|
179
|
+
"type": "array",
|
|
180
|
+
"items": { "enum": ["page", "form", "container", "dashboard", "report"] },
|
|
181
|
+
"minItems": 1
|
|
182
|
+
},
|
|
183
|
+
"preview": {
|
|
184
|
+
"description": "Design-mode preview descriptor (Mendix editorPreview precedent) - rendered by the builder without mounting the real block.",
|
|
185
|
+
"type": "object",
|
|
186
|
+
"properties": {
|
|
187
|
+
"kind": { "enum": ["field", "text", "button", "box", "table"] },
|
|
188
|
+
"sampleKey": { "$ref": "#/$defs/i18nKey" }
|
|
189
|
+
},
|
|
190
|
+
"required": ["kind"],
|
|
191
|
+
"additionalProperties": false
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"required": ["displayNameKey", "descriptionKey", "icon", "category", "propertyGroups", "allowedTargets", "preview"],
|
|
195
|
+
"additionalProperties": false
|
|
196
|
+
},
|
|
197
|
+
"slots": {
|
|
198
|
+
"description": "Container-class blocks only: named child slots and what they may contain.",
|
|
199
|
+
"type": "array",
|
|
200
|
+
"items": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {
|
|
203
|
+
"name": { "$ref": "#/$defs/identifier" },
|
|
204
|
+
"allowedCategories": {
|
|
205
|
+
"type": "array",
|
|
206
|
+
"items": { "enum": ["input", "display", "layout", "data", "action"] },
|
|
207
|
+
"minItems": 1
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"required": ["name", "allowedCategories"],
|
|
211
|
+
"additionalProperties": false
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"composition": {
|
|
215
|
+
"description": "2026-07-18: present only on tenant-authored composite Block definitions (Studio's Block Designer) - the block's own fixed internal content, an array of frozen @erp/block-engine BlockInstanceConfig, validated against the live BlockRegistry at mount time exactly like a page item's or a container's own children. undefined for every core/plugin block.",
|
|
216
|
+
"type": "array",
|
|
217
|
+
"items": { "type": "object" }
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"required": ["contractVersion", "type", "version", "publisher", "properties", "events", "permissions", "a11y", "designer"],
|
|
221
|
+
"additionalProperties": false
|
|
222
|
+
}
|