Per gstack team-mode install: adds a PreToolUse hook that blocks
skill usage when gstack isn't installed globally, so contributors
are prompted to install it. Un-ignores the two required files
(.claude/settings.json, .claude/hooks/check-gstack.sh) while
keeping settings.local.json and other Claude state ignored.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reflect current state: dual-agent migration + Codex review round +
branch cleanup (RLS test gating, Phase 9 docs, .remember/ gitignore,
landing-handoff deletion). Working tree clean, no active task, 3
cleanup commits queued to push.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Review findings companion to docs/FlowAssist_Migration/Issues/phase-8-review-issues.md.
Documents the issues addressed by commit 24972e8 (partial-outcome notes
+ per-fix script-builder remount).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Continues the test-isolation work from dab740d. RLS migration tests run
against a policy-installed database and fail in the default create_all
suite, so they need to be opt-in:
- pytest.ini: register `rls` marker.
- conftest.py: auto-deselect test_rls_isolation.py unless
RUN_RLS_TESTS=1. Drops the deprecated session-scoped event_loop
fixture (not needed since pytest-asyncio 0.23+).
- test_rls_isolation.py: tag module with `rls` marker. Replace
hardcoded `patherly_test` DB reference with parsed DATABASE_TEST_URL
(matches conftest.py default `resolutionflow_test`). Updated docstring
command to show RUN_RLS_TESTS=1.
- requirements-dev.txt: bump pytest-asyncio 0.23.0 → 0.24.0 (loop-scope
marker behavior required by the RLS module fixture).
Run the RLS suite with:
RUN_RLS_TESTS=1 DB_APP_ROLE_PASSWORD=... pytest tests/test_rls_isolation.py
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codex review of the dual-agent handoff migration flagged factual errors
carried over verbatim from the pre-migration CLAUDE.md. All claims
verified against the live code before correction.
PROJECT_CONTEXT.md — SaaS shape:
- Role hierarchy was `super_admin > team_admin > engineer > viewer`,
but `backend/app/core/permissions.py:4` and
`frontend/src/hooks/usePermissions.ts:4` both define it as
`super_admin > owner > engineer > viewer`. The `team_admin` concept
exists separately as an orthogonal team-scoped gate
(`require_team_admin`, `is_team_admin=True` + valid `team_id`), not
a level in the primary hierarchy.
- Dep list was missing `require_account_owner` and `require_team_admin`,
both present in `backend/app/api/deps.py`.
PROJECT_CONTEXT.md — directory tree:
- `api/endpoints/` comment listed 11 routers; `api/router.py` actually
registers 50+. Replaced with a summary that points at `router.py` as
the source of truth instead of trying to maintain a freezing list.
- `services/psa/` comment omitted `exceptions.py` and `ticket_context.py`,
both present in the directory.
CURRENT_TASK.md + TODO.md:
- Replaced `<!-- EXAMPLE -->` placeholders with clearer empty-state
sentinels so a resume agent sees "no real task yet" at a glance
rather than placeholder acceptance criteria that look unresolved.
SESSION_LOG.md updated with a follow-up bullet documenting this pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Split the monolithic CLAUDE.md into a durable handoff system:
- .ai/PROJECT_CONTEXT.md — stable architectural truth (stack, structure,
SaaS shape, ConnectWise, coding standards, frontend patterns, critical
lessons). Ported verbatim from the previous CLAUDE.md.
- .ai/CURRENT_TASK.md — single active task with DoD + out-of-scope.
- .ai/HANDOFF.md — resume point, kept under ~2K tokens.
- .ai/TODO.md — backlog, read only when CURRENT_TASK complete.
- .ai/DECISIONS.md — append-only architectural decision log.
- .ai/SESSION_LOG.md — append-only chronological history.
- .ai/README.md — human-facing explanation of the system.
Root agent files share a byte-identical protocol block (verified via diff):
- CLAUDE.md — primary agent, with GitNexus + gstack tooling and the
Claude Opus 4.7 co-author trailer.
- AGENTS.md — OpenAI Codex resume agent, with grep/rg fallbacks and the
Codex co-author trailer. Steps in when Claude hits session/weekly
limits.
Legacy root-level SESSION-HANDOFF.md deleted — superseded by .ai/HANDOFF.md.
It was a self-describing one-off from the Design System v4 migration and
had no external references.
Supersedes previous CLAUDE.md. Old version recoverable via
`git show pre-ai-handoff:CLAUDE.md` (tag points at commit e110fed).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause of the 06:32 AM outage: running 'pytest tests/' inside the
resolutionflow_backend container silently dropped the public schema on
the DEV database. Two layered bugs made this possible; both are fixed.
Bug 1 — env-var lookup in conftest.TEST_DATABASE_URL put DATABASE_URL
(which normally points at the dev/prod DB) ahead of DATABASE_TEST_URL.
When DATABASE_URL is set, pytest used the dev DB as the 'test' DB and
the test_db fixture's DROP SCHEMA public CASCADE wiped it. Fixed:
- Honor only DATABASE_TEST_URL (or the localhost fallback).
- Assert at module load that the DB name contains 'test' — refuses
to run otherwise. Makes future misconfiguration impossible.
Bug 2 — conftest overrode app.dependency_overrides[get_db] but not
get_admin_db. Endpoints using get_admin_db (register, admin routes)
bypassed the test session and hit the real admin DB. Before Bug 1 was
fixed this was hidden because both engines pointed at the same dev DB.
With isolation in place, register started failing 'Email already
registered' because of stale users in the dev DB. Fixed:
- Also override get_admin_db to yield the same test session. RLS is
not enabled in the create_all-managed test schema, so sharing is
safe.
Also adds DATABASE_TEST_URL=resolutionflow_test to docker-compose.dev.yml
so pytest in the container works out of the box.
Verified: 49/50 Phase 8 + 9 tests pass against resolutionflow_test; the
1 failure is the pre-existing Phase 8 Issue #4
(test_record_decision_persists_and_bumps_state_version).
Refs gitea #145 (will update that issue with this as the primary fix).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses docs/FlowAssist_Migration/Issues/phase-9-review-issues.md.
Issue #1 (High): "Applied partially" from the escalation intercept silently
dropped because the backend requires notes on applied_partial and the dialog
sent none. The catch was silent and the UI advanced into the conclude flow
as if the outcome were recorded.
- EscalateInterceptDialog now has a two-step flow: clicking the partial
choice reveals a notes textarea (autofocused, required non-empty) plus
Back / "Record partial & escalate" buttons.
- onChoose signature extended to (choice, notes?).
- handleInterceptChoice passes notes to patchOutcome; on failure it
surfaces a toast and does NOT advance to the conclude modal, so the
intercept stays open for retry.
Issue #2 (Medium/High): ScriptBuilderTab kept local state across active-fix
changes within the same pilot session, so a stale draft could PATCH against
a newer fix.id. Added key={activeFix.id} on the mount — forces a clean
remount per fix; backend get-or-create (keyed on user+ai_session_id) still
returns the same session row, which is the intended resume-on-refresh
semantic; but messages/editorBuffer/latestScript local state resets.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Handoff + migration spec incorrectly claimed Phase 9 added a new
parent_pilot_session_id FK. The implementation reuses the existing
ai_session_id column; the migration only adds the origin discriminator
+ partial unique index. Also: ScriptBuilderTab wraps ScriptBuilderChat
and ScriptBodyEditor (Monaco), not "ScriptBuilderChat in ephemeral
mode" — there is no ephemeral mode on the presentational component.
Applies applied_at call-site specifics: handleScriptDecision stamps
on one_off/draft_template, TemplateMatchPanel stamps on onMarkRun,
Script Builder tab Submit does not stamp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marks open items #1 (NoTemplateDialog narrow-lane) and #3 (Tabbed
Script Builder) as resolved. Records the applied_at semantics
correction as shipped. Final Phase 9 row added to the 'What shipped'
table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Phase 9 §5. Before: banner Apply click stamped applied_at
regardless of whether the engineer had committed to running anything,
starting the Verifying timer prematurely. After:
- handleApplyFix no longer calls applyFix(). It just routes to the
right surface (TemplateMatchPanel / InlineNoTemplateDialog / Script
Builder tab).
- handleScriptDecision stamps applied_at for one_off + draft_template
(both labels are 'Run now, …' — the click is the declaration).
build_template does not stamp.
- TemplateMatchPanel's new 'I ran this' button calls applyFix via a
new onMarkRun prop.
- Script Builder tab Submit does not stamp (a draft is not a run).
No backend change — the /apply endpoint is unchanged. Only call sites
move.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the three new components into AssistantChatPage:
- ChatTabStrip renders when the active fix needs a script drafted.
- ScriptBuilderTab sits alongside chat via display:none toggling so
chat scroll position + builder state both persist.
- InlineNoTemplateDialog replaces the task-lane bottomSlot render for
the drafted-script evaluation case; three cards finally fit.
- Banner Apply routing updated: no-draft/no-template → Script Builder
tab; drafted → InlineNoTemplateDialog; template → unchanged path.
applyFix() call site moves land in the next task.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the gap Phase 8 final review flagged. When a fix is in
applied_partial state and the engineer escalates, the intercept no
longer forces them to approximate with didn't-work/worked/never-applied.
AssistantChatPage's handleInterceptChoice (Task 13) already dispatches
to patchOutcome for any FixOutcome value, so no handler change is
needed — the type already supports applied_partial.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generate and Copy alone don't declare a run — the engineer can walk
away after copying. Phase 9 §5 defines an explicit run-declaration
affordance so applied_at only stamps on the engineer's positive
commitment. Wiring from AssistantChatPage lands in Task 13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slide-up wrapper around the existing NoTemplateDialog for rendering
in the chat region above the composer (parallel to ProposalBanner).
The chat region's width lets grid-cols-3 finally work as intended.
No change to NoTemplateDialog itself; decision callbacks and card
copy stay identical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Owns the inline Script Builder session lifecycle:
- Get-or-create (origin='pilot_inline', ai_session_id) on mount.
- Renders ScriptBuilderChat in AI mode and CodeModeEditor (Monaco) in
'Write it myself' mode. Mode toggles via display:none so buffer and
messages persist across switches.
- Submit → sessionSuggestedFixesApi.patchScript; emits onScriptDrafted
to parent, which refreshes the fix and hides the tab strip.
- Relays in-progress state to the parent via onProgressChange for the
ChatTabStrip's indicator dot.
ScriptBuilderChat is untouched (stays presentational). Persistence
semantics live on the controller, not the display component.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-tab strip for the chat region. Parent controls mounting (strip only
appears when the fix needs a script drafted). Indicator dot signals
in-progress draft state. Tab switching via onChange callback; parent
handles display:none toggling so tab contents preserve state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sessionSuggestedFixesApi.patchScript(sessionId, fixId, script, params?)
hits the new PATCH /script endpoint.
scriptBuilder.createSession accepts an optional options bag with
origin + aiSessionId, defaulting to standalone when omitted so legacy
callers stay behavior-preserving.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Called by the inline Script Builder tab on Submit. Writes
ai_drafted_script + ai_drafted_parameters to the fix without stamping
applied_at (a draft is not an application — that's §5 of the Phase 9
spec). Bumps state_version so Resolve/Escalate preview bundles
regenerate.
409 on terminal fix status. 404 on wrong session. 422 on empty script.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
POST /script-builder/sessions now supports origin='pilot_inline':
- Requires ai_session_id; validates it against current user ownership.
- Get-or-create: returns existing row for (user, ai_session_id) pair.
- Partial unique index on the DB backs the invariant; races resolve to
the single winner row.
list_sessions + count_user_sessions default-scope to origin='standalone'
so inline scratch sessions don't pollute the /script-builder dashboard
or count against the 5-session cap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the DB column added in the prior migration. App-level default
is 'standalone' so existing callers of ScriptBuilderSession(...) work
without code changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 9 prep. Adds:
- origin VARCHAR(20) NOT NULL with CHECK ('standalone' | 'pilot_inline')
- invariant: pilot_inline rows must have ai_session_id
- partial unique index on (user_id, ai_session_id) WHERE origin='pilot_inline'
— backs get-or-create idempotency for the inline Script Builder tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Frontend scriptBuilder API client inventory now matches the backend
schema: createSession accepts BOTH origin and ai_session_id (both
required together for inline callers, both omitted for standalone).
- 'If template -> unchanged' sharpened: render location is unchanged,
but run stamping moves into the panel's new 'I ran this' action per
the §5 apply lifecycle correction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- scriptBuilderMode ownership: pinned to ScriptBuilderTab, removed from
AssistantChatPage's state list. Parent never drives the AI/editor
toggle; controller owns it and resets naturally on session switch via
unmount/remount. scriptBuilderHasProgress stays on the page (needed
for the tab strip indicator dot) and is driven by the controller via
an onProgressChange callback.
- ScriptBuilderCreateRequest schema: explicitly calls for TWO new
optional fields (origin + ai_session_id), not just origin. Handler
enforces: when origin='pilot_inline', ai_session_id is required and
must pass the current-user ownership check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three consistency fixes:
- File inventory (backend + frontend) now names all three apply-stamp
call sites: handleScriptDecision('one_off' | 'draft_template') plus
TemplateMatchPanel's 'I ran this' handler. Previously listed only
'one_off' in two places, contradicting the §5 lifecycle table.
- NoTemplateDialog relocation section no longer claims the decision
handler is 'unchanged' — it is unchanged EXCEPT for the moved
apply stamp, which is the point of §5.
- Open deferrals entry on ScriptBuilderChat 'ephemeral mode' removed;
replaced with the actual new surface (ScriptBuilderTab controller),
which reuses the existing script-builder prompt unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four review findings addressed:
- High: draft_template 'Run now, templatize after' DOES run the
script; applied_at table now stamps for both one_off and
draft_template. Only build_template (no run) skips the stamp.
- Medium: TemplateMatchPanel needs an explicit '✓ I ran this' button.
Generate/Copy don't commit to running. The new button is the stamp
moment for template-match fixes.
- Medium: get-or-create for inline script_builder_sessions —
POST /script-builder/sessions is now idempotent for
origin='pilot_inline' (returns the existing row for a
(user, ai_session_id) pair). Backed by a partial unique index:
UNIQUE (user_id, ai_session_id) WHERE origin = 'pilot_inline'
so remount doesn't create duplicates and draft continuity is
preserved.
- Medium: authorization — the create endpoint validates that any
provided ai_session_id is owned by the current user (same guard
other pilot endpoints use). Prevents cross-user attachment of
scratch sessions to arbitrary pilot sessions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four findings addressed:
- High: drop proposed parent_pilot_session_id column; reuse the
existing ai_session_id FK on script_builder_sessions. Add an
origin + ai_session_id coherence invariant.
- High: don't add a 'mode' prop to ScriptBuilderChat (it's
presentational). Introduce a ScriptBuilderTab controller that owns
session lifecycle + submit, renders ScriptBuilderChat unchanged.
- Medium: filter list_sessions / count_user_sessions to origin='standalone'
so pilot_inline scratch sessions don't pollute the /script-builder
dashboard or count against the 5-session cap.
- Medium: applied_at is stamped only when the engineer commits to a
run-action (one_off, TemplateMatchPanel Run), not on banner Apply
click. Corrects a Phase 8 over-eager stamp that would otherwise
multiply across three surfaces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Design doc for the FlowPilot migration's remaining open items:
- NoTemplateDialog narrow-lane bug (resolved by moving the dialog to
the chat region alongside ProposalBanner — three cards fit naturally
at that width; grid-cols fix no longer needed)
- Tabbed Script Builder inside the chat (new [Chat] [Script Builder ●]
tab strip; AI chat default with 'Write it myself' Monaco escape hatch)
Plus a Phase 8 cleanup:
- EscalateInterceptDialog fourth 'I applied some of it — partial' choice
All six architecture decisions settled via brainstorming before writing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tracks the three code-review issues that were fixed on this branch
(#1 outcome-aware previews, #2 persist Apply, #3 persist proposal
rejection) plus a newly-documented pre-existing test failure (#4 —
decision-endpoint test written in Phase 3 never updated when Phase 5
added the drafted-script validation guard).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #3 from phase-8-review-issues.md. 'Not yet' on the AI-confirming
banner was a local-state hide; the proposal re-surfaced on the next
refreshSessionDerived call.
Two-part fix:
- PATCH /outcome now clears ai_outcome_proposal on any terminal action
(engineer has taken a decision; stale AI proposal is moot).
- New DELETE /ai-sessions/:sid/suggested-fixes/:fid/ai-outcome-proposal
endpoint for explicit 'Not yet' rejection. Does not touch status
or state_version — pure UI state.
Frontend handleRejectAIProposal now calls the DELETE and setActiveFix
with the server response.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #2 from phase-8-review-issues.md. Apply was client-side-only via
a bannerApplied flag. Refresh / chat reselect / multi-tab would drop
Verifying state back to Proposed.
- New POST /ai-sessions/{sid}/suggested-fixes/{fid}/apply stamps
applied_at without changing status (still 'proposed'). Idempotent
if already stamped; 409 if fix is past proposed (a terminal outcome
was already recorded).
- Bumps state_version so resolve/escalate preview bundles reflect that
the fix has entered verifying.
- Frontend handleApplyFix calls the endpoint and uses the returned
applied_at directly. bannerApplied client flag is removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #1 from phase-8-review-issues.md. Cache invalidation alone isn't
enough — previews were also omitting outcome fields from the LLM bundle,
so a fresh regenerate still couldn't distinguish proposed / failed /
partial / success.
- PATCH /outcome now bumps ai_sessions.state_version (matches
record_decision's existing pattern).
- Resolution-note + escalation-package bundles now include status,
applied_at, verified_at, partial_notes, failure_reason on the active fix.
- Generator prompts prescribe outcome-aware phrasing (closure language
for success; what-we've-tried + next-steps for failed/partial).
- New end-to-end test asserts the regenerated preview reflects the
recorded outcome, not just that the cache key changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Correct the stale ai_sessions.fix_outcome reference (no such column) —
the real schema adds six columns to session_suggested_fixes. Update
last_commit to reflect the docs-correction tip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Correct the FLOWPILOT-MIGRATION.md stale references to a non-existent
ai_sessions.fix_outcome column — the actual implementation added six
columns to session_suggested_fixes. Also fix a stale first-commit SHA
(6721b84 → cdd8bb0, the former was amended away).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marks open item #2 (task-lane crowding / Suggested Fix discoverability)
as resolved by Phase 8. Open items #1 (NoTemplateDialog narrow-lane)
and #3 (Tabbed Script Builder inside chat) remain deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Superseded by ProposalBanner (Phase 8). The import was already removed
from AssistantChatPage in the previous commit; this deletes the orphaned
file itself and strips the now-unused suggestedFixSlot prop from
TaskLane's interface and both call sites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the task-lane SuggestedFix card with the ProposalBanner docked
above the chat composer. Wires:
- Resolve-while-verifying auto-marks applied_success (one-click resolve).
- Escalate-while-verifying opens EscalateInterceptDialog to capture the
real outcome (default: didn't work) before handoff.
- 3+ post-apply engineer messages trigger the passive Nudge banner.
- AI [FIX_OUTCOME] proposals surface in the AIConfirming state; one-click
confirm applies the outcome.
Banner state resets on session switch via resetSessionDerivedState.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Anchored above the Escalate button, captures fix outcome before the
engineer hands off the ticket. Defaults to 'didn't work' on Enter
(the common case). Alternatives: 'worked, escalating for another
reason' (preserves success) and 'never actually applied' (dismiss).
Task 11 will wire this to AssistantChatPage's Escalate handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes ProposalBanner's state machine. AIConfirming (accent-blue)
surfaces the AI's [FIX_OUTCOME] proposal with one-click accept; Nudge
is the compact passive-prompt variant for post-apply chats; Collapsed
is the 28px expand-hint strip.
Adds onSilenceNudge prop so the parent can silence the nudge without
collapsing it (Task 11 wires this). Removes the last three stale
eslint-disable-next-line comments — all sub-components now use props.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verifying: amber pulse animation, confidence pill becomes 'Applied Xm ago',
three actions (overflow for Mark partial, Didn't work, It worked). window.prompt
used for the partial notes + failure reason inputs — good-enough v1 pending
an inline composer.
Partial: cyan-toned to signal 'parked, outcome unknown', shows saved notes
inline, Finish it / Didn't work / It worked actions.
Adds pulse-amber to @theme animations alongside slide-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New component that will replace the task-lane SuggestedFix card. Docks
above the chat composer with a 320ms slide-up animation. This commit
implements only the Proposed state (Tasks 8 & 9 fill Verifying, Partial,
AI-confirming, Nudge, Collapsed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends SessionSuggestedFix with outcome fields (status, applied_at,
verified_at, partial_notes, failure_reason, ai_outcome_proposal) and
adds a patchOutcome method hitting the new backend endpoint.
FixStatus (5 values) + FixOutcome (4 writable values) mirror the
backend Pydantic types and the DB check constraint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tells the AI when + how to emit the [FIX_OUTCOME] marker that Task 4's
parser consumes. Placeholder-only per the anti-parrot pattern — no
literal UUIDs, outcomes, or reasons that could leak into unrelated
sessions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The AI emits [FIX_OUTCOME] when the engineer indicates in chat that a
prior suggested fix worked, didn't work, or was partially applied. The
marker writes to session_suggested_fixes.ai_outcome_proposal (JSONB),
which the frontend surfaces as a "confirm outcome?" banner. The status
column is only updated when the engineer clicks confirm (via PATCH
/outcome endpoint from Task 3).
Placeholder-only system prompt wiring comes in Task 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records engineer-reported outcome (applied_success|applied_failed|
applied_partial|dismissed). Enforces transition rules (partial → success/
failed allowed; terminal outcomes return 409) and notes requirements
(applied_partial requires notes).
Sets verified_at on success/failure, stamps applied_at if not already
set (handles the case where the AI [FIX_OUTCOME] marker fires before
the engineer clicks Apply).
Also fixes pre-existing test-infrastructure bug: network_diagram.py used
bare string server_default="'[]'" for JSONB columns, which asyncpg
rejects during test schema creation. Changed to text("'[]'::jsonb") to
match the pattern used by script_template.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>