DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/phase-39-cross-engagement-aggregation/phase-39-cr-cross-engagement-aggregation-v0_1.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-051 — Phase 39: Cross-engagement Aggregation (v0.1)

Version. 0.1 Date. 2026-05-04 Author. Marvin Percival (DUNIN7), prepared via Claude. Target. /Users/dunin7/loomworks (substrate) on DUNIN7-M4 (MacMini). Priority. Standard. Confidential. Internal. Supersedes. Nothing for Phase 39 specifically. Companion to. Phase 39 scoping note v0.1, engine implementation strategy v0.2 §9, Phase 14 CR v0.1 (person layer and memberships), Phase 33 CR v0.1 (per-engagement activity summary). Status. First draft. Consumes settled scoping decisions from scoping note v0.1 without relitigating them.


Contents


1. Executive summary

Phase 39 adds three endpoints under /me/dashboard/ that aggregate state across all the requesting person's engagements. Today every endpoint is single-engagement-scoped. The Operator Layer Dashboard needs a single-call cross-engagement view of active jobs, items needing decision, and recently finished artifacts.

Three endpoints:

1 — GET /me/dashboard/active — Active jobs across all engagements. Shape production, render production, external polling.

2 — GET /me/dashboard/needs_you — Items requiring the person's decision. Pending shapes (awaiting confirmation), held assertions (awaiting commit), produced renders (awaiting review).

3 — GET /me/dashboard/recent — Recently finished artifacts. Completed renders with download URLs where applicable.

No migrations. No new MemoryObject types. Pure endpoint addition querying existing tables through the person's memberships.

Substrate-only phase. One checkpoint.


2. Grounding

Phase 39 is grounded in three layers, consulted in order.

Layer 1 — Methodology document v0.20. Recognition is load-bearing: the cross-engagement view makes the Operator's full portfolio visible in one surface, not fragmented across N engagement dashboards. Plain-English communication: the response schemas carry human-readable labels and context alongside IDs.

Layer 2 — Reference Design v0.1.2. The person layer (Phase 14) establishes that a person has memberships across engagements. Phase 39 aggregates state across those memberships.

Layer 3 — Specification (Playground Spec v0.10 and prior phase CRs). Phase 14 (person layer): GET /me/memberships returns the person's engagement list. Phase 33 (activity observability): GET /engagements/{eid}/activity-summary returns per-engagement counts. Phase 39 replaces N concurrent per-engagement calls with one aggregated call per dashboard zone.


3. Prerequisites

3.1 Baseline

3.2 Pre-flight checks

CC confirms before Step 1:

  1. uv run pytest -v reports the expected passed count. If the count differs significantly, stop and reconcile.
  2. GET /me/memberships endpoint location identified. Confirm response includes engagement_id, engagement_name (or title), and designation.
  3. EngagementMembership model location identified. Confirm designation field structure (enum, string, or list).
  4. shaping_jobs operational table location identified. Confirm status column with queued and dispatched values. Confirm engagement_id column.
  5. render_jobs operational table location identified. Confirm status column and engagement_id column.
  6. external_production_records table location identified (Phase 34). Confirm status column.
  7. Shape events view location identified. Confirm state column with pending_confirmation value.
  8. Assertion view location identified. Confirm state column with held value.
  9. Render events view location identified. Confirm state column with produced value.
  10. No existing /me/dashboard/... endpoints.
  11. Phase 33's activity-summary endpoint location identified for reference.

3.3 CR archival

Archive this CR to docs/phase-crs/phase-39-cr-cross-engagement-aggregation-v0_1.md at Step 0 before Step 1 begins.


4. Construction decisions this CR closes

D1 — Forward-compatible item kinds

All item kinds from the strategy doc are included in the Literal types now:

Kinds without backing data (composition, review_report, downstream_specification, transformed_artifact) return no items. The schemas are forward-compatible for Phases 37 and 40.

D2 — Designation-aware filtering

At pre-flight, CC checks whether the designation field on EngagementMembership carries values that distinguish read-only from full membership. If it does, needs_you filters out engagements where the person has read-only access. If all memberships are currently equal, the filtering is noted as a future refinement in implementation notes.

D3 — UNION query pattern preferred

