def assemble_bundle(start_id=None, query=None, role="engineer",
                    max_hops=3, top_k=10, include_full_dr=False, redact=True):

    # 0. anchor resolution
    if not start_id:
        anchors = vector_search(query, top_k=3)  # maybe return DR ids
        if not anchors:
            return {"error":"no anchors found"}
        start_id = anchors[0]["id"]

    # 1. try cache
    cache_key = f"bundle:{start_id}:{role}:{max_hops}:{hash(query)}:{top_k}"
    cached = rocks.get(cache_key)
    if cached:
        return cached  # signed object

    # 2. provenance walk
    ancestors = walk_back(start_id, n=max_hops, query=query, role=role, top_k=top_k)
    # walk_back returns sorted list of metadata objects with scores

    # 3. fetch required metadata and optionally full payloads
    timeline = []
    evidence_refs = set()
    constraints_violations = set()
    for meta in ancestors[:top_k]:
        # meta is fetched from RocksDB or SQLite (fast)
        item = {
            "id": meta["id"],
            "timestamp": meta["timestamp"],
            "type": meta.get("type"),
            "statement": redact_field(meta["statement"], role) if redact else meta["statement"],
            "rationale": redact_field(meta.get("rationale",""), role) if redact else meta.get("rationale",""),
            "lifecycle_state": meta.get("lifecycle_state"),
            "relation_to_start": meta.get("relation"),
            "score": meta.get("score"),
            "tags": meta.get("tags", []),
            "role_exposure": meta.get("role_exposure", {})
        }

        if include_full_dr and is_authorized(role, meta["id"]):
            item["full_payload"] = fetch_and_decrypt_dr(meta["id"])  # heavy, audited
        else:
            item["full_payload_ref"] = meta.get("ucxl_path")

        timeline.append(item)
        for c in meta.get("constraints", []):
            if violates_policy(c):
                constraints_violations.add(c)
        evidence_refs.update(meta.get("evidence", []))

    # 4. constraints summary (human readable)
    constraints_summary = [render_policy_summary(c) for c in constraints_violations]

    # 5. goal alignment (heuristic)
    project_goal_vec = get_project_goal_embedding()  # from project metadata
    alignment_scores = []
    for t in timeline:
        alignment_scores.append( cosine_similarity(project_goal_vec, embed(t["statement"])) )
    goal_alignment_score = mean(alignment_scores)
    goal_reasons = top_reasons_from_timeline(timeline, project_goal_vec, n=3)

    # 6. suggested actions (LLM-assisted)
    # Provide LLM with:
    # - start statement
    # - concise timeline (n items)
    # - constraints summary
    # Ask: "Given constraints, what next steps? Who to ask? What to avoid?"
    llm_prompt = build_prompt_for_actions(start_statement=timeline[0]["statement"],
                                          timeline=timeline[:5],
                                          constraints=constraints_summary,
                                          role=role)
    suggested_actions = llm_call("action_suggester", llm_prompt, max_tokens=400)
    # LLM output should be parsed into structured actions {type, desc, assignee, confidence}

    # 7. escalation check
    escalation = {"required": False, "who": []}
    if any("budget" in c for c in constraints_violations) and goal_alignment_score > 0.8:
        escalation = {"required": True, "who": ["pm","finance"]}

    # 8. assemble, sign, cache
    bundle = {
        "bundle_id": "bundle:sha256:"+sha256_of_content(...),
        "start_id": start_id,
        "generated_at": now_iso(),
        "summary": auto_summary_from_timeline(timeline[:5]),
        "timeline": timeline,
        "constraints_summary": constraints_summary,
        "key_evidence_refs": list(evidence_refs)[:10],
        "goal_alignment": {"score": goal_alignment_score, "reasons": goal_reasons},
        "suggested_actions": suggested_actions,
        "escalation": escalation,
        "signatures": [signer_sig("leader"), signer_sig("assembler")],
        "cache_hit": False
    }

    rocks.put(cache_key, serialize(bundle))
    return bundle
