Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ AIFA relies on executable guardrails, not documentation alone:
- transactional idempotency, audit records, and an event outbox;
- architecture, behavior, integration, and bundle checks.

The current feature set covers task creation, listing, status changes, deletion, AI-generated task plans, and AI workspace settings.
The current feature set covers task creation, listing, status changes, deletion, AI-generated task
plans, explicit suggestion acceptance, and AI workspace settings.

## Repository map

Expand Down
8 changes: 7 additions & 1 deletion demo/ai-todo-assistant/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ OLLAMA_MODEL=llama3.2
OIDC_ISSUER_URL=
OIDC_AUDIENCE=
OIDC_JWKS_URL=
# Browser OIDC settings are embedded by Vite at build time.
VITE_OIDC_ISSUER_URL=
VITE_OIDC_CLIENT_ID=
VITE_OIDC_AUDIENCE=
VITE_OIDC_REDIRECT_URI=
VITE_OIDC_SCOPES=TaskRead TaskWrite TaskDelete AssistantPlanGenerate SettingsRead EventRead
# Production MCP calls use this OIDC access token. Never commit a real token.
MCP_ACCESS_TOKEN=
IDEMPOTENCY_LEASE_MS=300000
DEMO_TENANT_ID=local-tenant
DEMO_USER_ID=local-user
# Optional authenticated SSE endpoint for domain events created outside this browser.
# Optional override; defaults to VITE_API_BASE_URL + /api/events.
VITE_EVENT_STREAM_URL=
19 changes: 17 additions & 2 deletions demo/ai-todo-assistant/ARCHITECTURE-REMEDIATION-TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ Status legend:
- [x] Configuration validates ports and URLs; liveness and MongoDB readiness are separate endpoints.
- [x] Tests now cover contract boundaries, capability failures, tenant overrides, event schemas, fencing, and concurrent outbox claims.
- [x] Production contributions are code-split and MUI icons use direct imports.
- The build now transforms about 957 modules; the largest chunk is about 358 kB.
- The build now transforms about 972 modules; the largest chunk is about 368 kB.
- CI enforces a 400 kB largest-chunk and 600 kB total-JavaScript budget.
- [x] Generated contracts are used by frontend DTOs and selected feature inputs/outputs; runtime schemas remain authoritative.
- Schema titles produce stable names, and generation deduplicates identical declarations while rejecting conflicts.
Expand All @@ -152,7 +152,22 @@ Status legend:
## Verification status

- [x] `npm run check`
- [x] `npm test` — 18 tests passing
- [x] `npm test` — 23 tests passing
- [x] `npm run build` — server and web artifacts produced
- [x] Built-server smoke test
- [x] Browser end-to-end verification

## Follow-up review remediation — 2026-08-15

- [x] Browser command hooks retain a stable command ID after unknown transport outcomes and use a
new ID only after a success or definitive HTTP failure.
- [x] Browser OIDC requests explicit configurable application scopes, including `EventRead` for
the live event boundary.
- [x] Native `EventSource` was replaced with bearer-authenticated fetch streaming; `/api/events`
reads tenant-scoped committed outbox events and supports opaque reconnect cursors.
- [x] Dependency rules now reject feature/domain imports from `core/backend`, Node runtime modules,
MongoDB, MCP, identity, environment, and provider packages; executable negative fixtures guard
the rules themselves.
- [x] Suggestion acceptance is a separate discovered feature composed through the typed
`TaskSuggestionActions` slot. AI Planning no longer knows the Task Management route, hook, or
cache tag.
27 changes: 21 additions & 6 deletions demo/ai-todo-assistant/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ demo/ai-todo-assistant/
domain/ # Task invariants and value objects
features/
create-task/
accept-task-suggestion/
list-tasks/
change-task-status/
delete-task/
Expand Down Expand Up @@ -81,7 +82,7 @@ The task-status transition invariant lives here. Features invoke it; they do not

### AI Planning

Owns the persisted `TaskPlan` aggregate, validated task-plan suggestions, and the rules for safely turning provider output into task categories and priorities. Generating a plan creates a tenant-owned review record and emits `TaskPlanGeneratedV1`; it does not create tasks. Accepted suggestions use the Create Task feature boundary.
Owns the persisted `TaskPlan` aggregate, validated task-plan suggestions, and the rules for safely turning provider output into task categories and priorities. Generating a plan creates a tenant-owned review record and emits `TaskPlanGeneratedV1`; it does not create tasks. The independent Task Management `accept-task-suggestion` feature consumes the published suggestion contract and contributes its action through the typed `TaskSuggestionActions` slot. AI Planning therefore does not know an acceptance route or import another feature's hook.

