Conversation
…atten EavExtraction.WalkPropertiesNative tested 'val is IReadOnlyDictionary<string,object?>', which is false for the Revit parameter extractor's strongly-typed nested dicts (Dictionary<string, Dictionary<string,object?>> — IReadOnlyDictionary is invariant in TValue), so the entire Instance/Type parameter subtree was silently dropped from eav (only Material Quantities + class props survived). Add TryAsStringKeyedRecord: recognise any string-keyed IDictionary, normalising typed nested dicts to IReadOnlyDictionary<string,object?>. Fast path unchanged so uniform <string,object?> dicts (Navis) stay allocation-free and byte-identical. Validated on Snowdon: eav distinct paths 271 -> 1059, rows 97k -> 398k; Instance(237)+Type(551) param paths now present with value/units/internal_definition_name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SGEO mesh blobs do compress meaningfully with Zstd (quantized verts/indices have structure); Zstd is fast and DuckDB reads it natively. Replaces CompressionMethod.None. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…OT §6) Catalog (EnvelopeWriter): meta/rel_types/node_kinds tables seed the rel+kind vocabulary INTO the artefact so a generic consumer learns names+namespaces and can skip unknowns. Type-param dedup (weak ref in eav): type params are a function of an element's TYPE, so they were duplicated on every instance (Snowdon Structural: 38% of eav rows, 96.6% redundant). Add types/type_eav/object_type + an object_properties VIEW. EavWriter.AddType writes type_eav ONCE per type (lazy factory runs once, not per instance) and links each object via object_type; ObjectsArtifactPipeline.AddProperties splits Instance vs Type/System params. Shared paths dict keeps path-discovery unchanged; the view preserves the flat read contract. Validated on Snowdon arch: total eav rows 398,391 -> 279,102 (-30%); type params 144,017 -> 6,842 (21x, 367 types); object_properties re-flattens to the full per-object set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ckDB) Port EavWriter + EnvelopeWriter from DuckDB to direct row-group-buffered Zstd parquet (one file per table) a la GeometriesParquetWriter: passive columnar files, no WAL/checkpoint/index, bounded memory at any row count (removes the eav finalize-OOM cliff). New generic ParquetTableWriter. The object_properties view + the catalog ship as a manifest.sql the consumer runs on attach (DuckDB reads parquet natively). Size: eav ~87% smaller, envelope ~81% (Trabzon T1: eav 12.3->1.5MB, envelope 2.3->0.4MB; ~67% of the total artefact set). Data validated IDENTICAL: parquet vs duckdb row counts match exactly across objects/eav/types/type_eav/object_type/relations/nodes/geometries on Trabzon T1 (16,251 objects) and T2 (22,425). Tests read back via read_parquet; 39/39 pipeline tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pre-allocated versionId flow + filename-keyed sign/complete, matching the
server's new data-endpoints contract. Validated E2E (3/3 Revit->bundle uploads,
schemaVersion=3, 12 files served).
- ModelIngestion carries the server pre-allocated versionId (dedicated field,
distinct from the Success-gated statusData.versionId)
- ModelIngestionResource.Create requests it
- ArtifactPipeline: factory/ctor thread versionId; sign sends {files:[basenames]},
complete sends {etags,rootId,totalChildrenCount}; dropped viewer/eav purpose
constants; UploadAsync/UploadFilesAsync share a filename-keyed core; returns the
pre-allocated id (cross-checked against any server echo)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correct the PackfileLoader2 consumer contract after E2E validation: manifests
are producer-LOCAL and never served. The server hands a flat 12-parquet list via
GET .../versions/:v/artifacts (presigned GET URLs); the consumer builds its own
read_parquet views and embeds the object_properties union (the manifest.sql is a
template to copy into code, not a file to fetch). New shape flagged by
schemaVersion=3; files keyed versions/{versionId}/{name}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The producer manifests were never uploaded or served (server stores parquet verbatim; consumers hold presigned URLs, not the basenames the manifest's read_parquet() hardcodes — so it could never resolve anyway). Remove Manifest() from EavWriter/EnvelopeWriter; EavDbPath/EnvelopeDbPath now return the output dir. The canonical read recipe + the object_properties flat view (instance ∪ deduped-type) now live in topology-envelope-SOT.md §4/§6 as the single source of truth; the PackfileLoader2 handoff points there. Bundle is now 12 parquet on disk == 12 served (no .sql). Build clean, 39/39 pipeline tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- handoff-server-v2-data-endpoints.md: add a SUPERSEDED/RESOLVED banner. It was the pre-implementation "open questions for the server thread" doc; all resolved now — server built + E2E-validated, manifest dropped, versionId naming, dataShape skipped. Body kept as historical record (banner disclaims its open questions). - handoff-packfileloader2-envelope.md: clear residual staleness — naming is SETTLED (base = pre-allocated versionId, not "provisional"), strike the *.manifest.sql file rows (not produced/served), fix the dangling EavWriter.Manifest() ref -> SOT §6. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ANCE (->node) DEFINES (rel 4) carried "node(DEFINITION) -> geometry | nested INSTANCE node", but geometry-K and node-K are independent per-namespace counters that overlap. A single rel spanning both namespaces is unresolvable: the day a real DEFINES->nested-INSTANCE edge is emitted, its node-id dst reads as a geometry-id (and vice versa), so definition-graph traversal sees phantom cycles -> infinite expansion. Harmless today only because ODA bakes nesting flat (every DEFINES dst is in fact a geometry; the rel_types catalog's dst_ns="geometry" is accidentally true). Pre-empt before nesting ships: - RelKind.Defines (4) is now geometry-only; new RelKind.DefinesInstance (9) is node(DEFINITION) -> node(INSTANCE). rel fixes the dst namespace, mirroring the existing DISPLAY/DISPLAY_INSTANCE split. - ObjectsArtifactPipeline: Defines(definitionK, geometryK) + new DefinesInstance(definitionK, instanceK). rel_types gains row 9; catalog dst_ns is now truthful. - Tests + the PackfileLoader2 handoff updated; "never resolve a DEFINES dst as a node id" called out. Build clean, 39/39 pipeline tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Standalone console harness that walks a stored Speckle Base graph and drives ObjectsArtifactPipeline to produce the 3-artefact bundle (geometries+eav+envelope), for validating the v2 artefact-bundle migration against real models. - GraphArtifactProducer: first-cut Base-graph driver (DataObject leaves; definition geometry routed to the geometry namespace; dangling proxy refs skipped). Validated on real Revit/architectural models — DEFINES/HAS_MATERIAL 100% resolve, 0 phantoms. - Program/Options: --local (ndjson) | --remote (SDK server-pull) input; --out | --upload (v2 sign/PUT/complete) output. - RemoteSource: latest-version resolve + SDK deserialize (User-Agent for Cloudflare). - BundleUploader: v2 envelope-bundle upload; presigned S3 PUTs via auth-free client. - run-validation.sh + README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…' into dim/artefact-graph-harness
… (gap 2) Per SOT §1, atomic = any collection leaf with an applicationId, not just DataObject. EmitObject now dispatches by leaf shape: - mesh-atomic: leaf IS geometry -> object-K + geometry-K + DISPLAY(objK -> own geomK) - instance-atomic: bare InstanceProxy leaf -> object-K + DISPLAY_INSTANCE -> INSTANCE node - dataobject-atomic: walk displayValue (unchanged; Revit regression-clean) Definition members route by shape: raw mesh -> Defines (rel 4, geometry); nested instance -> DefinesInstance (rel 9, INSTANCE node) via the merged gap-2 API. GetBaseList falls back to the '@'-prefixed dynamic key (older connectors keep @elements/@displayValue dynamic). S3 PUT via auth-free client; GraphQL UA for Cloudflare. SketchUp: objects 0 -> 26,224; DEFINES/DEFINES_INSTANCE/DISPLAY/DISPLAY_INSTANCE 100% resolve, 0 phantoms, 0 encode failures. Revit (snowdon) unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#514) HAS_COLOR's src is "geometry|object" per the spec, but the two dense K-spaces overlap numerically — a consumer reading an edge cannot tell an object-sourced instance colour from a geometry-sourced one, so it either drops instance colours or paints an unrelated mesh. Tag the namespace in the edge's ord column: 0 = geometry (what every pre-tag bundle wrote), 1 = object. HasColor gains an optional srcIsObject flag; the reader splits ColorByObject out of ColorByGeometry. Fully backward compatible — old bundles read exactly as before. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(artifacts): tag the HAS_COLOR source namespace via ord (ENG-8822) HAS_COLOR's src is "geometry|object" per the spec, but the two dense K-spaces overlap numerically — a consumer reading an edge cannot tell an object-sourced instance colour from a geometry-sourced one, so it either drops instance colours or paints an unrelated mesh. Tag the namespace in the edge's ord column: 0 = geometry (what every pre-tag bundle wrote), 1 = object. HasColor gains an optional srcIsObject flag; the reader splits ColorByObject out of ColorByGeometry. Fully backward compatible — old bundles read exactly as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(artifacts): recover untagged object-sourced HAS_COLOR edges (ENG-8822) Bundles written between the object-sourced colour edge (connectors #1473) and the ord namespace tag carry an instance colour with ord=0, so it lands in ColorByGeometry and is dropped — those versions would need a re-send to ever show instance colours. Recover them, but ONLY when the geometry reading is provably impossible: the src is not a geometry K yet IS an object K. A tagged bundle, or an untagged edge whose K collides with a real geometry, is never second-guessed (a collision would be a coin flip — re-send with a current producer to tag it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(artifacts): carry material name + emissive/ior through the bundle (ENG-8791) Send: AddMaterial gains name (authored host material name -> shared node name column; NOT part of the intern key) and optional emissive/ior — the remaining universal PBR scalars. EnvelopeWriter.AddNode widened to the 14-column nodes schema from the regenerated bundle-spec. Receive: ArtefactNode carries Emissive/Ior (Has()-guarded for pre-8791 bundles); ObjectsArtifactReader rebuilds RenderMaterial with the authored name, emissive, and ior as the v1 RhinoMaterialUnpacker-convention dynamic prop so receive converters find it where v1 put it. Requires speckle-bundle-spec main @ 92590bc (emissive/ior columns). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(artifacts): normalize black emissive to NULL in AddMaterial (ENG-8791) Black RGB emission IS 'no emission' (alpha byte meaningless), so AddMaterial normalizes it to NULL centrally — connectors keep passing RenderMaterial.emissive (black default) naively and the bundle still carries NULL, which null-RLEs away. Consumers already default NULL to black on every path (SDK reader, Rhino/GH receive, viewer). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* first pass * Correctness * feat(harness): migrate raw solids + v2 pre-collection graphs; trim comments - Emit the SOLID relationship for raw-encoded objects (BrepX/ExtrusionX/ SubDX/SolidX, RhinoObject/AutocadObject) during migration: 3dm/sat blobs via AddRawGeometry + Solid, with definition members riding DEFINES. - Add v2 backwards-compat: when the root is not a Collection, synthesize the collection hierarchy from each object's dynamic-property path. - Trim verbose comments across the touched harness/pipeline files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * simplify a bunch * duckdb reads * duckdb helper * fix(harness): skip unencodable geometry instead of aborting the migration GraphArtifactProducer.AddGeometry routed emission through the new PipelineExtensions.AddGeometryMigrated, which returns null for geometry the SGEO encoder can't handle (Surface/Vector) so it is skipped rather than throwing. The bool it returned was inverted, so DISPLAY edges (and embedded materials) were wired only for skipped geometry and dropped for everything actually written — a model migrated with objects but zero DISPLAY edges. Return true only when a geometry row was written, and don't mark skipped appIds as seen. Also in this change: - PipelineExtensions.AddGeometryMigrated centralises v2 graph fixups: Arc plane normalization (moved out of ObjectsArtifactPipeline), legacy mesh-face re-encoding, default ICurve domain, and skipping Surface/Vector. - Skip RenderMaterial nodes during traversal (v2 commit gap). - Deprecate Objects.BuiltElements.NetworkLink and Revit.FamilyInstance. - run-validation.ps1: use the `remote --dest-server/--dest-project/--dest-model` CLI (with optional --legacy-api). - .gitignore: ignore local secret-bearing launchers/run configs (*.local.ps1, etc.). Verified: rhino/objects DISPLAY 0 -> 331 (HAS_MATERIAL 0 -> 327); the pinned Vector-geometry model that previously threw now migrates cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(harness): migrate definition-member geometry; retarget net10; refresh README - route definition-member geometry through AddGeometryMigrated so block/definition content gets the same Arc / legacy-mesh-face / curve-domain migration, and unencodable Surface/Vector geometry is skipped rather than aborting - retarget Speckle.Sdk.Artifacts.Harness to net10.0 (regenerated packages.lock.json) - rewrite README for the remote/ndjson/packfile/selftest subcommand CLI Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * minor tweaks * In place migrations * refactor(harness): drop the ndjson migration path Migration now goes through DuckDB packfiles only — on disk via `packfile`, or fetched from a server via `remote`. Removes the `ndjson` subcommand, RunNdjson/LoadNdjson and NDJsonTransport, plus the root-autodetect/collection-candidate fallback that only the NDJSON path used. Also refreshes the `remote` command description, which still described only the source -> destination flow and omitted SPECKLE_TOKEN, now that in-place is the default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * text annotations * refactor(harness): split the producer into v2 and v3 implementations GraphArtifactProducer handled both graph vintages in one class, with the concerns tangled: one flag guarded the hierarchy strategy while eight other seams were unconditional code that no-opped on the other vintage. Split behind IGraphArtifactProducer: - V3GraphArtifactProducer — proxies, instances, raw solids, ByLayer binding - V2GraphArtifactProducer — embedded renderMaterials, synthesized or real collections, and the RenderMaterial-in-elements workaround - ArtifactHelper — the stateless pure functions (keys, member readers, property extraction), injected as a singleton; per-run state stays in each producer The factory now picks the implementation per graph: a root is v3 if it declares version 3 or carries any root-level proxy collection, else v2. This also fixes a recall gap — 2.13-2.20 graphs have a Collection root via deprecated type aliases, so `root is not Collection` was silently routing them to the v3 path. Embedded-material handling is dropped from v3 entirely: a v3 graph is proxy-bearing by definition, so the fallback could never fire. Verified against synthetic packfiles: v3 output is byte-identical to the pre-refactor bundle (all 12 files), and both vintages classify and produce correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * small tweaks * Add otel * validate service provider * remove console logging * afsd * Trace correctly * asdf * asfdafsd * Flush logging * Views * feat(harness): migrate RenderMaterial name, emissive and ior The bundle spec added nullable emissive/ior columns to MATERIAL nodes and declared the shared name column for them (ENG-8791), so both producers now pass all three. name and emissive are typed on RenderMaterial; ior has no typed member, so it is read dynamically (double or long). emissive is passed naively — the pipeline normalizes a black RGB to the bundle's NULL. Also folds the level-elevation read into the shared ReadDouble helper. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* v2-plane skip * Fallback to ServerTransport * fix(harness): fill missing packfile objects from the server A few packfiles on the server are missing objects, which previously killed the migration partway through the deserialize with a bare KeyNotFoundException. EnsureAllObjects now checks the packfile against its own root closure with a single local DuckDB query. When nothing is absent it returns the DuckDbTransport untouched — no MemoryTransport, no ServerTransport, no network. Only when something is missing does it build a local-only aggregate and let ServerTransport.CopyObjectAndChildren diff against it and download just the absent objects into memory. Anything still missing afterwards is absent from both sides and now fails with the ids rather than mid-deserialize. Nothing is written back to the packfile. Supporting fixes: - PackFileManager.GetObjectData returns null instead of throwing on a miss, so the aggregate can fall through to memory. Adds HasObjects, which is what makes the local completeness check possible. - AggregateTransport: HasObjects stops once nothing is left to check and drops a redundant O(n^2) removal; CancellationToken now fans out to children instead of being silently stored; CopyObjectAndChildren throws, since the direction is reversed (copy INTO the aggregate). Documents that read transports must all be local — an aggregate that can reach the server answers "I have everything" to the very probe that decides what to download. - Cancellation is threaded through the fill, which was uncancellable. Also drops the unused `packfile` CLI subcommand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* v2-plane skip * Fallback to ServerTransport * fix(harness): fill missing packfile objects from the server A few packfiles on the server are missing objects, which previously killed the migration partway through the deserialize with a bare KeyNotFoundException. EnsureAllObjects now checks the packfile against its own root closure with a single local DuckDB query. When nothing is absent it returns the DuckDbTransport untouched — no MemoryTransport, no ServerTransport, no network. Only when something is missing does it build a local-only aggregate and let ServerTransport.CopyObjectAndChildren diff against it and download just the absent objects into memory. Anything still missing afterwards is absent from both sides and now fails with the ids rather than mid-deserialize. Nothing is written back to the packfile. Supporting fixes: - PackFileManager.GetObjectData returns null instead of throwing on a miss, so the aggregate can fall through to memory. Adds HasObjects, which is what makes the local completeness check possible. - AggregateTransport: HasObjects stops once nothing is left to check and drops a redundant O(n^2) removal; CancellationToken now fans out to children instead of being silently stored; CopyObjectAndChildren throws, since the direction is reversed (copy INTO the aggregate). Documents that read transports must all be local — an aggregate that can reach the server answers "I have everything" to the very probe that decides what to download. - Cancellation is threaded through the fill, which was uncancellable. Also drops the unused `packfile` CLI subcommand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Ignore brep in displayValue as they are invalid * Handle bad curves --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…521) * feat(harness): read v2 displayStyle colours Adds ReadV2DisplayStyle and ReadArgb to ArtifactHelper so a v2 displayStyle's packed ARGB carries through to the bundle, accepting both the signed and unsigned spellings of a 32-bit colour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(harness): migrate v3 groupProxies to v4 IN_GROUP v3 group proxies (Rhino/AutoCAD/Civil3d/Plant3d) were dropped on migration: nothing in the SDK called InGroup or AddContainer, so authored groups never reached the bundle even though the receive path already reads IN_GROUP into GroupsByObject. V3GraphArtifactProducer now emits one CONTAINER(subtype "Group") per proxy plus an IN_GROUP edge per resolved member. Groups migrate flat -- v3 records no nesting, and an outer group already lists its inner groups' members. Dangling refs are skipped rather than minting phantom object Ks, and a group whose every ref is absent emits no node. Adds GROUP/IN_GROUP/skipped counters to Stats and emit coverage over the envelope writer (overlapping membership, null group name, flat def_ref). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bjects for v1 receive (#520)
* Add default scene view * format
…iter ParquetTableWriter.AddRow took a positional object array validated against nothing while the table shape came from the spec-generated BundleSchemas — the same latent bug class that produced the Jul 29 empty-envelope incident in the native converters (spec inserted emissive/ior before elevation, 12 -> 14 nodes columns; schema auto-updated, positional call sites did not, every row was silently dropped). AddRow now throws an ArgumentException naming the file and both counts on any arity mismatch instead of misaligning or dropping rows. Adopts the generated per-table column-index constants (BundleCols, speckle-bundle-spec codegen — PR in flight there): spec-schema'd tables (nodes, relations, camera_views) are now constructed via a new ParquetTableWriter ctor that takes the generated ColumnSpec[] plus the matching BundleCols ColumnCount and cross-checks the two generated files at construction. That ctor also keeps Parquet.Net types out of the public surface, which the ILRepack internalizes — making it the only ctor callable from outside the assembly (the new unit tests use it to exercise the guard on the real 14-column nodes schema). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mat failure Not introduced by this PR: big-truck's PR Test build job has been failing 'dotnet csharpier check .' on this file before ever reaching the build step, masking everything downstream. Formatting it here so CI can proceed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s not exist there Pre-existing on big-truck (introduced in #520, last touched at 8042489): Speckle.Objects targets netstandard2.0, which has no Dictionary<K,V>.GetValueOrDefault. CI never surfaced it because the build job dies at the csharpier gate first. TryGetValue is behavior-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(ENG-8849) speckle-bundle-spec d485e68 broadened HAS_MATERIAL's src namespace from 'geometry' to 'geometry|instance' (instances can carry material overrides). The SDK compiles the spec's generated catalog verbatim, so with CI now pinned to spec main this assertion must match the broadened row. Legitimate sync, not a behavior change in the writer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…— sync with #520 Pre-existing failure on big-truck, masked by the csharpier CI gate: #520 (8042489) deliberately made ObjectsArtifactReader skip objects with no DISPLAY geometry and no accepted SOLID instead of fabricating an empty-displayValue DataObject the v1 converter pipeline has no path for (see BuildGeometryObject's documented null return). This test predates #520 (#516) and still asserted the old fabricated-DataObject behavior for the PreferSolids:false path. Updated to the documented skip semantics; the PreferSolids:true path is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iling Speckle.Sdk.Parquet Speckle.Sdk.Parquet compiles BundleSpec.cs/BundleSchemas.cs/BundleCols.cs from $(MSBuildThisFileDirectory)../../../speckle-bundle-spec — a sibling checkout one level up from the SDK repo. No workflow provisioned it, so every job building Speckle.Sdk.slnx failed with CS2001 (the build job was dying at the csharpier gate first, hiding its own copy of the failure). - pr.yml (build), integration-test.yml, release.yml: checkout specklesystems/speckle-bundle-spec into the workspace, then move it one level up (actions/checkout cannot write outside $GITHUB_WORKSPACE; moving also keeps the spec's generated .cs out of 'csharpier check .'). - integration-test-callable-from-server-repo.yml: SDK lives at ./client there, so the sibling location is the workspace root — plain checkout, no move. - Pinned to spec main as of 2026-08-04: ecbbd270fae06b00ee08e1b1022a08dd3f170c8f (merge of spec PR #12, the BundleCols.cs codegen). Bump deliberately. - speckle-bundle-spec is private and GITHUB_TOKEN cannot clone a second private repo, so the checkout authenticates with a new BUNDLE_SPEC_TOKEN secret (fine-grained PAT, Contents:Read on speckle-bundle-spec — same convention as speckle-converters' SUBMODULE_TOKEN, whose PAT already has that scope). The secret must be added to this repo's Actions secrets for CI to go green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y-guard fix(artifacts): fail fast on row/schema arity drift in ParquetTableWriter
…-loud writes) Seeds docs/adr/ per the atlas ADR-0003 amendment: repo-side pointer to the standing stack rule born from the 2026-08 empty-envelope.nodes incident; the binding code landed via #525. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The envelope meta table recorded the writer class name ('Speckle.Sdk
EnvelopeWriter') in produced_by, but the column is meant to carry the
producing host app's slug (e.g. 'rhino', 'revit') so consumers can
detect same-app round-trips. EnvelopeWriter/ObjectsArtifactPipeline now
take an optional producedBy (connectors pass ISpeckleApplication.Slug);
null keeps the old generic label for un-migrated producers.
Drive-by: replace Dictionary.GetValueOrDefault in ObjectsArtifactReader
with TryGetValue — unavailable on netstandard2.0, it broke the net48
plugin (Local) builds.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
docs(adr): pointer stub for atlas ADR-0004 — generated constants, fail-loud writes
* Expand envelope.meta to include source info * bunch of fixes because claude got carried away * more fixes * Remove comments * pick a less misleading test data * Fix
* Fix CI secrets * Pass integration secrets properly
Test-only. Locks the two properties the Rhino/SketchUp member-layer scheme
rests on, neither of which is enforced anywhere:
- The `@speckle.geometry_k` eav stamp survives the parquet round trip, and a
dotted eav path comes back NESTED. It is the only route from a definition's
DEFINES geometry back to the member object holding the layer, so if it stops
round-tripping every block member silently lands on the base layer.
- A carrier object row — object-sourced IN_COLLECTION, no render edge — does not
materialise as a scene object. ObjectsArtifactReader must keep dropping it;
the day it doesn't, members bake twice and appear in the scene explorer.
Also documents a surprise the writer has no say over: eav infers a type per
value, so a multi-K stamp ("7,8") stays a string while a single-K one is coerced
to a number and reads back as a double. Any reader has to accept both. The
fixture uses the real Rhino shape (a member owning a 3dm solid AND its display
mesh) so the list form is what's asserted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er-in-collection test(artifacts): pin the definition-member carrier shape (ENG-9110)
* Fix CI secrets
* Pass integration secrets properly
* feat(migration): emit Revit room/host topology relations (ENG-9050)
Convert the five flat v3 Revit property keys (roomApplicationId,
spaceApplicationId, fromRoomApplicationId, toRoomApplicationId,
parentApplicationId) into IN_ROOM, CONNECTS_TO and SUBELEMENT edges in
V3GraphArtifactProducer, mirroring big-truck's EmitElementTopology:
- IN_ROOM from room ?? space (room wins), ord 0
- one directed CONNECTS_TO per opening when both rooms resolve, ord =
the opening's own object K (scope)
- host SUBELEMENT unless the elements lineage already provided one
Refs are bare UniqueIds while sent appIds may carry the _t{8-hex}
transform suffix; resolution is exact-match first, then the referring
element's own suffix (same document, same transform). Dangling refs are
skipped and counted in Stats.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(migration): map v3 Revit linked-model tier to IN_MODEL (ENG-9054)
Detect federated v3 Revit sends (SendCollectionManager nested mode) and
retag the per-source-model collections as CONTAINER(subtype=Model):
- trigger: root has a child collection named exactly like the root (the
host-model wrapper) AND >=1 sibling collection whose descendants carry
the _t{8-hex} linked-model appId suffix; anything else is untouched
- detected collections become flat Model containers instead of
collection nodes; descendants get IN_MODEL edges via the hierarchy walk
- default scene view gains the Rel(InModel) tier when >1 model container,
matching the v4 Revit builder
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(migration): resolve linked-model ON_LEVEL/IN_GROUP refs (ENG-9077)
Level and group proxies reference bare Revit UniqueIds, but the v3
connector interned linked-model elements under {UniqueId}_t{hash}
appIds (one per link placement), so every linked element's ref was
skipped and federated migrations lost the level axis.
Collect the distinct linked-model suffixes while interning and resolve
a missed bare ref by appending each and re-checking the seen set — one
ON_LEVEL/IN_GROUP edge per placement, no derived-key index. Skip
counters now count only refs to elements genuinely absent from the
send.
Also renames the suffix helpers to linked-model terms and rewrites the
related comments to explain the Revit appId scheme rather than the code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(parquet): suppress IDE0370 false positive on scheduler-thread field init .NET SDK 10.0.2xx's new IDE0370 flags the null-forgiving initializers in ParquetTableWriter as unnecessary, but removing them raises CS8625 — the fields are assigned on the scheduler thread, invisible to flow analysis. Unblocks local builds on rolled-forward SDKs (global.json pins 10.0.200 with rollForward latestFeature). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(harness): TreeMaterializer — graph-to-tree projection for artefact bundles Materializes a 4.0 bundle back into a v3-style Base/Collection tree by composing the existing readers (ArtefactBundleReader + ObjectsArtifactReader) and stamping the root with the version=4 vintage marker. First piece of the v4 receive-DX work (Operations.receive parity for graph commits). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: csharpier formatting for TreeMaterializer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…erials on artefact receive (#534) * feat(artifacts): surface type parameters and instance-sourced materials (ENG-9136, ENG-9109) Type Parameters/System Type Parameters are already split out and deduped per-type on send (EavWriter's types/type_eav/object_type tables), but ArtefactBundleReader never read them back, so no host builder could reach them. Adds TypePropertiesByObject, resolved via a shared dictionary per type so many instances of one type cost one parse, not one per instance. HAS_MATERIAL's src was broadened to geometry|instance (ENG-8849) so a material painted directly on a block placement has somewhere to go, but nothing produced or consumed the instance-sourced form yet. HasMaterial gains an srcIsInstance tag (mirroring HasColor's srcIsObject) and ArtefactRelations exposes the resolved MaterialByInstance map. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(artifacts): surface type parameters and instance-sourced materials (ENG-9136, ENG-9109) * chore: csharpier --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…acts (#531) * fix(artifacts): preserve repeated instance placements in ObjectsArtifactReader * chore: csharpier
The completeWithVersion mutation bypasses the v2 REST uploads/complete seam, so the server never dispatches viewer .dat generation and the resulting version is not viewer-consumable in the bundle era. Mirrors the server-side GraphQL @deprecated (ENG-8970); warning-only so deployed packfile-era connectors keep compiling. Replacement is the ArtifactPipeline completion path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ingestionresourcecompletewithversion-obsolete-in feat(api): mark ModelIngestionResource.Complete obsolete (ENG-9221)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Keep in draft, just so we can keep track of changes