You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/specs/pr-e-1-manifest-modules.md
+159Lines changed: 159 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -364,3 +364,162 @@ Rough breakdown:
364
364
`hubspo-rs` in <30 LOC of consumer-side code
365
365
-`.claude/specs/sprint-3-execution-plan.md` -- W1 master execution plan
366
366
-`.claude/knowledge/tier-0-pattern-recognition.md` -- Pattern E section
367
+
368
+
---
369
+
370
+
## CORRECTION (2026-05-12, PR #360 review)
371
+
372
+
**Defect:** This spec said the build script in `lance-graph-contract` emits Rust glue from each manifest, including the consumer's actor type (e.g. `actor.crate: medcare-rs`, `actor.type: MedCareActor`). For the build script to emit working Rust referencing `medcare_rs::MedCareActor`, the contract crate would need `medcare-rs` as a dependency when the `module-medcare` feature is enabled. **But every consumer crate already depends on `lance-graph-contract`** (per the consumer-template W8 spec and the Single-Binary Topology I-1 invariant: consumers always pull contract). Enabling the feature therefore creates a **Cargo dependency cycle** (contract → medcare-rs → contract), which Cargo refuses to compile.
373
+
374
+
**Fix:** Move the concrete actor registration OUT of the contract crate. The contract crate's build script should emit ONLY:
375
+
376
+
1.**OGIT::* u32 namespace constants** (pure data; no consumer code referenced)
The `lance-graph-callcenter` supervisor enumerates `inventory::iter::<ConsumerRegistration>()` at startup — no compile-time generation of consumer references in the contract crate. **No dependency cycle.**
408
+
409
+
### Option B — umbrella-binary registration crate
410
+
411
+
A separate `lance-graph-binary` (or per-deployment binary crate) depends on ALL active consumer crates AND on `lance-graph-callcenter`. The build script for THIS umbrella crate (NOT the contract crate) emits:
// (hubspo-rs absent → not registered; G=CRM stays inert)
420
+
}
421
+
```
422
+
423
+
The contract crate stays consumer-agnostic. The umbrella crate eats the dependency-graph union. **No cycle.**
424
+
425
+
### Option C — callback registry at supervisor init
426
+
427
+
`lance-graph-callcenter::supervisor::CallcenterSupervisor::with_consumers(...)` takes an explicit list of consumer types passed by the binary's `main()`. Each consumer registers itself by spec-value (no compile-time enumeration). Most explicit; no macro magic; least automation.
428
+
429
+
**Recommendation:** Option A (inventory crate) — best ergonomics, zero per-consumer wiring beyond the `inventory::submit!` macro, no umbrella-binary requirement. Used by `tracing` subscriber registry and many other Rust ecosystems for exactly this pattern.
actor_crate:Some("medcare-rs"), // string only — no Rust ref
466
+
actor_type_name:Some("MedCareActor"),
467
+
},
468
+
// ...
469
+
};
470
+
```
471
+
472
+
Every emitted symbol is **data only** — no `use medcare_rs::*` import, no actor type reference, no spawn function. The contract crate stays consumer-agnostic.
473
+
474
+
The **consumer-side** crate then reads its `MANIFEST_METADATA[G]` entry at compile time:
**Defect:** The original `pre_start` loop sketched in this spec iterates over `registry.active_g_list()` and unwraps `bundle.consumer_pointer` — but inert bundles (DOLCE G=0, FMA G=5) have `consumer_pointer = None` by design. Per the W11 smoke test spec, DOLCE must remain registered as inert context (no actor) while Healthcare spawns its actor. The original loop would either panic on `unwrap()` or return `ActorProcessingErr` and abort `pre_start` before any consumer actor spawns.
365
+
366
+
**Fix:** Skip inert bundles in the supervisor's spawn loop. Two equivalent options:
367
+
368
+
### Option A — explicit filter inside `pre_start` (recommended)
369
+
370
+
```rust
371
+
asyncfnpre_start(
372
+
&self,
373
+
myself:ActorRef<Self::Msg>,
374
+
registry:Self::Arguments,
375
+
) ->Result<Self::State, ActorProcessingErr> {
376
+
letmutchildren=HashMap::new();
377
+
forginregistry.all_registered_g() {
378
+
letbundle=registry.resolve(g).expect("registered g must resolve");
tracing::debug!("g={} is inert (no consumer_pointer); skipping spawn", g);
386
+
continue;
387
+
}
388
+
};
389
+
390
+
let (actor_ref, _handle) =Actor::spawn_linked(
391
+
Some(format!("consumer_g_{}", g)),
392
+
pointer.actor_type.spawn(),
393
+
(),
394
+
myself.get_cell(),
395
+
).await?;
396
+
children.insert(g, actor_ref);
397
+
}
398
+
Ok(children)
399
+
}
400
+
```
401
+
402
+
### Option B — narrow the iterator's contract
403
+
404
+
Rename `active_g_list()` to `active_consumer_g_list()` and have it return ONLY G slots whose bundle has `consumer_pointer.is_some()`. The supervisor loop becomes:
405
+
406
+
```rust
407
+
forginregistry.active_consumer_g_list() {
408
+
letbundle=registry.resolve(g).unwrap();
409
+
letpointer=bundle.consumer_pointer.as_ref().unwrap(); // safe by iterator contract
410
+
// ... spawn
411
+
}
412
+
```
413
+
414
+
Plus a sibling iterator `inert_g_list()` for SPARQL/Cypher consumers who need read access to all G (active + inert).
415
+
416
+
**Recommendation:** Option A — explicit filter — surfaces the inert-vs-active distinction at the spawn site (debugging clarity > iterator API minimalism).
417
+
418
+
**New test for the fix** (extends PR-F-1 test plan):
0 commit comments