HarborForge.Backend: dev-2026-03-29 -> main #13

Merged
hzhang merged 43 commits from dev-2026-03-29 into main 2026-04-05 22:08:15 +00:00
Owner

Automated PR creation for HarborForge submodule development branch.

Automated PR creation for HarborForge submodule development branch.
zhi added 43 commits 2026-04-05 20:48:09 +00:00
- New canonical model: Proposal, ProposalStatus (app/models/proposal.py)
- New canonical router: /projects/{id}/proposals (app/api/routers/proposals.py)
- Schemas renamed: ProposalCreate, ProposalUpdate, ProposalResponse, etc.
- Old propose.py and proposes.py kept as backward-compat shims
- Legacy /proposes API still works (delegates to /proposals handlers)
- DB table name (proposes), column (propose_code), and permission names
  (propose.*) kept unchanged for zero-migration compat
- Updated init_wizard.py comments
- Add comprehensive docstring to Proposal model documenting all relationships
- Add column comments for all fields (title, description, status, project_id, etc.)
- Mark feat_task_id as DEPRECATED (will be replaced by Essential->task mapping in BE-PR-008)
- Add proposal_code hybrid property as preferred alias for DB column propose_code
- Update ProposalResponse schema to include proposal_code alongside propose_code
- Update serializer to emit both proposal_code and propose_code for backward compat
- No DB migration needed -- only Python-level changes
- New app/models/essential.py with Essential model and EssentialType enum
  (feature, improvement, refactor)
- Fields: id, essential_code (unique), proposal_id (FK to proposes),
  type, title, description, created_by_id (FK to users), created_at, updated_at
- Added essentials relationship to Proposal model (cascade delete-orphan)
- Added essentials table auto-migration in main.py _migrate_schema()
- Registered essential module import in startup()
- Format: {proposal_code}:E{seq:05x} (e.g. PROJ01:P00001:E00001)
- Prefix 'E' for Essential, 5-digit zero-padded hex sequence
- Sequence scoped per Proposal, derived from max existing code
- No separate counter table needed (uses max-suffix approach)
- Supports batch_offset for bulk creation during Proposal Accept
- Includes validate_essential_code() helper
- New router: /projects/{project_id}/proposals/{proposal_id}/essentials
  - GET (list), POST (create), GET/{id}, PATCH/{id}, DELETE/{id}
- All mutations restricted to open proposals only
- Permission: creator, project owner, or global admin
- Registered essentials router in main.py
- Updated GET /proposals/{id} to return ProposalDetailResponse with
  embedded essentials list
