Project Validator
Project-wide lint for a Datex Studio
branch. The branch is the source of truth: the validator enumerates the branch's configs via
dxs source explore configs
and bulk-exports them to a throwaway temp directory with
dxs source document build
(the CLI's offline-analysis path), then runs five cross-component checks against that temp export — checks that per-file validation cannot see in isolation. Returns a structured, read-only punch-list grouped by check type. Never modifies the branch, and never treats a local
checkout as authoritative.
This is the
project-level validator. It complements per-file validation:
audits one component file end-to-end against its type-specific rule sheet;
this skill audits the whole project (or a scope subset) for issues that only appear when you look across files — broken
references, OData queries that don't match the live schema, datasource query options that have drifted away from declared
, etc. The two skills are designed to be used together —
after authoring each file,
before merging a batch or shipping a release.
See also: — single-file audit against the per-type creator rule sheet. Use it after authoring or modifying any one component. This skill is the project-level counterpart.
See also: — chases a specific cross-component reference contract. This skill is broader (5 categories across the whole project) but shallower (lint-grade, not deep wiring trace).
See also: — quick post-write check on a single file. Different scope and lighter touch than this skill.
Dependencies
- — (description ≤100 chars, mandatory description), ( table, suffix rules), (type-indicator rules). These are the rules the checks below enforce.
- / — branch-setup primitives and platform-runtime globals used when interpreting flow code (e.g. resolving and references).
- — per-file companion. Sibling scope. When the project validator surfaces a file-local violation that a creator skill's rule sheet documents in detail, route the parent to for the deep audit.
- — invoked for OData pre-flight (check 4). The project validator must not load raw schema documents into the parent context — always delegate the entity / property / navigation lookups to .
- — invoked when a finding needs grounded read-only inspection of supporting context (e.g. confirming an enum's allowed values by fetching the config from the branch). Optional, on demand.
Mode
This is a
read-only project lint. Do NOT modify any files. Do NOT run commands that mutate branch state (no
, no
, no writes). The validator reports; the parent decides what to do with the findings, and routes fixes back to the matching creator/editor skill.
Workflow
1. Resolve scope
The validator always runs against a
branch (follow
datex-studio-shared/branch-setup.md
to establish the branch ID — never assume one). The caller additionally supplies one of:
- (default) — every owned config on the branch.
- A check name — , , , , . Run only that check across the project.
- A scope subset — one or more config types (e.g. , ) or a name pattern. Apply all five checks (or the named check) within that subset only.
If the caller is ambiguous (e.g. just says "validate the project"), default to
owned configs on the branch and report the scope you chose in the output.
2. Enumerate and export the target configs (from the branch)
The branch is the source of truth — do
not walk a local
checkout. Acquire the configs via the CLI:
bash
# List what's on the branch (optionally filter by type / owned-only / scope)
dxs source explore configs --branch <branchId> --owned-only
# Bulk-export every config to a THROWAWAY temp dir for offline analysis
dxs source document build --branch <branchId> --include-summaries -o "$TMP/dxs-validate"
writes one file per config under
$TMP/dxs-validate/<App>/<branchId>/local/<type>/<referenceName>.yaml
. That temp export is the only thing the checks read or grep — searching a temp file you just fetched is the sanctioned pattern; greping a persistent
tree is not. Delete the temp dir when done.
Group the exported configs by type (the export is already organized into per-type folders, and each config carries its
):
- — actions ()
- — functions ()
- — interfaces / enums ()
- — Footprint-tier OData datasources
- — cloud-tier datasources (OData or flow-backed)
- , , , , , ,
A config whose type doesn't match the table is recorded as
and skipped (do not block the run on it).
3. Run the 5 checks
Run each check against the enumerated files. Order doesn't matter; do whichever is cheapest first. Collect findings as a structured list, never edit in-place.
4. Report
Compose the punch-list per the
Output format section below. Findings are grouped by check type. End with a one-line summary (
,
,
,
). Do not propose fixes beyond a one-line pointer.
The 5 Checks
1. Description Validation ()
Scans: every component file (all suffixes above).
Rule (from
datex-studio-conventions/defaults.md
and
):
- must not be .
- must not be (empty string).
- must be ≤ 100 characters. This is a hard SQL column cap on the Footprint side — imports fail with a SQL truncation error when exceeded.
Severity:
- or description → fail.
- description → fail (not a warning — imports break).
- Description present and within cap → pass.
Output line:
[pass] i_awi_configuration-customType.json — "Allocation config interface" (35 chars)
[FAIL] my_action-footprintFlow.json — description is null
[FAIL] orders_grid-grid.json — description is 137 chars (cap is 100)
2. Schema-Code Alignment ()
Scans: action files (
) and function files (
).
What it checks: the declared
/
JSON declaration vs. the embedded TypeScript code that lives at
nodes[0].stepConfig.executeCodeConfig.code
(per the pattern documented in
— flow code lives inside the JSON, not on top).
Procedure:
- Parse and from the JSON.
- Extract the embedded code string (and iterate for multi-step flows).
- Heuristic match each declared against references in the code.
- Heuristic match each declared against references in the code.
- Look for or references in code that have no matching declaration.
Severity:
- Declared not referenced in code → warning (may be deliberate — kept for back-compat, future use, or external wiring).
- Declared never assigned in code → warning.
- Code references / that has no matching declaration → fail (runtime error or silent ).
Caveat: this is a heuristic string match, not a TypeScript compiler. False positives are acceptable when surfaced as warnings — the caller decides whether each warning is real. Do not escalate a warning to a fail without source-code grounded evidence.
Output line:
[warn] plan_inventory_consumption_action — inParam 'unused_param' declared but not referenced in code
[FAIL] commit_allocation_plan_action — code references $flow.inParams.foo but 'foo' is not declared in inParams
3. Type Resolution ()
Scans: every component file. Looks for
"objectType": "<Package>.<TypeName>"
values anywhere in the JSON (commonly inside
,
,
objectTypeDef.properties[*].objectType
, and nested datasource shapes).
Rule: every referenced
must resolve to either:
- A config owned by the current package on the branch (a
customtype/<TypeName>-customType.*
entry in the temp export, equivalently dxs source explore configs --branch <id> --type customtype --search <TypeName>
), or
- A type owned by another package the project depends on (treat unknown packages as "out of scope, do not report" unless the caller explicitly asked to chase cross-package references).
Procedure:
- Determine the current package (read from one of the package's component files; cross-check via folder convention).
- Collect every string across all scanned component files.
- For each in-package reference, verify a matching customType config exists on the branch (in the temp export's folder). Fast resolution shortcut: pull the branch's nomenclature registry once —
dxs configuration nomenclature -b <branchId>
returns a flat catalog (with enum members in ) to membership-test every against in one pass, instead of a per-type lookup. See ../datex-studio-shared/context-navigation.md#discovering-custom-types-and-enum-members.
- Report unresolved references.
Severity:
- Reference to a type in the current package with no matching customType config on the branch → fail.
- Reference whose package is unknown to the project → warning (may be a typo, may be a legitimate external package).
Output line:
[FAIL] orders_grid-grid.json — references `Allocations.i_unknown_type`, no i_unknown_type-customType.json found
[warn] import_orders_action-footprintFlow.json — references `ExternalPkg.i_thing`; ExternalPkg is unknown to this project
4. OData Schema Pre-Flight ()
Scans: OData datasource files —
*-footprintDatasource.json
, and
files whose
is
. Skips flow-backed datasources.
Procedure:
- Resolve the connection. Each datasource references a connection (a connection ID or a connection name). Determine it from the file. If ambiguous, ask the caller which connection to validate against.
- Delegate to for every entity / property / navigation lookup. Do not load raw OData metadata documents into the parent context — returns concise structured answers and handles connection resolution and FootPrintApi special cases.
- For each OData datasource:
- Confirm the in exists in the OData schema.
- Confirm every property in exists on the entity type.
- Confirm every property referenced in (filter-expression operands) exists on the entity type.
- Confirm every navigation property in exists on the entity type.
- Report mismatches.
Severity:
- Missing entity set → fail.
- Missing select/filter property → fail.
- Missing navigation in expand → fail.
- Schema lookup failed (connection unreachable, schema not loaded) → warning with a note that the check could not run for this file. Do not fail the entire run on a single unreachable connection — record the warning and continue.
Output line:
[FAIL] fpds_get_material-footprintDatasource.json — entitySet 'Materials' not found in schema (did you mean 'Material'?)
[FAIL] ds_orders_grid-datasource.json — selects: property 'CustomeName' not on entity 'Orders' (typo?)
[warn] ds_legacy_thing-datasource.json — could not reach connection 'LegacyProd'; check skipped
5. Result-Shape Sync ()
Scans: all datasource files (both
and
*-footprintDatasource.json
, both OData and flow-backed).
Rule: the datasource's query-side shape (
queryOptionsObjectTypeDef
) and its consumer-facing shape (
outParams[0].objectTypeDef
) must agree. Each property on one side should have a matching entry on the other with the same
and the same
. Drift between these two declarations is the canonical "datasource works in Studio preview but breaks consumers" failure mode.
Procedure:
- Read
queryOptionsObjectTypeDef.properties[]
from the file.
- Read
outParams[0].objectTypeDef.properties[]
from the file.
- Pair properties by .
- Flag any property on one side without a counterpart on the other, or any pair whose differs.
Severity:
- Property present on one side, missing on the other → fail.
- Property pair with mismatched → fail.
- Property pair with matching and but different ordering / metadata → nit.
Output line:
[FAIL] ds_open_orders-datasource.json — `queryOptionsObjectTypeDef.OrderId` is `int32`, `outParams[0].objectTypeDef.OrderId` is `string`
[FAIL] fpds_lots-footprintDatasource.json — `outParams[0].objectTypeDef` declares property `LotCode`, missing from `queryOptionsObjectTypeDef`
Output Format
Group findings by check type, in the order of the five checks above. Within each group, list failures first, then warnings, then nits. Pass-lines are optional — include them when the caller asked for a verbose run, omit them when the run is the default lint summary. Always end with a one-line summary.
## 1. Description Validation
[FAIL] my_action-footprintFlow.json — description is null
[FAIL] orders_grid-grid.json — description is 137 chars (cap is 100)
[pass] i_awi_configuration-customType.json — "Allocation config interface" (35 chars)
...
## 2. Schema-Code Alignment
[FAIL] commit_allocation_plan_action — code references $flow.inParams.foo but 'foo' is not declared in inParams
[warn] plan_inventory_consumption_action — inParam 'unused_param' declared but not referenced
## 3. Type Resolution
[FAIL] orders_grid-grid.json — references `Allocations.i_unknown_type`, no i_unknown_type-customType.json found
## 4. OData Schema Pre-Flight
[FAIL] fpds_get_material-footprintDatasource.json — entitySet 'Materials' not found (did you mean 'Material'?)
[warn] ds_legacy_thing-datasource.json — could not reach connection 'LegacyProd'; check skipped
## 5. Result-Shape Sync
[FAIL] ds_open_orders-datasource.json — `OrderId` type drift: query=int32, outParams=string
## Summary
files checked: 142
passes: 128
warnings: 7
failures: 7
scope: all
If a check ran with no findings, write
under that heading rather than omitting it — explicit emptiness is more legible than silent absence.
Rules
- Read-only. Do not modify any files. Do not auto-fix. Do not run mutating commands. The validator's job is to report; the parent (or the user) decides what to do.
- Punch-list, not rewrite. Findings are one-line citations with file path and a brief evidence line. Do not include long code suggestions or proposed JSON. If a fix is non-obvious, name the matching creator skill that owns the fix (e.g. "route to "), not the fix itself.
- Delegate OData schema to . Never load raw OData metadata into the parent context. This is the single biggest context-poisoning anti-pattern in Datex Studio work.
- Heuristic checks may warn, not fail. Schema-code alignment in particular is a string-match lint, not a compiler. Use for "may be real" and only when the evidence is unambiguous.
- Scope discipline. When the caller scopes to a check name, run only that check. When they scope to a subtree, walk only that subtree. Do not expand scope on your own initiative.
- Don't speculate about intent. If a violation could be deliberate (a kept-around for external wiring, an declared wider than the query for a future-proofing reason), flag it as a warning with a note — not as a failure.
- One run per invocation. Don't loop the validator until "clean." Surface the punch-list and let the parent decide whether to iterate.
- Branch is the source of truth. Enumerate and export configs from the branch via
dxs source explore configs
/ dxs source document build
. Never read a local checkout as authoritative.
- No writes to the branch, no fix patches. The only local artifact is the throwaway
dxs source document build
temp export, which the checks read and which you delete when done. The report itself is returned in the response body — no , log, or patch files.