Contexts do not import each other’s domain or feature modules. They communicate using versioned contracts or domain events.

Expand All @@ -91,7 +92,7 @@ Contexts do not import each other’s domain or feature modules. They communicat

Every request—HTTP, MCP, browser, or provider callback—must resolve to an `Actor` containing a `TenantId`, `UserId`, and enum-backed permission scopes. Anonymous mutation is prohibited.

Browser authentication uses an OpenID Connect authorization-code flow with PKCE. The remote MCP endpoint uses Streamable HTTP and acts as an OAuth 2.1 protected resource: it validates bearer-token audience, expiry, tenant, user, and scopes on every request. A local stdio MCP connection obtains its credentials from the local environment and is restricted to the configured tenant. Token revocation, client registration, and scope assignment are owned by the identity adapter in core.
Browser authentication uses an OpenID Connect authorization-code flow with PKCE. The browser requests the application scopes configured by `VITE_OIDC_SCOPES`; live event delivery additionally requires `EventRead`. The remote MCP endpoint uses Streamable HTTP and acts as an OAuth 2.1 protected resource: it validates bearer-token audience, expiry, tenant, user, and scopes on every request. A local stdio MCP connection obtains its credentials from the local environment and is restricted to the configured tenant. Token revocation, client registration, and scope assignment are owned by the identity adapter in core.

### Authorization Middleware

Expand All @@ -109,6 +110,10 @@ MCP authentication maps each client connection to an actor and only exposes tool

Every mutation has a caller-supplied idempotency key and expected task version where relevant. The idempotency store atomically claims a key before feature execution, records a fenced execution identifier, and persists the command result on completion. A bounded lease lets a retry recover from a crashed worker without allowing an earlier execution to overwrite the recovered result. HTTP or MCP retries return the original completed result without repeating persistence or audit work.

Browser command hooks allocate one command identifier for each user intent. A successful response or
a definitive HTTP failure settles that identifier; an unknown transport outcome retains it so a
retry replays the same durable command instead of creating a duplicate task or plan.

Features use typed failures for `VersionConflict`, `IdempotencyKeyReuse`, `NotAuthorized`, and `ConfirmationRequired`. These failures are part of each adapter contract, not raw infrastructure errors.

## Events, Audit, and Outbox
Expand All @@ -120,7 +125,12 @@ Within the MongoDB transaction for a successful mutation, the runtime writes the
- `TaskDeletedV1`
- `TaskPlanGeneratedV1`

An outbox publisher delivers events asynchronously and records delivery state. Consumers receive versioned event contracts, never direct imports from another context. This preserves reliability without coupling feature implementations.
An outbox publisher delivers events asynchronously and records delivery state. The authenticated
`/api/events` SSE boundary also reads the tenant's committed outbox records in event order for live
browser synchronization. Opaque cursors allow reconnects to resume without relying on browser
cookies or exposing tenant identity in the request. Consumers receive versioned event contracts,
never direct imports from another context. This preserves reliability without coupling feature
implementations.

## Feature Slice Contract

Expand Down Expand Up @@ -224,7 +234,7 @@ browser. The successful command hook invalidates affected tags without waiting f
outbox event to return to that browser.

Tasks may also be created by MCP clients, another browser, another actor, or background processing.
For those cases, a generic core SSE or WebSocket adapter delivers validated versioned domain events
For those cases, the generic core authenticated-fetch SSE adapter delivers validated versioned domain events
to dynamically registered frontend event consumers. A Task Management consumer maps
`TaskCreatedV1`, `TaskStatusChangedV1`, and `TaskDeletedV1` to the same `TaskCollection`
invalidation. Receiving both the local-success invalidation and its later domain event is expected;
Expand All @@ -245,14 +255,15 @@ Focused frontend tests must verify that:
3. each domain-event consumer accepts its declared version and maps it to the correct tags;
4. duplicate local and remote invalidations are harmless and request work is deduplicated;
5. independently mounted slot contributions observe the same cache without importing each other;
6. actor or tenant changes clear cached data and terminate the previous event subscription.
6. actor or tenant changes clear cached data and terminate the previous event subscription;
7. reconnects reuse the last opaque event cursor and event reads remain tenant scoped.

## Dependency Boundaries