Each endpoint runs a single SQL query with UNIONs joined through the membership table. Each arm selects from one source table filtered by the person's memberships. The UNION result is sorted by recency and paginated.

If the UNION approach proves unwieldy at pre-flight (different column shapes), CC falls back to N queries with in-memory merge-sort and notes the choice in implementation notes.

D4 — 200ms performance budget

Each endpoint completes within 200ms for a person with 10 engagements. CC benchmarks at pre-flight. If the benchmark exceeds budget, CC adds indexes on join columns before proceeding.

D5 — Cursor-based pagination

Each endpoint accepts ?limit (default 50, max 100) and ?cursor (opaque base64-encoded timestamp). The cursor keys on started_at (active), created_at (needs_you), or completed_at (recent).


5. Three endpoints

5.1 GET /me/dashboard/active

Returns active jobs across all the requesting person's engagements.

Source tables: shaping_jobs (status IN queued, dispatched), render_jobs (status IN queued, dispatched), external_production_records (status = awaiting_external).

Each item carries: engagement_id, engagement_name, item_kind, item_id (job ID), item_label (human-readable), started_at, progress_hint (nullable).

Sorted by started_at descending (most recent first).

5.2 GET /me/dashboard/needs_you

Returns items requiring the person's decision.

Source views: shape events (state = pending_confirmation), assertions (state = held), render events (state = produced, where "produced" implies awaiting Operator review before the artifact is considered final).

Each item carries: engagement_id, engagement_name, item_kind, item_id, item_label, detail (brief context), created_at.

Sorted by created_at descending.

Designation-aware filtering: if the person's membership on an engagement is read-only, items from that engagement are excluded from needs_you (the person cannot act on them).

5.3 GET /me/dashboard/recent

Returns recently finished artifacts.

Source views: render events (state = produced, with completed_at or created_at as the recency key).

Each item carries: engagement_id, engagement_name, artifact_kind, artifact_id, artifact_label (render title or display number), completed_at, download_url (nullable — populated for renders with downloadable content per Phase 35 content kinds).

Sorted by completed_at descending.


6. Query patterns

6.1 Membership subquery

All three endpoints share a common membership subquery:


person_engagements = (
    select(EngagementMembership.engagement_id)
    .where(EngagementMembership.person_id == current_person.id)
)

For needs_you, this subquery additionally filters by designation if the designation model supports it.

6.2 UNION pattern

The active endpoint UNIONs across three source tables:


SELECT engagement_id, 'shape_production' as item_kind, id as item_id,
       label, started_at, progress_hint
FROM shaping_jobs
WHERE engagement_id IN (person_engagements)
  AND status IN ('queued', 'dispatched')
UNION ALL
SELECT engagement_id, 'render_production', id, label, started_at, progress_hint
FROM render_jobs
WHERE engagement_id IN (person_engagements)
  AND status IN ('queued', 'dispatched')
UNION ALL
SELECT engagement_id, 'external_polling', id, label, started_at, NULL
FROM external_production_records
WHERE engagement_id IN (person_engagements)
  AND status = 'awaiting_external'
ORDER BY started_at DESC
LIMIT :limit

CC adapts the SQL to the actual column names and table structures found at pre-flight. The label column may need to be composed from available fields (shape type name, render type name, etc.).


7. Pagination

Each endpoint accepts:

Response includes:


class PaginatedResponse(BaseModel):
    items: list[...]
    total_count: int
    next_cursor: str | None = None  # None when no more pages

The cursor is the timestamp of the last item in the current page. The next request filters < cursor_timestamp for descending sort.


8. Response schemas


from datetime import datetime
from uuid import UUID
from typing import Literal
from pydantic import BaseModel


# --- Active ---

class DashboardActiveItem(BaseModel):
    engagement_id: UUID
    engagement_name: str
    item_kind: Literal[
        "shape_production", "render_production",
        "composition", "external_polling"
    ]
    item_id: UUID
    item_label: str
    started_at: datetime
    progress_hint: str | None = None


class DashboardActiveResponse(BaseModel):
    items: list[DashboardActiveItem]
    total_count: int
    next_cursor: str | None = None


# --- Needs You ---

