buildingSMART / buildingSMART/IFC5-development

CLI: `--no-validate` cannot bypass validation — `IfcxLayerStack` always validates during `Build()`

Open
#133 1 comment 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.