integration: landing + ai-structured-outputs + l1-workspace (Phase 1 internal-only) #190

Closed
chihlasm wants to merge 13 commits from feat/three-feature-integration into main
Owner

Closing in favor of the three individual PRs (#187 landing, #188 AI, #189 L1), all now merged to main per the agreed land-individually plan. This integration branch served its purpose as the combined PR-env smoke test (which surfaced the /home routing regression now fixed in #189). No code from this branch is lost — every commit landed via the three feature PRs.

Closing in favor of the three individual PRs (#187 landing, #188 AI, #189 L1), all now merged to main per the agreed land-individually plan. This integration branch served its purpose as the combined PR-env smoke test (which surfaced the /home routing regression now fixed in #189). No code from this branch is lost — every commit landed via the three feature PRs.
chihlasm added 46 commits 2026-05-29 03:53:25 +00:00
docs(legal): add baseline legal documents (privacy, ToS, DPA, subprocessors, cookies)
All checks were successful
Mirror to GitHub / mirror (push) Successful in 6s
41f5519916
Generated by the resolutionflow-legal skill from a code scan of the FastAPI
backend + React frontend on commit 0564646. Each document is a starting
point for attorney review, not legal advice.

Includes:
- privacy-policy.md, terms-of-service.md, cookie-policy.md (public-facing)
- dpa.md (contractual; signed with MSP customers)
- subprocessor-list.md (Railway, Anthropic, Voyage, Stripe, Resend, Sentry,
  PostHog, Google Fonts — confirmed live as of scan)
- data-inventory.md + classification.md (Phase 1/2 working files)
- attorney-review-checklist.md (consolidated [LEGAL REVIEW] punch list)
- implementation-verification.md (claim-by-claim audit vs. actual code)

Three blocking issues filed before public publication:
- #175 deletion-on-offboarding (or rewrite retention claims)
- #176 narrow Sentry send_default_pii + Session Replay config
- #177 EU/UK consent for PostHog + Google Fonts

Public-facing documents intentionally route physical-mail requests through
support@ rather than publishing the LLC's registered address.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New seat tier between engineer and viewer. Dedicated /l1 surface
(dashboard + walker + drafts) for first-call helpdesk staff. Walk-in
intake + PSA queue both produce tickets. Match-or-build pipeline
prefers authored flows, then outcome-validated AI drafts, then builds
fresh from KB. Three KB connectors: IT Glue, Hudu, SharePoint/OneDrive.
Escalation via package + PSA reassign, picked up in chat. Engineer
coverage via per-user can_cover_l1 flag with audit-log tagging.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restructure walked_path off FlowProposal onto new l1_walk_sessions table
(each L1 walk has its own path; proposal carries only the validation bit).
Add adhoc walk variant for live calls when no KB content exists, with a
dedicated BuildAbortedNoKB screen offering ad-hoc/escalate/near-miss
options. Introduce SUGGEST_THRESHOLD below MATCH_THRESHOLD so near-miss
flows surface as suggestions instead of triggering a 10s build. Define
empty-state dashboard mode for first-run accounts. Spec the Microsoft
Graph OAuth flow concretely (multi-tenant app, redirect callback, token
refresh). Add seat enforcement for both L1 and engineer tracks via shared
helper (engineer enforcement was missing in current code). Make audit
policy explicit (resolve/escalate only, not per-step). Add session
lifecycle (concurrent sessions, browser-close recovery, 24h abandonment).
Clarify KB doc visibility is owner/engineer only (L1s see citations in
walker, not /account/kb directly). Acknowledge escalation notification
noise as v1 limitation with targeted notification deferred to v2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
26 bite-sized TDD tasks covering: l1_tech role + perms, seat enforcement
(L1 + engineer together), 5 migrations (role/columns, FlowProposal,
internal_tickets, l1_walk_sessions), seat_enforcement/internal_ticket/
l1_session services, full L1 endpoint surface (intake/queue/step/notes/
resolve/escalate/escalate-without-walk), APScheduler cleanup for 24h
abandoned sessions, frontend usePermissions/Sidebar/router updates,
L1Dashboard (active + empty state + resume widget), L1WalkPage with tree
and adhoc variants, coverage banner, seat counter widget, RLS regression
tests, E2E Playwright suite, acceptance walkthrough.

Phase 2 (AI build + KB documents) and Phase 3 (KB connectors) get
their own plan files. Phase 1 ships with adhoc walks as the default
intake; user-facing flow selection ships in Phase 2 alongside the AI
matcher. PSA close/reassign is a Phase 1 stub (deferred to Phase 2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds users.can_cover_l1, accounts.l1_seats_purchased, subscriptions.l1_seat_limit,
audit_logs.acting_as. Rotates the users.account_role CHECK constraint to include
'l1_tech' (was: 'owner', 'admin', 'engineer', 'viewer').

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds source (NOT NULL, backfilled to 'manual_draft'), linked_ticket_id,
linked_ticket_kind, validated_by_outcome columns. CHECK constraints on
source values and linked_ticket_kind values. walked_path lives on the
new l1_walk_sessions table (Task 6) — NOT on FlowProposal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tenant-scoped fallback ticket model for accounts without PSA integration.
Tracks customer-name, problem-statement, status lifecycle (open/walking/
resolved/escalated), and optional links to flow/proposal/ai_session/
assigned engineer + PSA promotion ID. Account-scoped RLS policy uses
app.current_account_id session setting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-session state for L1 walking a ticket. Supports flow/proposal/adhoc
session kinds; check constraint enforces target-consistency (flow_id set
iff kind=flow; flow_proposal_id set iff kind=proposal; both null iff
kind=adhoc). walked_path + walk_notes JSONB columns track step-by-step
progress; resolved/escalated/abandoned terminal statuses captured.
Account-scoped RLS matches the internal_tickets precedent (FORCE RLS +
tenant_isolation policy with COALESCE/NULLIF guard).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Aligns the model with the migration (T6 review caught: migration creates
ix_l1_walk_sessions_last_step_at but model annotation was missing, causing
schema drift if Base.metadata.create_all is used in tests).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Shared helper used by invite, accept-invite, and role-change endpoints
(integrated in T8). Counts active users by role against role-specific
seat limit on subscription (engineer → seat_limit, l1_tech → l1_seat_limit).
None limit = unlimited.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
For engineer + l1_tech roles, check_seat_available is called at each
mutation point. Returns 402 Payment Required with structured detail
{code: 'seat_limit_exceeded', role, current, limit, upgrade_url} when
seats are full. Grandfathering: existing over-seated accounts keep
existing users; only new mutations are blocked.

Also updates AccountInviteCreate and AccountRoleUpdate schemas to
accept l1_tech as a valid role value.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- oauth.py: use status.HTTP_402_PAYMENT_REQUIRED constant (was raw 402)
- accounts.py bulk-invite: catch HTTPException separately to preserve
  structured detail dict in failed-row error (was stringified repr,
  unparseable by clients)
- Add bulk-invite per-row 402 test verifying structured error preserved

T8 code review identified these as Important issues. Functional change is
the bulk-invite fix; clients can now parse seat-limit errors from bulk
responses. 13/13 seat-enforcement tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Returns {engineer: SeatCheckResult, l1_tech: SeatCheckResult} for the
authenticated engineer's account. Powers the SeatCounterWidget UI in the
admin/users + account/users surfaces. Engineer+ access only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Owner-only endpoint to toggle can_cover_l1 on an engineer user. 422 if target
role is not engineer (owners/super_admins already see L1 surface; viewers/
l1_techs don't need this flag). 404 for cross-account targets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
create_ticket, update_status (sets resolved_at on resolve), get_ticket,
list_tickets_for_account (status filter, account-scoped), promote_to_psa.
Used by L1 intake when account has no PSA integration configured.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
T11 review caught that get_ticket was the one function without the *, marker
all other functions in the module use. One-line fix, no caller impact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three start_* functions creating L1WalkSession rows with appropriate
session_kind and target id. Engineers acting in L1 mode get
acting_as='l1_coverage' for audit; native l1_tech users get acting_as=None.

step/notes (T13) and resolve/escalate (T14) extend this file next.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
record_step appends to walked_path JSONB and advances current_node_id
on flow/proposal walks; refuses adhoc sessions. update_notes replaces
walk_notes (used by adhoc walks for debounced autosave); 256KB size cap
to prevent unbounded JSONB growth. Both reject non-active sessions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
resolve: sets status=resolved, helpful, resolution_notes, resolved_at;
flips FlowProposal.validated_by_outcome on helpful=True proposal walks;
closes linked internal ticket. PSA close is a Phase 2 stub.

escalate: marks session + internal ticket as escalated. PSA reassign
deferred to Phase 2.

escalate_without_walk: creates an immediately-escalated adhoc session
with no walked_path, used by the BuildAbortedNoKB → Escalate path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mounts /api/v1/l1/* with require_l1_or_coverage on every route. Intake
creates an internal ticket and starts a flow OR adhoc session (PSA queue
merge follows in Phase 2). Step/notes/resolve/escalate delegate to
l1_session_service. escalate-without-walk creates an immediately-
escalated session for the BuildAbortedNoKB path.

ValueError from services → 400. Cross-account session access → 404.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
flip_stale_sessions flips L1WalkSession.status from 'active' to
'abandoned' for rows where last_step_at is older than 24h. Preserves the
row for audit; removes it from the L1 dashboard's 'Resume in progress'
widget. Runs hourly via APScheduler with max_instances=1 (Lesson 1).
Uses the admin session factory (no RLS context at startup).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds 8 synchronous psycopg2-based tests that connect as resolutionflow_app
and verify the tenant_isolation RLS policies (USING + WITH CHECK) on the two
new L1 Phase 1 tables block cross-tenant reads and reject cross-tenant INSERTs.

Uses psycopg2 (not asyncpg) to avoid the conftest pytest_runtest_teardown hook
that closes the asyncio event loop after every test — incompatible with
module-scoped asyncpg fixtures in pytest-asyncio 0.24.

conftest.py: extends _RLS_TEST_FILES set to include test_l1_rls.py so it is
excluded from the default create_all test suite (requires RUN_RLS_TESTS=1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds 'l1_tech' to the AccountRole union, includes can_cover_l1 on the User
type, and exposes isL1Tech / canCoverL1 / canUseL1Surface /
canUseEngineerSurface from usePermissions. Existing isEngineer/isOwner/
etc. flags unchanged in semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
L1 users see a focused sidebar with only their L1 surfaces (Workspace,
Tickets, My Drafts, Guides, Account). Engineers with can_cover_l1
(plus owners/super_admins) get an appended "L1 Workspace" entry in
their existing sidebar. ProtectedRoute redirects L1 users from / to /l1
on login.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
L1RouteGuard wraps the new routes and redirects users without
canUseL1Surface back to /. Page components are stubs in this task
(real UI in T21-T24): L1Dashboard, L1WalkPage, L1DraftsPage,
L1TicketsPage.

Routes: /l1, /l1/walk/:sessionId, /l1/drafts, /l1/tickets — all gated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the T20 stub. L1 dashboard renders greeting, "Describe the
problem" intake card (autofocus textarea, optional customer fields,
primary "Start walk" CTA), open-tickets queue (Phase 1: display-only),
and a "Resume in progress" widget listing the L1's active sessions
ordered by last_step_at DESC. Empty-state card shows on accounts with
no queue + no active sessions (first-run nudge to upload KB or auth flows).

Adds /api/l1.ts (full L1 API client surface) and /types/l1.ts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the T20 stub. WalkPage dispatches by session_kind:
- 'flow' / 'proposal' → L1WalkTreeVariant (this commit)
- 'adhoc' → placeholder until T23

L1WalkTreeVariant: sticky header with back link + AI-built badge +
persistent Escalate/Resolve buttons; two-pane body (current step
yes/no card on left, walked-path transcript on right). ResolveModal
and EscalateModal extracted to shared WalkModals.tsx (T23 reuses).

Phase 1 caveat: this surface isn't reached by user-driven intake
(which creates adhoc sessions only). It's exercised via direct URL
or integration tests until Phase 2 wires match_or_build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The session variant that Phase 1 L1 users actually hit (intake creates
adhoc sessions when no flow_id is provided). Single-pane note-taking
surface with 300ms-debounced autosave to walk_notes. Shares header
shape + Resolve/Escalate modals with the tree variant. Splits the
notes textarea by paragraph and persists each as a structured
AdhocNote entry. Stops saving once status leaves 'active'.

L1WalkPage now dispatches both variants.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
L1DraftsPage is a Phase 1 placeholder (AI drafts arrive in Phase 2).
L1TicketsPage replaces the stub with a status-filterable internal-tickets
queue. L1CoverageBanner renders inside L1RouteGuard so every /l1/* page
shows it for engineer-coverers (hidden for native L1). SeatCounterWidget
+ /api/seats.ts surface engineer + L1 seat usage from the /accounts/me/
seats endpoint (T9).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
l1-workspace.spec.ts covers:
- L1 user lands on /l1, intakes a problem, takes notes (autosave), resolves
- L1 cannot access /pilot, /trees/new, /escalations (route guards)
- Engineer with can_cover_l1 sees the L1 Workspace nav + coverage banner
- escalate-without-walk path via direct API call returns escalated session

Seed script adds l1@resolutionflow.example.com (l1_tech) and
engineer-coverage@resolutionflow.example.com (engineer + can_cover_l1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Full backend suite (1325/1325 passing, xdist) + L1-specific tests
(57/57) + L1 RLS tests (8/8) + frontend build (tsc clean, vite clean)
+ migration roundtrip results. Per-line checklist against spec §15.
Known Phase 2/3 items explicitly deferred per plan scope section.

fix(test): RLS fixture users INSERT missing NOT NULL columns
  test_l1_rls.py and test_rls_isolation.py seeded users without the
  five NOT NULL columns added in prior migrations (is_super_admin,
  is_team_admin, is_service_account, must_change_password, timezone).
  Also adds DROP SCHEMA before alembic upgrade in _ensure_rls_schema
  to prevent DuplicateTable errors when create_all tables are present.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per spec §5.6.1, audit rows are written at session terminal events
(resolve, escalate, escalate_without_walk). log_audit gains an optional
acting_as parameter that propagates the session's acting_as tag
('l1_coverage' for engineer coverers, null for native L1 users).
Final code review flagged this as Important — column existed but was
never populated. Four new integration tests cover all three paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sessions are account-scoped (per spec §7.9), not user-scoped, to support
team coverage. Comment-only fix surfaced by final review.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Harden the Anthropic provider and lay the groundwork for schema-constrained
JSON, optimizing the existing claude-sonnet-4-6 / claude-haiku-4-5 usage
(no model changes).

ai_provider.py:
- _extract_text_from_response replaces fragile response.content[0].text:
  skips non-text leading blocks (e.g. thinking), returns the first text
  block, logs an anthropic.stop_reason warning on max_tokens/refusal
  (truncation now observable), and raises ValueError on a no-text response.
- generate_json gains an optional `schema` param. Anthropic wires it to
  output_config.format (structured outputs); schema=None preserves the exact
  prior call for every existing caller. Gemini accepts-and-ignores it.

kb_conversion_service.py:
- TROUBLESHOOTING_SCHEMA / PROCEDURAL_SCHEMA + _schema_for_target_type(),
  modelled as a strict superset of every field the prompts emit.
- convert_document passes the schema only when the new
  AI_KB_CONVERT_STRUCTURED_OUTPUT setting is True (default False). The
  _try_repair_json fallback stays as belt-and-suspenders.

Tests: 14 provider + 7 schema, TDD (red-green). Live constrained-decoding
smoke-test still required before enabling the flag in production.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Final review flagged silent failure on intake error. Adds a toast with
the backend detail message (or fallback) on catch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stop crashed-process core dumps (core.144926, etc.) from showing up as
untracked noise / being committed by accident.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Catches the structured detail from the seat-enforcement 402 and surfaces
a clear toast with current/limit counts instead of a silent failure.
Modal-with-upgrade-link is a v2 polish — Phase 1 just ships a toast.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(landing): redesign hero + editorial layout with Atkinson Hyperlegible
All checks were successful
Mirror to GitHub / mirror (push) Successful in 7s
CI / frontend (pull_request) Successful in 7m6s
CI / e2e (pull_request) Successful in 10m32s
CI / backend (pull_request) Successful in 11m54s
60b1e654f8
Recover and commit the landing-page redesign that had been sitting
uncommitted in the working tree: refreshed dark palette (adjusted
--lp-bg-alt, electric-blue accent), Atkinson Hyperlegible Next display
+ body type, and editorial hero/section layout in LandingPage.tsx, with
the matching font preload in index.html.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
docs(decisions): scope structured outputs to flat-array JSON (close 3c)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 6s
CI / frontend (pull_request) Successful in 7m12s
CI / backend (pull_request) Successful in 11m51s
CI / e2e (pull_request) Successful in 10m7s
02db15f118
Record the 3c finding: Anthropic structured outputs apply only to flat-array
generate_json outputs (kb_conversion). ai_fix and knowledge_flywheel flow-gen
emit recursive/nested decision trees that the "no recursive schemas" limit
excludes; their fence-strippers stay. Documents the deferred kb-only
_try_repair_json removal pending staging validation of the
AI_KB_CONVERT_STRUCTURED_OUTPUT flag.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
docs(l1): post-final-review fixes addendum to acceptance report
Some checks failed
Mirror to GitHub / mirror (push) Successful in 5s
CI / frontend (pull_request) Failing after 1m46s
CI / e2e (pull_request) Failing after 6m10s
CI / backend (pull_request) Successful in 11m47s
2f2f4eea29
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Merge feat/l1-workspace into integration branch
Some checks failed
Mirror to GitHub / mirror (push) Successful in 5s
CI / frontend (pull_request) Failing after 1m29s
CI / e2e (pull_request) Failing after 6m23s
CI / backend (pull_request) Successful in 11m30s
5c67dc9c59
# Conflicts:
#	frontend/src/router.tsx
chihlasm added 1 commit 2026-05-29 04:07:00 +00:00
fix(l1): block L1 users from engineer-only AI routes (/pilot, /assistant)
Some checks failed
Mirror to GitHub / mirror (push) Successful in 6s
CI / frontend (pull_request) Failing after 2m19s
CI / e2e (pull_request) Failing after 7m3s
CI / backend (pull_request) Successful in 17m3s
4da30531cb
The post-login redirect pushes l1_tech users from / to /l1, but a
bookmark, browser back, or direct URL still landed L1 users on /pilot,
where the page tried to POST /api/v1/ai-sessions and got 403. Frontend
swallowed that as a generic 'Failed to start AI conversation' toast.

Add a route-level redirect in ProtectedRoute so L1 users hitting /pilot
or /assistant bounce to /l1 — turns the backend 403 into a clean UX path
that matches the spec's intent (L1 = walker, engineer = pilot).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
chihlasm closed this pull request 2026-05-29 05:18:48 +00:00
Some checks failed
Mirror to GitHub / mirror (push) Successful in 6s
CI / frontend (pull_request) Failing after 2m19s
Required
Details
CI / e2e (pull_request) Failing after 7m3s
CI / backend (pull_request) Successful in 17m3s
Required
Details

Pull request closed

Sign in to join this conversation.