- Activity logging on all CRUD operations
- Removed old logic that created a single story/feature task on accept
- Accept now iterates all Essentials under the Proposal
- Each Essential.type maps to a story/* task (feature/improvement/refactor)
- All tasks created in a single transaction
- Added ProposalAcceptResponse and GeneratedTaskSummary schemas
- Proposal must have at least one Essential to be accepted
- No longer writes to deprecated feat_task_id field
- Add source_proposal_id and source_essential_id FK columns to Task model
- Populate tracking fields during Proposal Accept task generation
- Add generated_tasks relationship on Proposal model for reverse lookup
- Expose source_proposal_id/source_essential_id in TaskResponse schema
- Add GeneratedTaskBrief schema and include generated_tasks in ProposalDetailResponse
- Proposal detail endpoint now returns generated story tasks with status
- Expand RESTRICTED_TYPE_SUBTYPES to include story/feature, story/improvement,
  story/refactor, and story/None (all story subtypes)
- Add FULLY_RESTRICTED_TYPES fast-path set for entire-type blocking
- Update _validate_task_type_subtype to block all story types via general
  create endpoint with clear error message directing to Proposal Accept
- Add type/subtype validation to PATCH /tasks/{id} to prevent changing
  existing tasks to story/* type via update
- Internal Proposal Accept flow unaffected (creates tasks directly via ORM)
- Updated model docstring with full deprecation strategy
- Updated column comment to mark as deprecated (BE-PR-010)
- Updated schema/router comments for deprecation clarity
- Added deprecation doc: docs/BE-PR-010-feat-task-id-deprecation.md
- feat_task_id superseded by Task.source_proposal_id (BE-PR-008)
- Patched conftest.py to monkey-patch app.core.config engine/SessionLocal
  with SQLite in-memory DB BEFORE importing the FastAPI app, preventing
  startup event from trying to connect to production MySQL
- All 29 tests pass: Essential CRUD (11), Proposal Accept (8),
  Story restricted (6), Legacy compat (4)
- New calendar.py model file with TimeSlot table definition
- SlotType enum: work, on_call, entertainment, system
- SlotStatus enum: not_started, ongoing, deferred, skipped, paused, finished, aborted
- EventType enum: job, entertainment, system_event
- All fields per design doc: user_id, date, slot_type, estimated_duration,
  scheduled_at, started_at, attended, actual_duration, event_type, event_data (JSON),
  priority, status, plan_id (FK to schedule_plans)
- Add DayOfWeek and MonthOfYear enums for plan period parameters
- Add SchedulePlan model with at_time/on_day/on_week/on_month fields
- Add DB-level check constraints enforcing hierarchy:
  on_month requires on_week, on_week requires on_day
- Add application-level @validates for on_week range (1-4),
  on_month hierarchy, and estimated_duration (1-50)
- Add is_active flag for soft-delete (plan-cancel)
- Add bidirectional relationship between SchedulePlan and TimeSlot
- All existing tests pass (29/29)
- New app/models/agent.py with Agent model, AgentStatus & ExhaustReason enums
- Agent has 1-to-1 FK to User, unique agent_id (OpenClaw $AGENT_ID),
  claw_identifier (OpenClaw instance, convention-matches MonitoredServer.identifier)
- Status fields: status (idle/on_call/busy/exhausted/offline), last_heartbeat
- Exhausted tracking: exhausted_at, recovery_at, exhaust_reason (rate_limit/billing)
- User model: added 'agent' back-reference (uselist=False)
- Schemas: AgentResponse, AgentStatusUpdate, UserCreate now accepts agent_id+claw_identifier
- UserResponse: includes agent_id when agent is bound
- Users router: create_user creates Agent record when agent_id+claw_identifier provided
- Auto-migration: CREATE TABLE agents in _migrate_schema()
- Startup imports: agent and calendar models registered
- New model: minimum_workloads table with JSON config column (per-user)
- Schemas: MinimumWorkloadConfig, MinimumWorkloadUpdate, MinimumWorkloadResponse
- Service: CRUD operations + check_workload_warnings() entry point for BE-CAL-007
- API: GET/PUT/PATCH /calendar/workload-config (self + admin routes)
- Migration: auto-create minimum_workloads table on startup
- Registered calendar router in main.py
- New service: app/services/plan_slot.py
  - Virtual slot ID: plan-{plan_id}-{YYYY-MM-DD} format with parse/make helpers
  - Plan-date matching: on_month/on_week/on_day hierarchy with week_of_month calc
  - Materialization: convert virtual slot to real TimeSlot row from plan template
  - Detach: clear plan_id after edit/cancel to break plan association
  - Bulk materialization: materialize_all_for_date for daily pre-compute
- New tests: tests/test_plan_slot.py (23 tests, all passing)
- New overlap.py service with check_overlap(), check_overlap_for_create(),
  and check_overlap_for_edit() functions
- Detects same-day time conflicts for a user's calendar
- Checks both real (materialized) TimeSlots and virtual (plan-generated) slots
- Excludes skipped/aborted slots from conflict checks
- Edit scenario excludes the slot being edited from conflict candidates
- Returns structured SlotConflict objects with human-readable messages
- 24 passing tests covering no-conflict, conflict detection, inactive
  exclusion, edit self-exclusion, virtual slot overlap, and message content
BE-CAL-007: Workload warning computation (already implemented in prior wave,
verified tests pass - 24/24). Computes daily/weekly/monthly/yearly scheduled
minutes and compares against user thresholds. Warnings are advisory only.

BE-CAL-008: New slot_immutability service with guards for:
- Forbid edit/cancel of past real slots (raises ImmutableSlotError)
- Forbid edit/cancel of past virtual slots
- Plan-edit/plan-cancel helper to identify past materialized slot IDs
  that must not be retroactively modified
Tests: 19/19 passing.
- Add TimeSlotCreate, TimeSlotResponse, TimeSlotCreateResponse schemas
- Add SlotConflictItem, SlotTypeEnum, EventTypeEnum, SlotStatusEnum to schemas
- Add POST /calendar/slots endpoint with overlap detection and workload warnings
- Add _slot_to_response helper for ORM -> schema conversion
- Add GET /calendar/day endpoint with optional ?date= query param
- Returns unified CalendarDayResponse merging real slots + virtual plan slots
- New CalendarSlotItem schema supports both real (id) and virtual (virtual_id) slots
- Excludes inactive slots (skipped/aborted) from results
- All slots sorted by scheduled_at ascending
- Helper functions for real/virtual slot conversion
- Add TimeSlotEdit schema (partial update, all fields optional)
- Add TimeSlotEditResponse schema
- Add PATCH /calendar/slots/{slot_id} for editing real slots
- Add PATCH /calendar/slots/virtual/{virtual_id} for editing virtual slots
  - Triggers materialization before applying edits
  - Detaches from plan after edit
- Both endpoints enforce past-slot immutability, overlap detection, plan
  detachment, and workload warnings
- Add POST /calendar/slots/{slot_id}/cancel for real slot cancellation
- Add POST /calendar/slots/virtual/{virtual_id}/cancel for virtual slot cancellation
- Virtual cancel materializes the slot first, then marks as Skipped
- Both endpoints enforce past-slot immutability guard
- Both endpoints detach from plan (set plan_id=NULL)
- Status set to SlotStatus.SKIPPED on cancel
- Add TimeSlotCancelResponse schema
- Add SchedulePlanCreate, SchedulePlanResponse, SchedulePlanListResponse schemas
- Add DayOfWeekEnum, MonthOfYearEnum schema enums
- Add POST /calendar/plans endpoint (create plan with hierarchy validation)
- Add GET /calendar/plans endpoint (list plans, optional include_inactive)
- Add GET /calendar/plans/{plan_id} endpoint (get single plan)
- PATCH /calendar/plans/{plan_id}: edit a recurring schedule plan
  - Validates period-parameter hierarchy after merge
  - Rejects edits to inactive (cancelled) plans
  - Detaches future materialized slots so they keep old data
  - Past materialized slots remain untouched

- POST /calendar/plans/{plan_id}/cancel: cancel (soft-delete) a plan
  - Sets is_active=False
  - Detaches future materialized slots (plan_id -> NULL)
  - Preserves past materialized slots, returns their IDs

- Added SchedulePlanEdit and SchedulePlanCancelResponse schemas
- Add GET /calendar/dates endpoint that returns sorted future dates
  with at least one materialized (real) slot
- Excludes skipped/aborted slots and pure plan-generated virtual dates
- Add DateListResponse schema
- New service: app/services/agent_heartbeat.py
- get_pending_slots_for_agent(): queries today's NotStarted/Deferred slots
  where scheduled_at <= now, sorted by priority descending
- get_pending_slot_count(): lightweight count-only variant
- Auto-materializes plan virtual slots for today before querying
- Supports injectable 'now' parameter for testing
- New service: app/services/agent_status.py
  - transition_to_busy(): Idle → Busy/OnCall based on slot type
  - transition_to_idle(): Busy/OnCall/Exhausted/Offline → Idle
  - transition_to_offline(): Any → Offline (heartbeat timeout)
  - transition_to_exhausted(): Any → Exhausted (rate-limit/billing)
  - check_heartbeat_timeout(): auto-detect >2min heartbeat gap
  - check_exhausted_recovery(): auto-recover when recovery_at reached
  - record_heartbeat(): update timestamp, recover Offline agents
- Tests: tests/test_agent_status.py (22 test cases)
- resolve_slot_competition: selects highest-priority slot as winner,
  marks remaining as Deferred with priority += 1 (capped at 99)
- defer_all_slots: defers all pending slots when agent is not idle
- CompetitionResult dataclass for structured return
- Full test coverage: winner selection, priority bumping, cap, ties,
  empty input, single slot, already-deferred slots
hzhang merged commit a2ab541b73 into main 2026-04-05 22:08:15 +00:00
hzhang deleted branch dev-2026-03-29 2026-04-05 22:08:16 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: zhi/HarborForge.Backend#13