Skip to content

Commit 34241fd

Browse files
committed
Merge branch 'gtf-manifest' into gtf-buildingmotif (#30)
Manifests become a set of libraries stored as owl:imports. Merged clean: nothing else on this branch touches the manifest API.
2 parents 72b9f82 + d9c0610 commit 34241fd

23 files changed

Lines changed: 977 additions & 152 deletions

.agents/skills/buildingmotif/references/validation.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,9 @@ Two things in this script that trip people up:
391391
- **Passing shape collections replaces the manifest.** `model.validate([sc1, sc2])`
392392
validates against *only* `sc1, sc2` — the model's standing manifest is **not** added.
393393
That's usually what you want for an ad-hoc "does it support *this* app?" check. To
394-
validate against the manifest plus extras, include `model.get_manifest()` in the list.
395-
`model.validate()` with no list validates against the manifest alone.
394+
validate against the manifest plus extras, spread `model.manifest.shape_collections()`
395+
into the list. `model.validate()` with no list validates against the manifest alone —
396+
that is, against the shape collections of every library the manifest names.
396397
- **`error_on_missing_imports=False` is the notebooks' default for real models.** A real
397398
model's shapes usually `owl:imports` something you haven't loaded; with `True` (the
398399
default) validation raises and stops. `False` gets you a report now — but always check

.agents/skills/buildingmotif/references/writing_shapes.md

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ these), and the shape you write depends on which:
2626
don't write these; you load them and validate against them to check the model *uses*
2727
the ontology correctly. `model.validate([brick.get_shape_collection()])`.
2828
2. **Manifest shapes** — your model's *standing* requirements: "this site has exactly 1
29-
AHU, 1 supply fan, …". A manifest is a shape collection you associate with the model
30-
via `model.add_to_manifest(...)`. Written with the `constraint:` vocabulary (below).
29+
AHU, 1 supply fan, …". You write them into a library and add that library to the
30+
model's manifest with `model.manifest.add(lib)`; the manifest is the set of libraries
31+
the model claims to satisfy. Written with the `constraint:` vocabulary (below).
3132
3. **Application / use-case shapes** — "to run G36 §4.8, an AHU must have these points."
3233
These are the pointlist/equipment shapes that answer "is my model sufficient for X?"
3334
and are what most of this file is about. Libraries tag them as
@@ -214,17 +215,31 @@ associate it:
214215

215216
```python
216217
manifest = Library.from_ontology("my_manifest.ttl")
217-
model.add_to_manifest(manifest.get_shape_collection())
218+
model.manifest.add(manifest)
218219
ctx = model.validate() # validates against the manifest by default
219220
```
220221

221-
`add_to_manifest` **merges** into whatever the model already has — call it twice and you get
222-
the union, so a manifest can grow but never shrink. To swap the requirements out wholesale,
223-
use `model.replace_manifest(sc)`, which discards the previous contents. Reach for `replace_`
224-
when re-running a script that would otherwise accumulate stale requirements across runs.
222+
The manifest is a **set of libraries**, so it behaves like one:
225223

226-
(`update_manifest` is the old name for `add_to_manifest`. It still works and warns; the name
227-
was misleading, since it never replaced anything.)
224+
```python
225+
model.manifest.add(manifest, g36) # Libraries, names, or iterables of either
226+
model.manifest.remove(g36) # KeyError if absent; .discard() forgives
227+
model.manifest.library_names # ['urn:my/manifest', ...]
228+
"urn:my/manifest" in model.manifest
229+
model.manifest.replace([manifest]) # the whole set at once
230+
model.manifest.libraries # the Library objects
231+
```
232+
233+
Shapes only ever reach a manifest *through a library* — there is no way to append loose
234+
shapes to it. That is what makes the set inspectable and subtractable: a manifest that
235+
had absorbed a copy of some shapes could grow but never shrink, and could not say where
236+
anything came from. A name that is not a loaded library is resolved through the ontology
237+
environment (cache first, then a fetch if it is a URL), so
238+
`model.manifest.add("https://brickschema.org/schema/1.4/Brick")` loads Brick if it has
239+
to; a name that resolves nowhere raises rather than failing later at validation time.
240+
241+
Storage-wise the manifest is a graph of `owl:imports` and nothing else —
242+
`model.manifest.graph` hands you a copy to serialize or diff.
228243

229244
### `constraint:` components validate but block repair
230245

API-CLEANUP.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,15 @@ the old name implied), the latter built on `ShapeCollection.replace_graph`, so i
500500
copy-on-write -- a failure leaves the previous contents intact. `update_manifest` still works
501501
and warns. All 7 call sites migrated.
502502

503+
**Superseded by the manifest rework (`gtf-manifest`).** This fix kept the manifest a *shape
504+
graph* and only made its two mutations honest, which left the underlying problem: a manifest
505+
made of copied triples has nothing to subtract by name, cannot say where a shape came from,
506+
and goes stale when its source library is reloaded. A manifest is now a **set of libraries**
507+
stored as `owl:imports` -- `model.manifest.add(lib)` / `.remove(lib)` -- so `add_to_manifest`
508+
takes libraries rather than shape collections, `replace_manifest` becomes
509+
`model.manifest.replace([...])`, and `update_manifest` is gone rather than deprecated. See
510+
`docs/explanations/manifests.md`.
511+
503512
---
504513

505514
## Proposed, not yet done

buildingmotif/api/views/model.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,10 @@ def validate_model(models_id: int) -> flask.Response:
177177

178178
# no body provided -- default to model manifest
179179
if request.content_length is None:
180-
shape_collections = [model.get_manifest()]
180+
# error_on_missing=False for the same reason validate() below passes
181+
# error_on_missing_imports=False: a service should report what it could
182+
# check rather than 500 because one manifest entry is unloadable.
183+
shape_collections = model.manifest.shape_collections(error_on_missing=False)
181184
else:
182185
# get body
183186
if request.content_type != "application/json":

buildingmotif/dataclasses/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
RepairWitness,
66
)
77
from buildingmotif.dataclasses.library import Library # noqa
8+
from buildingmotif.dataclasses.manifest import Manifest, ManifestLibraryNotFound # noqa
89
from buildingmotif.dataclasses.model import Model # noqa
910
from buildingmotif.dataclasses.shape_collection import ShapeCollection # noqa
1011
from buildingmotif.dataclasses.template import IncompleteTemplateError, Template # noqa

0 commit comments

Comments
 (0)