Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
22b947d
feat(ai): expose bounded project public materials without runtime pay…
a3384379 Oct 21, 2026
e296a02
feat(ai): guide explicit task materials and recover human review outc…
a3384379 Oct 21, 2026
1fb4b5e
fix(ai): keep change-set jobs read-only in the assistant
a3384379 Oct 21, 2026
c416ebd
Merge reviewed R3 evidence fixes before AI full validation
a3384379 Oct 21, 2026
b014920
docs: record AI task and human review remediation acceptance
a3384379 Oct 21, 2026
986ced4
feat(context): link fixed evidence conflicts and knowledge views
a3384379 Oct 21, 2026
f50eda9
feat(ai): freeze review baselines and allow authorized history reads
a3384379 Oct 21, 2026
8e8dfa6
docs(audit): record context evidence acceptance and delivery state
a3384379 Oct 21, 2026
25ee683
Merge commit '8e8dfa6faaa69f2bf63b34242f2b097d7fea9b96' into codex/ui…
a3384379 Oct 21, 2026
edb06bb
feat(ai): present frozen change evidence and protect manual reviews
a3384379 Oct 21, 2026
b1440de
fix: restore organization governance states and audit navigation
a3384379 Oct 21, 2026
291ea98
fix(execution-fabric): expose scoped queue and task evidence
a3384379 Oct 21, 2026
1d499ef
fix(ai): retain navigation protection through pending submission
a3384379 Oct 21, 2026
3734479
Merge remote-tracking branch 'origin/main' into codex/ai-submit-revie…
a3384379 Oct 21, 2026
d448ebf
fix(ai): retain navigation protection through pending submission
a3384379 Oct 21, 2026
0682804
docs: record M22 remediation and pending integration checks
a3384379 Oct 21, 2026
c34de93
fix(platform): bind inspectors to versioned inventory identity
a3384379 Oct 21, 2026
3752a7b
fix(execution-fabric): scope lease-linked event history
a3384379 Oct 21, 2026
654b13e
fix(mcp): read exact materialized design behind proposal authorization
a3384379 Oct 21, 2026
57bdf80
fix(ai): respect OFF policy when accepting literal draft values
a3384379 Oct 21, 2026
6f95c3d
fix(ai): align review previews and bind forms to change-set scope
a3384379 Oct 21, 2026
78c0cc3
fix(ui): improve governance and capability inspection readability
a3384379 Oct 21, 2026
51ab5a8
test(ai): bind policy review fixtures to generated suggestions
a3384379 Oct 21, 2026
a6f6b5d
fix(mcp-ui): expose governance previews and verified materialized res…
a3384379 Oct 21, 2026
0ec0252
fix(organization): lock member picker during pending updates
a3384379 Oct 21, 2026
821c345
docs: record parallel remediation acceptance and remaining gates
a3384379 Oct 21, 2026
c89c047
test(ai): scope review interactions to active dialogs
a3384379 Oct 21, 2026
6628ad3
Merge commit 'c89c0474776a432e3f05636489d118ba7a193b6f' into codex/ui…
a3384379 Oct 21, 2026
7c8610c
docs: correct audit wave mapping and record review follow-ups
a3384379 Oct 21, 2026
9fe6a18
fix(organization): refresh current role after membership updates
a3384379 Oct 21, 2026
e8bfd53
fix(mcp): refresh exact workflow drafts after materialization
a3384379 Oct 21, 2026
5a83ade
fix(standalone): preserve AI review baselines on upgrade
a3384379 Oct 21, 2026
b5d5992
fix(cases): align run confirmation and preserve explicit retries
a3384379 Oct 21, 2026
85da058
docs: record final cross-module fixes and frozen validation trees
a3384379 Oct 21, 2026
9e7162d
test(e2e): follow explicit platform capability selection
a3384379 Oct 21, 2026
0f85e1f
test(ai): scope viewer and review control queries
a3384379 Oct 21, 2026
c5c3d6a
test(context): narrow inspector interaction queries
a3384379 Oct 21, 2026
60f931d
test(organization): narrow audit interaction queries
a3384379 Oct 21, 2026
3fa68ce
test(ai): preserve ancestor accessibility exclusion
a3384379 Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions backend/app/api/v1/endpoints/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,49 @@
AISuggestionResponse,
AISuggestionReviewRequest,
)
from app.schemas.ai_sources import AIPublicSource, AISourceKind, AISourceSummary
from app.schemas.common import Page
from app.services.ai import AIJobService
from app.services.ai_sources import AISourceService