class DashboardNeedsYouItem(BaseModel):
    engagement_id: UUID
    engagement_name: str
    item_kind: Literal[
        "pending_shape", "held_assertion",
        "produced_render_for_review", "review_report",
        "external_failure", "downstream_specification"
    ]
    item_id: UUID
    item_label: str
    detail: str
    created_at: datetime


class DashboardNeedsYouResponse(BaseModel):
    items: list[DashboardNeedsYouItem]
    total_count: int
    next_cursor: str | None = None


# --- Recent ---

class DashboardRecentItem(BaseModel):
    engagement_id: UUID
    engagement_name: str
    artifact_kind: Literal[
        "render", "review_report", "transformed_artifact"
    ]
    artifact_id: UUID
    artifact_label: str
    completed_at: datetime
    download_url: str | None = None


class DashboardRecentResponse(BaseModel):
    items: list[DashboardRecentItem]
    total_count: int
    next_cursor: str | None = None

9. Test suite

9.1 Active endpoint tests

test_active_single_engagement — Person with one engagement, one active shaping job. Verify active returns one item with correct engagement context.

test_active_multiple_engagements — Person with two engagements, active jobs in both. Verify items from both engagements returned, sorted by started_at.

test_active_no_membership — Create an engagement the person is not a member of. Create an active job in it. Verify it does not appear.

test_active_empty — Person with engagements but no active jobs. Verify empty list, not error.

test_active_external_polling — Active ExternalProductionRecord in awaiting_external state. Verify it appears as external_polling kind.

9.2 Needs-you endpoint tests

test_needs_you_pending_shapes — Engagement with pending shapes. Verify they appear as pending_shape items.

test_needs_you_held_assertions — Engagement with held assertions. Verify they appear as held_assertion items.

test_needs_you_cross_engagement — Person with multiple engagements, pending items in several. Verify aggregation.

test_needs_you_empty — No pending items. Verify empty list.

test_needs_you_read_only_filtered — If designation supports it: person with read-only membership on an engagement. Verify that engagement's items are excluded. (If designation does not distinguish, this test is skipped with a note.)

9.3 Recent endpoint tests

test_recent_completed_renders — Engagement with completed renders. Verify they appear as render artifacts.

test_recent_with_download_url — Render with downloadable content (binary_blob or multi_file). Verify download_url populated.

test_recent_without_download_url — Render with inline_dict content. Verify download_url is null.

test_recent_cross_engagement — Multiple engagements, recent artifacts in several. Verify aggregation sorted by completed_at.

9.4 Pagination tests

test_pagination_limit — Create 10 items. Request with limit=3. Verify 3 items returned. Verify next_cursor is not null.

test_pagination_cursor — Use next_cursor from prior request. Verify next page of results.

test_pagination_exhausted — Request last page. Verify next_cursor is null.

9.5 Authentication tests

test_unauthenticated_401 — Request without session. Verify 401.

9.6 Performance test

test_performance_10_engagements — Create 10 engagements with membership. Populate with items. Verify each endpoint completes within 200ms.

9.7 Test count projection


10. Order of operations (steps with checkpoints)

Auto-mode posture: Steps 1–6 auto-mode-proceed. Checkpoint A halts until Operator confirms.

Step 0 — Pre-flight and CR archival

Archive this CR. Run pre-flight checks (Section 3.2). Confirm baseline. Benchmark membership query performance.

Verification: test count matches expectations. Alembic single head. CR archived. All pre-flight items confirmed.

Commit: Phase 39 step 0: CR archival and pre-flight.

Step 1 — Response schemas

Create src/loomworks/api/schemas/dashboard.py with all six response models (three items + three responses). Register in schema exports.

Verification: uv run pytest -v green (no behavioral change — schemas only).

Commit: Phase 39 step 1: dashboard response schemas.

Step 2 — GET /me/dashboard/active

Implement the active endpoint in a new router file src/loomworks/api/routers/dashboard.py. Membership-scoped UNION query. Pagination. Mount on the app.

Write: test_active_single_engagement, test_active_multiple_engagements, test_active_no_membership, test_active_empty, test_active_external_polling.

Verification: uv run pytest -v green.

Commit: Phase 39 step 2: active endpoint.

Step 3 — GET /me/dashboard/needs_you

Implement the needs_you endpoint. Membership-scoped query. Designation-aware filtering if applicable.