```txt
core ────────────────────────────► core only
context domain ──────────────────► core + its own domain
feature ─────────────────────────► core + itself + own domain + versioned context contracts
feature ─────────────────────────► core shared/frontend APIs + itself + own domain + versioned context contracts
adapter (HTTP/MCP/UI) ───────────► core + its registered feature contract
cross-context work ──────────────► versioned contracts or domain events only
```
Expand All @@ -268,6 +279,10 @@ Forbidden dependencies:

`npm run check:imports` runs dependency-cruiser in CI to enforce import boundaries and circular-import rules. `validate-feature-definitions.ts` validates feature JSON, enum references, contract files, routes, MCP tools, security invariants, and the declared dependency graph.

Feature and domain code are explicitly denied imports from `core/backend`, Node runtime modules,
MongoDB, MCP, identity, environment, and provider packages. `npm run check:import-fixtures` executes
deliberately invalid source fixtures so a weakened dependency rule makes the architecture gate fail.

## AIFA Runtime Rules

Every feature receives only:
Expand Down
14 changes: 13 additions & 1 deletion demo/ai-todo-assistant/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@ Probe `/health` for API liveness. Alert on Mongo connection failures, outbox doc

## Security and privacy

Production callers must provide a signed OIDC bearer token. The API verifies its issuer, audience, and signature using `OIDC_ISSUER_URL`, `OIDC_AUDIENCE`, and `OIDC_JWKS_URL`; it reads `sub`, `tenant_id`, and space-separated `scope` claims. The `x-aifa-*` headers are accepted only outside production. Set the short-lived `MCP_ACCESS_TOKEN` secret for the local stdio MCP bridge. Treat Ollama prompts and responses as tenant data; run local Ollama on trusted infrastructure and do not enable a remote model provider without a data-processing review.
Production callers must provide a signed OIDC bearer token. The API verifies its issuer, audience,
and signature using `OIDC_ISSUER_URL`, `OIDC_AUDIENCE`, and `OIDC_JWKS_URL`; it reads `sub`,
`tenant_id`, and space-separated `scope` claims. Configure the browser authorization request with
`VITE_OIDC_SCOPES`; it must include the application scopes required by the enabled features and
`EventRead` for live updates. The `x-aifa-*` headers are accepted only outside production. Set the
short-lived `MCP_ACCESS_TOKEN` secret for the local stdio MCP bridge. Treat Ollama prompts and
responses as tenant data; run local Ollama on trusted infrastructure and do not enable a remote
model provider without a data-processing review.

The browser consumes `/api/events` using authenticated fetch streaming. The endpoint requires
`EventRead`, derives the tenant from the verified actor, and resumes from an opaque cursor carried
in `Last-Event-ID`. Preserve the tenant event-stream index and outbox retention window long enough
for the expected reconnect interval.

## Incident response

Expand Down
5 changes: 3 additions & 2 deletions demo/ai-todo-assistant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ Each feature lives under `contexts/<context>/features/<feature-name>/` and owns
- `contracts/` — JSON Schema input and output contracts.
- `IMPLEMENTATION_PLAN.md` — the human-readable implementation guide.

The current feature set includes Create Task, List Tasks, Change Task Status, Delete Task, Generate Task Plan, and Manage AI Settings.
The current feature set includes Create Task, List Tasks, Change Task Status, Delete Task,
Generate Task Plan, Accept Task Suggestion, and Manage AI Settings.

### Typed UI slots

The React shell owns layout and composition, while feature slices contribute their own UI through typed slots such as:

- `AppHeader`, `AppNavigation`, `AppContent`, and `AppFooter`
- `TaskComposer`, `TaskList`, `TaskRowActions`, and `AssistantPanel`
- `TaskComposer`, `TaskList`, `TaskRowActions`, `AssistantPanel`, and `TaskSuggestionActions`
- `SettingsPanel`