router = APIRouter(prefix="/ai")


@router.get("/projects/{project_id}/sources", response_model=Page[AISourceSummary])
async def list_public_ai_sources(
project_id: UUID,
kind: AISourceKind,
session: SessionDependency,
current_user: CurrentUser,
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
search: str = Query(default="", max_length=200),
) -> Page[AISourceSummary]:
items, total = await AISourceService(session).list_sources(
actor=current_user,
project_id=project_id,
kind=kind,
page=page,
page_size=page_size,
search=search,
)
return Page(items=items, total=total, page=page, page_size=page_size)


@router.get("/projects/{project_id}/sources/{kind}/{source_id}", response_model=AIPublicSource)
async def read_public_ai_source(
project_id: UUID,
kind: AISourceKind,
source_id: UUID,
session: SessionDependency,
current_user: CurrentUser,
version: int | None = Query(default=None, ge=1),
) -> AIPublicSource:
return await AISourceService(session).read_source(
actor=current_user, project_id=project_id, kind=kind, source_id=source_id, version=version
)


@router.get("/status", response_model=AIStatusResponse)
async def get_ai_status(
project_id: UUID,
Expand Down
1 change: 1 addition & 0 deletions backend/app/api/v1/endpoints/ai_change_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,6 @@ def _detail(change_set: AIChangeSet, items: list[AIChangeItem]) -> AIChangeSetDe
return AIChangeSetDetailResponse(
**summary.model_dump(),
source_snapshot=change_set.source_snapshot,
review_baselines=change_set.review_baselines,
items=[AIChangeItemResponse.model_validate(item) for item in items],
)
59 changes: 42 additions & 17 deletions backend/app/api/v1/endpoints/capabilities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from uuid import UUID

from fastapi import APIRouter, Query

from app.api.dependencies import CurrentUser, SessionDependency
Expand All @@ -12,7 +14,7 @@
V3FeatureFlagsResponse,
)
from app.schemas.common import Page
from app.services.capabilities import CapabilityService, CapabilityView
from app.services.capabilities import CapabilityService, CapabilityView, RunnerPoolView

router = APIRouter()