Write: test_needs_you_pending_shapes, test_needs_you_held_assertions, test_needs_you_cross_engagement, test_needs_you_empty, test_needs_you_read_only_filtered.

Verification: uv run pytest -v green.

Commit: Phase 39 step 3: needs_you endpoint.

Step 4 — GET /me/dashboard/recent

Implement the recent endpoint. Membership-scoped query. Download URL population for applicable content kinds.

Write: test_recent_completed_renders, test_recent_with_download_url, test_recent_without_download_url, test_recent_cross_engagement.

Verification: uv run pytest -v green.

Commit: Phase 39 step 4: recent endpoint.

Step 5 — Pagination

Add cursor-based pagination to all three endpoints. ?limit and ?cursor query params.

Write: test_pagination_limit, test_pagination_cursor, test_pagination_exhausted.

Verification: uv run pytest -v green.

Commit: Phase 39 step 5: pagination.

Step 6 — Auth and performance

Write: test_unauthenticated_401, test_performance_10_engagements.

Verification: uv run pytest -v green. Performance within 200ms budget.

Commit: Phase 39 step 6: auth and performance tests.

Checkpoint A — Final

Operator confirms. Tag substrate repo.

All Phase 39 work complete. Three dashboard endpoints operational. Pagination working. Performance within budget. All tests green.

Tag: phase-39-cross-engagement-aggregation on DUNIN7/loomworks.

Implementation notes: docs/phase-impl-notes/phase-39-implementation-notes-v0_1.md absorbs execution-time surprises and findings.


11. Acceptance gate

  1. GET /me/dashboard/active returns active jobs across person's engagements.
  2. GET /me/dashboard/needs_you returns items requiring decision across engagements.
  3. GET /me/dashboard/recent returns recently finished artifacts across engagements.
  4. Items from engagements the person has no membership on do not appear.
  5. Read-only membership filtering applied if designation supports it.
  6. All endpoints paginate with ?limit and ?cursor.
  7. Empty state returns empty list, not error.
  8. Unauthenticated requests return 401.
  9. Each endpoint completes within 200ms for 10 engagements.
  10. Forward-compatible item kinds in Literals (composition, review_report, etc.) accepted by schema.
  11. Download URLs populated for renders with downloadable content kinds.

12. Post-CR state


13. What this CR does not specify


14. Kickoff prompt for the Claude Code session


Read the Change Request document at the path I supply below. This is
CR-2026-051 v0.1, the Phase 39 Change Request. You are the executing
agent named in the CR.

CR path: ~/Downloads/phase-39-cr-cross-engagement-aggregation-v0_1.md

Phase 39 adds three cross-engagement aggregation endpoints under
/me/dashboard/: active (jobs), needs_you (items requiring decision),
and recent (finished artifacts). Membership-scoped queries. No
migrations — pure endpoint addition querying existing tables.

Substrate-only phase. Six steps, one checkpoint.

Code baseline: at HEAD after prior phases. Post-prior-phase test count.

Run pre-flight (Step 0) per Section 3.2. The Step 0 checklist
includes: membership model and designation field; operational tables
(shaping_jobs, render_jobs, external_production_records); shape/
assertion/render views with state columns; no existing
/me/dashboard/ endpoints.

Per Section 3.3: archive this CR to
docs/phase-crs/phase-39-cr-cross-engagement-aggregation-v0_1.md
at Step 0 before Step 1 begins.

Per Section 10, six steps with one checkpoint. Auto-mode posture:
Steps 1–6 auto-mode-proceed; Checkpoint A halts for Operator
to confirm and tag.

Performance budget: 200ms per endpoint for 10 engagements.
Benchmark at pre-flight.

Cursor-based pagination with ?limit and ?cursor on all three
endpoints.

Pre-flight surprises (Section 3.2 ground-truth divergence) stop
execution at Step 0 and drive a CR v0.2 revision; do not proceed
through divergence.

Implementation notes at Checkpoint A:
docs/phase-impl-notes/phase-39-implementation-notes-v0_1.md
absorbs execution-time surprises and findings.

DUNIN7 — Done In Seven LLC — Miami, Florida Phase 39: Cross-engagement Aggregation — CR v0.1 — 2026-05-04