Frontend contributions are discovered automatically. Adding a feature does not require maintaining a central list of product components.
Expand Down
5 changes: 4 additions & 1 deletion demo/ai-todo-assistant/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ Update this checklist as work is completed. Read [ARCHITECTURE.md](ARCHITECTURE.
- [x] Implement persisted generated TaskPlan records and initial review state.
- [x] Implement the Ollama provider adapter with structured-output validation, timeouts, and safe local fallback.
- [x] Implement the Generate Task Plan feature: backend, MUI assistant panel, MCP tool, manifest, and tests.
- [x] Implement suggestion acceptance through the Create Task API boundary.
- [x] Implement suggestion acceptance as an independent Task Management feature composed through the typed `TaskSuggestionActions` slot.

## React Application

- [x] Introduce the generic frontend query/event runtime, versioned context cache tags, and feature-local query/command hooks; replace workspace-wide command orchestration with semantic cache invalidation.
- [x] Preserve browser command IDs across unknown transport outcomes and settle them only after a definitive result.
- [x] Deliver tenant-scoped outbox events through an authenticated, resumable SSE boundary.
- [x] Create the MUI theme, React shell, API client, typed slot registry, and dynamic frontend discovery.
- [x] Build the category-based task board.
- [x] Build task-category and task-status filtering.
Expand All @@ -64,6 +66,7 @@ Update this checklist as work is completed. Read [ARCHITECTURE.md](ARCHITECTURE.

## Quality and Release Readiness

- [x] Centralize command transport and task construction, remove taxonomy casts, and clean up event-stream lifecycle helpers.
- [x] Add feature-level runtime behavioral tests.
- [x] Add disposable-database MongoDB integration coverage (CI service and local opt-in script).
- [x] Add HTTP authentication/idempotency boundary and task tenant/concurrency integration coverage; MCP parity remains covered through manifest-driven adapter composition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export const frontendModules = [
eventConsumers: [] as const,
load: () => import("../../contexts/ai-planning/features/generate-task-plan/frontend/contribution.js"),
},
{
id: "accept-task-suggestion",
slots: ["TaskSuggestionActions"] as const,
eventConsumers: [{"name":"invalidate-task-collection-after-suggestion-acceptance","eventType":"TaskCreatedV1","contract":"task-management/contracts/events/v1/task-created.schema.json"}] as const,
load: () => import("../../contexts/task-management/features/accept-task-suggestion/frontend/contribution.js"),
},
{
id: "change-task-status",
slots: ["TaskRowActions"] as const,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"businessNeed": "Save users from manually decomposing goals by generating reviewable, categorized task suggestions from natural language.",
"dependsOn": [],
"consumesContracts": [
"task-management/contracts/v1/task-taxonomy.schema.json",
"task-management/contracts/v1/task-cache.schema.json"
"task-management/contracts/v1/task-taxonomy.schema.json"
],
"backend": {
"route": "/api/assistant/task-plan",
Expand Down Expand Up @@ -49,7 +48,7 @@
{
"id": "AIP-3",
"description": "Register the MUI suggestion-review panel.",
"acceptanceCriterion": "Accepted suggestions use the create-task API boundary."
"acceptanceCriterion": "Acceptance actions compose through the typed TaskSuggestionActions slot without a direct feature call."
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ import {
} from "../../../../task-management/contracts/v1/task-taxonomy.js";
import type { TaskPlanSuggestionV1 } from "../../../../../core/shared/generated/contracts.js";
import type { SlotContribution } from "../../../../../core/frontend/slots/slot-registry.js";
import { Slot } from "../../../../../core/frontend/slots/Slot.js";
import { SlotName } from "../../../../../core/shared/architecture-enums.js";
import { useAcceptTaskSuggestion, useGenerateTaskPlan } from "./use-generate-task-plan.js";
import { useGenerateTaskPlan } from "./use-generate-task-plan.js";

function Planner() {
const [goal, setGoal] = useState("");
const [category, setCategory] = useState(TaskCategory.Work);
const [priority, setPriority] = useState(TaskPriority.Medium);
const [suggestions, setSuggestions] = useState<TaskPlanSuggestionV1[]>([]);
const generatePlan = useGenerateTaskPlan();
const acceptSuggestion = useAcceptTaskSuggestion();
return (
<Paper
elevation={0}
Expand All @@ -42,9 +42,9 @@ function Planner() {
p: { xs: 2.5, md: 3.5 },
}}
>
{(generatePlan.error || acceptSuggestion.error) && (
{generatePlan.error && (
<Alert severity="error" sx={{ mb: 2, position: "relative" }}>
{(generatePlan.error ?? acceptSuggestion.error)?.message}
{generatePlan.error.message}
</Alert>
)}
<Box
Expand Down Expand Up @@ -191,15 +191,7 @@ function Planner() {
label={suggestion.category}
sx={{ bgcolor: "rgba(255,255,255,.12)", color: "white" }}
/>
<Button
size="small"
variant="contained"
disabled={acceptSuggestion.isPending}
onClick={() => void acceptSuggestion.mutate(suggestion)}
sx={{ bgcolor: "white", color: "#171923" }}
>
Add task
</Button>
<Slot name={SlotName.TaskSuggestionActions} model={{ suggestion }} />
</Stack>
</Paper>
))}
Expand Down
Loading
Loading