Expand Down Expand Up @@ -42,8 +44,11 @@ async def list_capabilities(
current_user: CurrentUser,
page: int = Query(default=1, ge=1),
page_size: int = Query(default=50, ge=1, le=100),
search: str = Query(default="", max_length=200),
) -> Page[CapabilityResponse]:
capabilities = await CapabilityService(session).list_capabilities(actor=current_user)
capabilities = await CapabilityService(session).list_capabilities(
actor=current_user, search=search
)
start = (page - 1) * page_size
selected = capabilities[start : start + page_size]
return Page(
Expand Down Expand Up @@ -78,8 +83,9 @@ async def list_plugins(
current_user: CurrentUser,
page: int = Query(default=1, ge=1),
page_size: int = Query(default=50, ge=1, le=100),
search: str = Query(default="", max_length=200),
) -> Page[PluginResponse]:
plugins = await CapabilityService(session).list_plugins(actor=current_user)
plugins = await CapabilityService(session).list_plugins(actor=current_user, search=search)
start = (page - 1) * page_size
selected = plugins[start : start + page_size]
return Page(
Expand Down Expand Up @@ -109,30 +115,49 @@ async def list_runner_pools(
current_user: CurrentUser,
page: int = Query(default=1, ge=1),
page_size: int = Query(default=50, ge=1, le=100),
search: str = Query(default="", max_length=200),
) -> Page[RunnerPoolResponse]:
pools = await CapabilityService(session).list_runner_pools(actor=current_user)
pools = await CapabilityService(session).list_runner_pools(actor=current_user, search=search)
start = (page - 1) * page_size
selected = pools[start : start + page_size]
return Page(
items=[
RunnerPoolResponse(
id=view.pool.id,
name=view.pool.name,
runner_type=view.pool.runner_type,
network_zone=view.pool.network_zone,
labels=view.pool.labels,
max_concurrency=view.pool.max_concurrency,
enabled=view.pool.enabled,
runners=[RunnerResponse.model_validate(item) for item in view.runners],
)
for view in selected
],
items=[_runner_pool_response(view) for view in selected],
total=len(pools),
page=page,
page_size=page_size,
)


@router.get("/plugins/{plugin_id}", response_model=PluginResponse)
async def get_plugin(
plugin_id: UUID, session: SessionDependency, current_user: CurrentUser
) -> PluginResponse:
return PluginResponse.model_validate(
await CapabilityService(session).get_plugin(actor=current_user, plugin_id=plugin_id)
)


@router.get("/runner-pools/{pool_id}", response_model=RunnerPoolResponse)
async def get_runner_pool(
pool_id: UUID, session: SessionDependency, current_user: CurrentUser
) -> RunnerPoolResponse:
view = await CapabilityService(session).get_runner_pool(actor=current_user, pool_id=pool_id)
return _runner_pool_response(view)


def _runner_pool_response(view: RunnerPoolView) -> RunnerPoolResponse:
return RunnerPoolResponse(
id=view.pool.id,
name=view.pool.name,
runner_type=view.pool.runner_type,
network_zone=view.pool.network_zone,
labels=view.pool.labels,
max_concurrency=view.pool.max_concurrency,
enabled=view.pool.enabled,
runners=[RunnerResponse.model_validate(item) for item in view.runners],
)


def _capability_response(view: CapabilityView) -> CapabilityResponse:
manifest = view.manifest
return CapabilityResponse(
Expand Down
16 changes: 16 additions & 0 deletions backend/app/api/v1/endpoints/mcp_controlled_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
MCPControlledWriteEnvelope,
MCPControlledWriteReview,
MCPManualApprovalCreate,
MCPMaterializedDesignResponse,
)
from app.services.mcp_controlled_write import MCPControlledWriteService

Expand Down Expand Up @@ -54,6 +55,21 @@ async def get_change_set(
)


@router.get(
"/change-sets/{change_set_id}/items/{item_id}/materialized-design",
response_model=MCPMaterializedDesignResponse,
)
async def get_materialized_design(
change_set_id: UUID,
item_id: UUID,
session: SessionDependency,
current_user: CurrentUser,
) -> MCPMaterializedDesignResponse:
return await MCPControlledWriteService(session).materialized_design(
actor=current_user, change_set_id=change_set_id, item_id=item_id
)


@router.get(
"/change-sets/{change_set_id}/control-block-preview",
response_model=MCPControlBlockPreviewResponse,
Expand Down
11 changes: 10 additions & 1 deletion backend/app/api/v1/endpoints/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ async def list_organization_members(
actor=current_user,
organization_id=organization_id,
)
return [OrganizationMemberResponse.model_validate(item) for item in members]
return [
OrganizationMemberResponse.model_validate(item.member).model_copy(
update={"display_name": item.display_name, "email": item.email}
)
for item in members
]


@router.put(
Expand Down Expand Up @@ -255,7 +260,9 @@ async def list_organization_audit_logs(
session: SessionDependency,
current_user: CurrentUser,
action: str | None = Query(default=None, max_length=100),
actor_user_id: UUID | None = None,
resource_type: str | None = Query(default=None, max_length=100),
resource_id: UUID | None = None,
created_from: datetime | None = None,
created_to: datetime | None = None,
page: int = Query(default=1, ge=1),
Expand All @@ -265,7 +272,9 @@ async def list_organization_audit_logs(
actor=current_user,
organization_id=organization_id,
action=action,
actor_user_id=actor_user_id,
resource_type=resource_type,
resource_id=resource_id,
created_from=created_from,
created_to=created_to,
offset=(page - 1) * page_size,
Expand Down
68 changes: 55 additions & 13 deletions backend/app/api/v1/endpoints/runner_fabric.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
RunnerPoolResponse,
RunnerPoolUpdate,
RunnerProgressRequest,
RunnerQueueSnapshotResponse,
RunnerRegisterRequest,
RunnerRegisterResponse,
RunnerRegistrationTokenCreate,
Expand Down Expand Up @@ -57,16 +58,48 @@ async def list_runner_fabric_pools(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=50, ge=1, le=100),
) -> Page[RunnerPoolResponse]:
views = await _service(session).list_pools(actor=current_user)
start = (page - 1) * page_size
views, total = await _service(session).pool_page(actor=current_user, page=page, limit=page_size)
return Page(
items=[_pool_response(pool, runners) for pool, runners in views[start : start + page_size]],
total=len(views),
items=[_pool_response(pool, runners) for pool, runners in views],
total=total,
page=page,
page_size=page_size,
)


@admin_router.get("/queue", response_model=RunnerQueueSnapshotResponse)
async def get_runner_queue(
session: SessionDependency, current_user: CurrentUser
) -> RunnerQueueSnapshotResponse:
return await _service(session).queue_snapshot(actor=current_user)


@admin_router.get("/pools/{pool_id}", response_model=RunnerPoolResponse)
async def get_runner_pool(
pool_id: UUID, session: SessionDependency, current_user: CurrentUser
) -> RunnerPoolResponse:
pool, runners = await _service(session).inspect_pool(actor=current_user, pool_id=pool_id)
return _pool_response(pool, runners)


@admin_router.get("/runners/{runner_id}", response_model=RunnerResponse)
async def get_runner(
runner_id: UUID, session: SessionDependency, current_user: CurrentUser
) -> RunnerResponse:
return RunnerResponse.model_validate(
await _service(session).inspect_runner(actor=current_user, runner_id=runner_id)
)


@admin_router.get("/tasks/{task_id}", response_model=RunnerTaskResponse)
async def get_runner_task(
task_id: UUID, session: SessionDependency, current_user: CurrentUser
) -> RunnerTaskResponse:
return RunnerTaskResponse.model_validate(
await _service(session).inspect_task(actor=current_user, task_id=task_id)
)


@admin_router.post("/pools", response_model=RunnerPoolResponse, status_code=status.HTTP_201_CREATED)
async def create_runner_fabric_pool(
payload: RunnerPoolCreate,
Expand Down Expand Up @@ -133,12 +166,13 @@ async def list_runner_tasks(
session: SessionDependency,
current_user: CurrentUser,
limit: int = Query(default=100, ge=1, le=100),
page: int = Query(default=1, ge=1),
) -> Page[RunnerTaskResponse]:
items = await _service(session).list_tasks(actor=current_user, limit=limit)
items, total = await _service(session).task_page(actor=current_user, page=page, limit=limit)
return Page(
items=[RunnerTaskResponse.model_validate(item) for item in items],
total=len(items),
page=1,
total=total,
page=page,
page_size=limit,
)

Expand All @@ -148,12 +182,16 @@ async def list_runner_leases(
session: SessionDependency,
current_user: CurrentUser,
limit: int = Query(default=100, ge=1, le=100),
page: int = Query(default=1, ge=1),
task_id: UUID | None = None,
) -> Page[RunnerLeaseAdminResponse]:
items = await _service(session).list_leases(actor=current_user, limit=limit)
items, total = await _service(session).lease_page(
actor=current_user, page=page, limit=limit, task_id=task_id
)
return Page(
items=[RunnerLeaseAdminResponse.model_validate(item) for item in items],
total=len(items),
page=1,
total=total,
page=page,
page_size=limit,
)

Expand All @@ -163,12 +201,16 @@ async def list_runner_events(
session: SessionDependency,
current_user: CurrentUser,
limit: int = Query(default=100, ge=1, le=100),
page: int = Query(default=1, ge=1),
task_id: UUID | None = None,
) -> Page[RunnerEventResponse]:
items = await _service(session).list_events(actor=current_user, limit=limit)
items, total = await _service(session).event_page(
actor=current_user, page=page, limit=limit, task_id=task_id
)
return Page(
items=[RunnerEventResponse.model_validate(item) for item in items],
total=len(items),
page=1,
total=total,
page=page,
page_size=limit,
)

Expand Down
6 changes: 6 additions & 0 deletions backend/app/core/standalone_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ async def _ensure_incremental_columns(connection: AsyncConnection) -> None:
await _ensure_governance_tables(connection)
await _ensure_test_context_tables(connection)
await _ensure_flow_spec_change_set_columns(connection)
await _add_column_if_missing(
connection,
table="ai_change_sets",
column="review_baselines",
definition="JSON NOT NULL DEFAULT '[]'",
)
await _ensure_s42_controlled_write_tables(connection)
await _ensure_s62_test_plan_update_constraint(connection)
await _ensure_s47_test_design_columns(connection)
Expand Down
3 changes: 3 additions & 0 deletions backend/app/models/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ class AIChangeSet(UuidPrimaryKeyMixin, TimestampMixin, Base):
String(24), default="generating", server_default="generating", index=True
)
source_snapshot: Mapped[dict[str, Any]] = mapped_column(JSON)
review_baselines: Mapped[list[dict[str, Any]]] = mapped_column(
JSON, default=list, server_default="[]", nullable=False
)
source_fingerprint: Mapped[str] = mapped_column(String(64), index=True)
source_type: Mapped[str] = mapped_column(
String(24), default="ai", server_default="ai", index=True
Expand Down
Loading
Loading