buildingSMART / buildingSMART/IFC5-development
CLI: `--no-validate` cannot bypass validation — `IfcxLayerStack` always validates during `Build()`
- Dominant language
- HTML
- Stars
- 220
- Forks
- 80
- Avg merge
- 4d 6h
- Merged PRs (30d)
- 2
Description
## TL;DR
`ifcx-cli compose --no-validate` still throws `SchemaValidationError` on any input that
fails schema validation, because `IfcxLayerStack`'s constructor eagerly composes with
validation hardcoded on — inside `IfcxLayerStackBuilder.Build()`, before the CLI's
`validate` flag is ever consulted. The flag only skips a *second* validation of a layer
stack that already survived the first, so it is a no-op for the exact case it exists for.
## Environment
- Commit: `02b0b21aaa4ab10354cdf931a6ec0d704d6e8d22` (current `main`)
- Node v25.9.0, CLI built per the repository's own instructions:
`cd src && npm install && npx esbuild ifcx-cli/ifcx-cli.ts --bundle --outfile=ifcx-cli.js --external:three --platform=node`
## Reproduction
Any layer stack that fails schema validation triggers it. The fixtures below set a
String-typed attribute in one layer and `null` it in a later one (found while
investigating the companion issue #132, which this bug has been masking).
base.ifcx + override-null.ifcx
`base.ifcx`:
```json
{
"header": {
"id": "repro/minimal/base.ifcx",
"ifcxVersion": "ifcx_alpha",
"dataVersion": "1.0.0",
"author": "repro",
"timestamp": "2026-07-22"
},
"imports": [],
"schemas": {
"test::rating": {
"uri": "https://example.com/repro/test-rating",
"value": { "dataType": "String" }
}
},
"data": [
{ "path": "wall-0001", "attributes": { "test::rating": "A" } }
]
}
```
`override-null.ifcx`:
```json
{
"header": {
"id": "repro/minimal/override-null.ifcx",
"ifcxVersion": "ifcx_alpha",
"dataVersion": "1.0.0",
"author": "repro",
"timestamp": "2026-07-22"
},
"imports": [],
"schemas": {},
"data": [
{ "path": "wall-0001", "attributes": { "test::rating": null } }
]
}
```
```
$ node ifcx-cli.js compose --no-fetch --no-validate base.ifcx override-null.ifcx out.json
SchemaValidationError: Error validating ["wall-0001"].attributes["test::rating"]: Expected "null" to be of type string
at ValidateAttributeValue (.../ifcx-core/schema/schema-validation.ts:40)
at Validate (.../ifcx-core/schema/schema-validation.ts:134)
at LoadIfcxFile (.../ifcx-core/workflows.ts:32)
at IfcxLayerStack.Compose (.../ifcx-core/layers/layer-stack.ts:31)
at new IfcxLayerStack (.../ifcx-core/layers/layer-stack.ts:18)
at IfcxLayerStackBuilder.Build (.../ifcx-core/layers/layer-stack.ts:80)
exit: 1
```
(Stack trace re-annotated with source locations for readability; the raw run against the
esbuild bundle shows the same frames as `ifcx-cli.js` line numbers. `out.json` is never
written.)
## Root cause
`IfcxLayerStack`'s constructor eagerly calls `Compose()`, which calls
`LoadIfcxFile(this.federated)` with no second argument — so `checkSchemas` takes its
default `true` (`LoadIfcxFile(file: IfcxFile, checkSchemas: boolean = true, ...)`,
`workflows.ts` L24):
https://github.com/buildingSMART/IFC5-development/blob/02b0b21aaa4ab10354cdf931a6ec0d704d6e8d22/src/ifcx-core/layers/layer-stack.ts#L14-L31
```ts
constructor(layers: IfcxFile[])
{
this.layers = layers;
this.Compose();
}
...
private Compose()
{
this.federated = Federate(this.layers);
// TODO: schema files
this.schemas = this.federated.schemas;
this.tree = LoadIfcxFile(this.federated);
}
```
`Build()` catches the resulting exception and returns it as an `Error`, which the CLI
rethrows — all before reaching the CLI's own, correctly wired second call
`LoadIfcxFile(federated, validate, true)`:
https://github.com/buildingSMART/IFC5-development/blob/02b0b21aaa4ab10354cdf931a6ec0d704d6e8d22/src/ifcx-cli/ifcx-cli.ts#L102-L109
```ts
let layerStack = await (new IfcxLayerStackBuilder(provider).FromId(userDefinedOrder.header.id)).Build();
if (layerStack instanceof Error)
{
throw layerStack;
}
let federated = layerStack.GetFederatedLayer();
let composed = LoadIfcxFile(federated, validate, true);
```
## Impact
- `--no-validate` cannot be used for its stated purpose: inspecting composition output of
files that fail validation. This has been masking the companion `attributes: null`
composition bug, since a nulled schema-declared attribute always dies in validation
before flatten's output can be observed.
- Library consumers going through `IfcxLayerStackBuilder` have no way to opt out of
validation at all.
## Suggested fix
Thread `checkSchemas` through `IfcxLayerStack`'s constructor and `Build()`, and have the
CLI pass its existing `validate` flag into `Build(validate)`:
fix-no-validate.patch
```diff
diff --git a/src/ifcx-core/layers/layer-stack.ts b/src/ifcx-core/layers/layer-stack.ts
index 7a2b78c..d31945a 100644
--- a/src/ifcx-core/layers/layer-stack.ts
+++ b/src/ifcx-core/layers/layer-stack.ts
@@ -11,10 +11,12 @@ export class IfcxLayerStack
private tree: PostCompositionNode;
private schemas: {[key:string]:IfcxSchema};
private federated: IfcxFile;
+ private checkSchemas: boolean;
- constructor(layers: IfcxFile[])
+ constructor(layers: IfcxFile[], checkSchemas: boolean = true)
{
this.layers = layers;
+ this.checkSchemas = checkSchemas;
this.Compose();
}
@@ -28,7 +30,7 @@ export class IfcxLayerStack
this.federated = Federate(this.layers);
// TODO: schema files
this.schemas = this.federated.schemas;
- this.tree = LoadIfcxFile(this.federated);
+ this.tree = LoadIfcxFile(this.federated, this.checkSchemas);
}
public GetFullTree()
@@ -64,7 +66,7 @@ export class IfcxLayerStack
return this;
}
- async Build(): Promise
+ async Build(checkSchemas: boolean = true): Promise
{
if (!this.mainLayerId) throw new Error(`no main layer ID specified`);
@@ -77,7 +79,7 @@ export class IfcxLayerStack
try
{
- return new IfcxLayerStack(layers);
+ return new IfcxLayerStack(layers, checkSchemas);
}
catch (e)
{
```
(plus a one-line call-site change in `ifcx-cli.ts`, `.Build()` -> `.Build(validate)`)
Happy to send this as a PR — either standalone or as a preceding step to the companion
issue's fix, since this bug is what has kept that one unobservable through the CLI.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with src/ifcx-core/layers/layer-stack.ts, especially IfcxLayerStack.Compose() and IfcxLayerStackBuilder.Build(), then inspect the compose call in src/ifcx-cli/ifcx-cli.ts and LoadIfcxFile in src/ifcx-core/workflows.ts. Reproduce with the documented `compose --no-fetch --no-validate` command and the supplied fixtures. Done means invalid input can be composed and written with `--no-validate`, while validation remains enabled by default.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100