Planning Proposal Engine & Capacity Reserve Calculations #25

Closed
opened 2026-09-22 17:58:57 +00:00 by yuris · 0 comments
Owner

Summary

Implement the planning proposal engine (POST /v1/plans and life plan --period today|week), calculating working window occupancy, enforcing the 40% unscheduled capacity reserve, applying personal policy constraints (max 3 intended outcomes per day, 1 active discretionary project), compiling ordered atomic proposal mutation steps, calculating canonical SHA-256 proposal digests, and storing proposals in SQLite without performing any writes to the Radicale CalDAV server.

Background & Technical Rationale

Calendar management systems frequently suffer from over-allocation: users schedule every available minute, leaving zero buffer for unexpected interruptions, overrun meetings, or cognitive fatigue. Furthermore, automated scheduling engines often make destructive or unexpected changes directly to remote calendar stores without human oversight.

Life resolves these challenges by introducing a two-stage planning model rooted in strict mathematical reserves and human-in-the-loop review:

  1. Mathematical Capacity Buffer (The 40% Rule): Rather than scheduling up to 100% of open calendar space, Life calculates declared available capacity (configured working window minus opaque busy time) and reserves exactly 40% of that available duration as unscheduled buffer time.
  2. Cognitive Load Caps: The engine enforces hard policy defaults: at most three intended outcomes per day, and at most one active discretionary project. Violations require explicit review and recorded policy exceptions.
  3. Observation Completeness: The engine never guesses availability. If calendar synchronization is incomplete, ongoing, or impaired (complete: false), the engine refuses confident free-time allocation, surfaces incomplete_calendar warnings, and marks remaining capacity as unknown.
  4. Decoupled Proposal Persistence (Zero Remote Writes): Generating a plan produces an immutable SQLite proposals artifact with an unforgeable cryptographic digest (SHA-256). Under no circumstances does proposal generation mutate Radicale calendar components (VEVENT) or task components (VTODO). Remote mutations occur only when the user explicitly reviews and executes the proposal via a separate approval workflow.

Exact Technical References & Specifications

1. Workflows Specification (docs/technical/workflows.md)

Section: ## Planning and interruption (lines 24–26):

| Workflow | Input and reads | Decision and approval | Writes and observable result | Failure path |
|---|---|---|---|---|
| Daily/weekly planning | Period, capacity preferences and intended outcomes; live events/tasks, explicit estimates and policy/context | Rust computes occupancy, deadlines and reserve; advisory Jev may rank eligible work; user reviews exact proposal | Generation stores a proposal; approved apply creates/updates blocks in deterministic order | Incomplete calendar forbids confident free-time allocation; missing estimates require clarification or clearly unallocated outcomes |

Section: ## Personal policy defaults (lines 42–50):

Proposed, configurable and user-changeable; never behavioral diagnoses:

- One meaningful career action per weekday. Success requires an explicit qualifying outcome linked to the action and day; allocating a calendar block is not success. Qualification must be user-approved or satisfy a reviewed deterministic rubric, never a model's unreviewed inference.
- At most three intended outcomes per day. This limits planned outcomes, not incoming obligations or factual agenda results.
- One active discretionary project. Necessary obligations remain visible; activating another requires an explicit policy exception or reprioritization, not hidden deletion.
- Reserve 40% of declared available capacity unscheduled. Compute availability from configured working windows minus opaque busy time; reserve is applied to that remaining capacity before new blocks. No working windows or incomplete busy data means unknown capacity.
- Explicit review after two confirmed deferrals of the same item since its last review. External differences do not count as deliberate deferrals without evidence. The prompt can offer smaller scope, queue/someday, retain or cancel; it does not decide automatically.

Policy exceptions are explicit, scoped and recorded with reason if supplied. No guilt-driven messaging, inferred completion, silent deletion or automatic idea promotion. Life-area categories are not diagnoses or rankings of human worth.

2. Domain Entities Specification (docs/technical/domain-model.md)

Section: ## Entities (lines 7–18):

| Entity | Meaning and authoritative home |
|---|---|
| Plan | SQLite proposal artifact: intended outcomes, capacity observations and proposed allocations |
| Proposal | Immutable SQLite review artifact: baseline versions, ordered steps, reasons, expiry and review digest |

A plan becomes neither a commitment nor actual effort until the relevant approved writes or explicit outcome reports occur. Recommendations cannot write. Context and timeline schemas are owned by Hindsight and Timeline consistency.

3. Calendar Semantics & Event Transparency (docs/technical/calendar.md)

Section: ## Query and time semantics (lines 24–27):

- Transparent VEVENTs appear in agenda but do not consume busy capacity. Cancelled occurrences do not consume capacity. Opaque tentative events conservatively count busy unless policy explicitly says otherwise. Missing or unsupported semantics prevent declaring the affected window free.

Schedule answers always perform current backend reads, return as_of/per-collection observation times and completeness. CalDAV has no global snapshot: concurrent changes can produce an explicitly incomplete observation despite successful individual requests. Memory is never fallback authority; a failed calendar read is not an empty agenda.

4. CLI and API Route & Payloads (docs/technical/cli-api.md)

Section: ## Command-to-route table (line 42):

| `life plan --period today|week --input -` | POST /v1/plans | write: stores proposal only |

Section: ## Command payloads and results (lines 89, 104):

| Plan | period; optional intended_outcomes,capacity_override,policy_exceptions | proposal, live input coverage, unallocated outcomes and warnings |

A proposal step contains step_id, action, item_id for updates or preallocated create identity, collection_id, baseline_etag (null for create), payload and reason. Digest covers ordered steps and policy exceptions. Default expiry 24 hours; expiry never bypasses baseline checks. Period week means Monday local midnight through next Monday; anchor_date selects the containing week. Agenda defaults require an explicit period; task lists have no implicit deadline cutoff.

Section: ## Error identifiers (lines 110–127):

Non-error warning identifiers: incomplete_calendar, concurrent_change, candidate_narrowed, context_unavailable, context_stale, history_incomplete, unknown_estimate, policy_review_required.

5. Database Schema (docs/technical/timeline-consistency.md)

Section: ## Illustrative schema (lines 63–75):

CREATE TABLE proposals (
  proposal_id TEXT PRIMARY KEY,
  schema_version INTEGER NOT NULL,
  state TEXT NOT NULL CHECK(state IN
    ('pending','applied','rejected','expired','partial')),
  digest TEXT NOT NULL,
  created_at TEXT NOT NULL,
  expires_at TEXT NOT NULL,
  steps_json TEXT NOT NULL,
  policy_json TEXT NOT NULL,
  operation_id TEXT REFERENCES operations(operation_id),
  approval_json TEXT
);

6. Verification Scenario W06 (docs/technical/verification.md)

Section: ## Workflow scenarios (line 20):

| ID | Requirement and specification | Input / action | Observable acceptance |
|---|---|---|---|
| W06 | Daily/weekly planning (workflows.md#planning-and-interruption) | Known work windows, live busy events, estimates and intended outcomes | Proposal respects reserve/outcome constraints; generation makes no calendar writes; exceptions require review |

What to build

1. Working Window & Capacity Calculator (src/domain/policy/capacity.rs)

  • Implement WorkingWindow resolution per weekday from configuration (e.g., Monday 09:00–17:00 in configured IANA timezone).
  • Fetch live VEVENT occurrences intersecting the planning window from Radicale.
  • Filter event occupancy:
    • If STATUS: CANCELLED -> ignores event (0 minutes consumed).
    • If TRANSP: TRANSPARENT -> ignores event for busy capacity (0 minutes consumed).
    • If TRANSP: OPAQUE (or missing transparency default) -> consumes interval between start and end. If STATUS: TENTATIVE, treat conservatively as busy.
  • Compute capacity metrics:
    \text{Gross Window} = \text{Window End} - \text{Window Start}
    \text{Declared Available Capacity} = \max(0, \text{Gross Window} - \text{Opaque Busy Duration})
    \text{Reserve Duration} = 0.40 \times \text{Declared Available Capacity}
    \text{Allocatable Work Capacity} = \text{Declared Available Capacity} - \text{Reserve Duration} = 0.60 \times \text{Declared Available Capacity}
  • If working windows are undefined or calendar response returns complete: false, set allocatable capacity to 0, emit incomplete_calendar warning, and do not attempt automatic free-time allocation.

2. Planning Policy Validator (src/domain/policy/planning.rs)

  • Check intended outcomes count:
    • Daily limit: maximum 3 intended outcomes.
    • If > 3, reject proposal generation unless an explicit policy_exceptions entry with code exceed_daily_outcomes_cap is supplied in the request (reason is optional and recorded if supplied, per docs/technical/workflows.md:50).
  • Check active discretionary projects:
    • Verify that tasks selected for planning belong to at most one active discretionary project.
    • If more than one active discretionary project is targeted, require explicit policy_exceptions with code multiple_active_projects.
  • Check task estimates:
    • If an intended task lacks estimated_minutes (X-LIFE-ESTIMATED-MINUTES), emit unknown_estimate warning. Do not fabricate an estimate. Such tasks may either remain unallocated or be allocated an explicit default block only when permitted by caller policy overrides.
  • Check task deferrals (Scenario W10):
    • If an intended task has two confirmed deferrals since its last review, emit a review recommendation prompting smaller scope, queue/someday, retain, or cancel; never decide or reschedule automatically (docs/technical/workflows.md:52).

3. Step Builder & Canonical Digest Generator (src/domain/proposal.rs)

  • Generate ordered mutation steps:
    • step_id: Stable alphanumeric identifier within the proposal (step_1, step_2, ...).
    • action: create_event (for scheduled work blocks) or update_task (to set planned_start or status).
    • item_id: For updates, the existing opaque item ID; for creates, a newly generated UUID prefixed with evt_.
    • collection_id: The target calendar or task collection.
    • baseline_etag: The live ETag retrieved during pre-planning read (must be null for create_event).
    • payload: Normalized JSON representing the component properties to create or patch.
    • reason: Explanation string (e.g., "Schedule 90m focus block for Task XYZ within allocatable window").
  • Compute Canonical SHA-256 Digest:
    • Construct a canonical representation containing schema_version, sorted/ordered steps, policy_exceptions, and baseline_etags.
    • Calculate digest = "sha256:" + hex(sha256(canonical_bytes)).
    • Set expires_at = created_at + 24 hours (default TTL).

4. SQLite Proposal Repository (src/storage/proposal_repo.rs)

  • Store proposal record in proposals table:
    • proposal_id: Generated ID (prop_...).
    • schema_version: 1.
    • state: 'pending'.
    • digest: Computed SHA-256 digest string.
    • created_at: Current UTC ISO-8601 timestamp.
    • expires_at: ISO-8601 timestamp 24 hours in the future.
    • steps_json: Serialized JSON array of steps.
    • policy_json: Serialized JSON of applied policy rules, capacity calculations, and exceptions.
    • operation_id: Null until application attempt.
    • approval_json: Null.
  • Ensure ZERO writes are made to Radicale CalDAV storage during proposal creation.

5. Axum Handler & CLI Subcommand (src/api/plans.rs, src/cli/plan.rs)

  • Implement POST /v1/plans:
    • Request body:
      {
        "period": "today",
        "intended_outcomes": ["itm_task1", "itm_task2"],
        "capacity_override": null,
        "policy_exceptions": []
      }
      
    • Response (HTTP 201 Created):
      {
        "schema_version": 1,
        "request_id": "req_...",
        "status": "ok",
        "data": {
          "proposal": {
            "proposal_id": "prop_01HN8...",
            "digest": "sha256:8bd4e98ba7204612c8459ad1e38e25c2642c181a234c4fd617a3675f8d720c11",
            "state": "pending",
            "created_at": "2026-09-23T08:00:00Z",
            "expires_at": "2026-09-24T08:00:00Z",
            "steps": [
              {
                "step_id": "step_1",
                "action": "create_event",
                "item_id": "evt_new_workblock_1",
                "collection_id": "personal",
                "baseline_etag": null,
                "payload": {
                  "title": "Work on Issue #025",
                  "start": {"kind": "instant", "value": "2026-09-23T09:00:00Z"},
                  "end": {"kind": "instant", "value": "2026-09-23T10:30:00Z"},
                  "related_task_id": "itm_task1"
                },
                "reason": "Allocated 90m block within 60% allocatable capacity"
              }
            ]
          },
          "coverage": [{"collection_id": "personal", "state": "complete", "as_of": "2026-09-23T08:00:00Z"}],
          "unallocated_outcomes": []
        },
        "warnings": [],
        "error": null
      }
      
  • Implement CLI command:
    life plan --period today|week [--input -] [--json]
    • Exit code 0 on successful proposal generation.

Acceptance Criteria

  • Running life plan --period today reads live events and computes Gross Window, subtracts opaque busy events, and reserves exactly 40% of the remaining capacity as unscheduled buffer time (verifying Scenario W06).
  • Transparent events (TRANSP:TRANSPARENT) and cancelled events (STATUS:CANCELLED) are verified not to consume busy capacity.
  • Incomplete calendar observation (complete: false) returns incomplete_calendar warning and refuses confident free-time allocation.
  • Attempting to plan 4 intended outcomes in one day without an explicit policy exception fails with policy warning or requires review.
  • Attempting to plan tasks spanning more than 1 active discretionary project requires an explicit policy exception.
  • Proposal generation creates an entry in SQLite proposals with state: "pending", a valid SHA-256 digest, 24-hour expiration, and performs ZERO writes to Radicale.
  • Output conforms to standard CLI JSON envelope with non-empty proposal_id and digest.
  • Policy exception reasons are optional and recorded if supplied (docs/technical/workflows.md:50).
  • A task with two confirmed deferrals triggers an explicit review prompt offering smaller scope, queue/someday, retain, or cancel, without automatic rescheduling (Scenario W10).

Notes

  • The 40% reserve is ceil(remaining × unscheduled_fraction) where remaining = gross − busy; the test explicitly asserts reserved != 192 (i.e. not 40% of gross).
  • Unknown capacity and incomplete coverage report null capacity fields (never 0) with zero steps.
  • Proposal digest is canonicalized (sorted-key) SHA-256 over the ordered steps + baselines.
  • live_task_inventory is now shared with the #24 next command; the CLI unimplemented-command fixture moved from plan to replan.
  • No dependency added.

Blocked by

  • #015: Scoped Recurring Mutations (Series vs Occurrence)
  • #024: Next Action CLI & Advisory Provenance
## Summary Implement the planning proposal engine (`POST /v1/plans` and `life plan --period today|week`), calculating working window occupancy, enforcing the 40% unscheduled capacity reserve, applying personal policy constraints (max 3 intended outcomes per day, 1 active discretionary project), compiling ordered atomic proposal mutation steps, calculating canonical SHA-256 proposal digests, and storing proposals in SQLite without performing any writes to the Radicale CalDAV server. ## Background & Technical Rationale Calendar management systems frequently suffer from over-allocation: users schedule every available minute, leaving zero buffer for unexpected interruptions, overrun meetings, or cognitive fatigue. Furthermore, automated scheduling engines often make destructive or unexpected changes directly to remote calendar stores without human oversight. Life resolves these challenges by introducing a two-stage planning model rooted in strict mathematical reserves and human-in-the-loop review: 1. **Mathematical Capacity Buffer (The 40% Rule):** Rather than scheduling up to 100% of open calendar space, Life calculates declared available capacity (configured working window minus opaque busy time) and reserves exactly 40% of that available duration as unscheduled buffer time. 2. **Cognitive Load Caps:** The engine enforces hard policy defaults: at most three intended outcomes per day, and at most one active discretionary project. Violations require explicit review and recorded policy exceptions. 3. **Observation Completeness:** The engine never guesses availability. If calendar synchronization is incomplete, ongoing, or impaired (`complete: false`), the engine refuses confident free-time allocation, surfaces `incomplete_calendar` warnings, and marks remaining capacity as unknown. 4. **Decoupled Proposal Persistence (Zero Remote Writes):** Generating a plan produces an immutable SQLite `proposals` artifact with an unforgeable cryptographic digest (SHA-256). Under no circumstances does proposal generation mutate Radicale calendar components (`VEVENT`) or task components (`VTODO`). Remote mutations occur only when the user explicitly reviews and executes the proposal via a separate approval workflow. ## Exact Technical References & Specifications ### 1. Workflows Specification (`docs/technical/workflows.md`) Section: `## Planning and interruption` (lines 24–26): ```markdown | Workflow | Input and reads | Decision and approval | Writes and observable result | Failure path | |---|---|---|---|---| | Daily/weekly planning | Period, capacity preferences and intended outcomes; live events/tasks, explicit estimates and policy/context | Rust computes occupancy, deadlines and reserve; advisory Jev may rank eligible work; user reviews exact proposal | Generation stores a proposal; approved apply creates/updates blocks in deterministic order | Incomplete calendar forbids confident free-time allocation; missing estimates require clarification or clearly unallocated outcomes | ``` Section: `## Personal policy defaults` (lines 42–50): ```markdown Proposed, configurable and user-changeable; never behavioral diagnoses: - One meaningful career action per weekday. Success requires an explicit qualifying outcome linked to the action and day; allocating a calendar block is not success. Qualification must be user-approved or satisfy a reviewed deterministic rubric, never a model's unreviewed inference. - At most three intended outcomes per day. This limits planned outcomes, not incoming obligations or factual agenda results. - One active discretionary project. Necessary obligations remain visible; activating another requires an explicit policy exception or reprioritization, not hidden deletion. - Reserve 40% of declared available capacity unscheduled. Compute availability from configured working windows minus opaque busy time; reserve is applied to that remaining capacity before new blocks. No working windows or incomplete busy data means unknown capacity. - Explicit review after two confirmed deferrals of the same item since its last review. External differences do not count as deliberate deferrals without evidence. The prompt can offer smaller scope, queue/someday, retain or cancel; it does not decide automatically. Policy exceptions are explicit, scoped and recorded with reason if supplied. No guilt-driven messaging, inferred completion, silent deletion or automatic idea promotion. Life-area categories are not diagnoses or rankings of human worth. ``` ### 2. Domain Entities Specification (`docs/technical/domain-model.md`) Section: `## Entities` (lines 7–18): ```markdown | Entity | Meaning and authoritative home | |---|---| | Plan | SQLite proposal artifact: intended outcomes, capacity observations and proposed allocations | | Proposal | Immutable SQLite review artifact: baseline versions, ordered steps, reasons, expiry and review digest | A plan becomes neither a commitment nor actual effort until the relevant approved writes or explicit outcome reports occur. Recommendations cannot write. Context and timeline schemas are owned by Hindsight and Timeline consistency. ``` ### 3. Calendar Semantics & Event Transparency (`docs/technical/calendar.md`) Section: `## Query and time semantics` (lines 24–27): ```markdown - Transparent VEVENTs appear in agenda but do not consume busy capacity. Cancelled occurrences do not consume capacity. Opaque tentative events conservatively count busy unless policy explicitly says otherwise. Missing or unsupported semantics prevent declaring the affected window free. Schedule answers always perform current backend reads, return as_of/per-collection observation times and completeness. CalDAV has no global snapshot: concurrent changes can produce an explicitly incomplete observation despite successful individual requests. Memory is never fallback authority; a failed calendar read is not an empty agenda. ``` ### 4. CLI and API Route & Payloads (`docs/technical/cli-api.md`) Section: `## Command-to-route table` (line 42): ```markdown | `life plan --period today|week --input -` | POST /v1/plans | write: stores proposal only | ``` Section: `## Command payloads and results` (lines 89, 104): ```markdown | Plan | period; optional intended_outcomes,capacity_override,policy_exceptions | proposal, live input coverage, unallocated outcomes and warnings | A proposal step contains step_id, action, item_id for updates or preallocated create identity, collection_id, baseline_etag (null for create), payload and reason. Digest covers ordered steps and policy exceptions. Default expiry 24 hours; expiry never bypasses baseline checks. Period week means Monday local midnight through next Monday; anchor_date selects the containing week. Agenda defaults require an explicit period; task lists have no implicit deadline cutoff. ``` Section: `## Error identifiers` (lines 110–127): ```markdown Non-error warning identifiers: incomplete_calendar, concurrent_change, candidate_narrowed, context_unavailable, context_stale, history_incomplete, unknown_estimate, policy_review_required. ``` ### 5. Database Schema (`docs/technical/timeline-consistency.md`) Section: `## Illustrative schema` (lines 63–75): ```sql CREATE TABLE proposals ( proposal_id TEXT PRIMARY KEY, schema_version INTEGER NOT NULL, state TEXT NOT NULL CHECK(state IN ('pending','applied','rejected','expired','partial')), digest TEXT NOT NULL, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, steps_json TEXT NOT NULL, policy_json TEXT NOT NULL, operation_id TEXT REFERENCES operations(operation_id), approval_json TEXT ); ``` ### 6. Verification Scenario W06 (`docs/technical/verification.md`) Section: `## Workflow scenarios` (line 20): ```markdown | ID | Requirement and specification | Input / action | Observable acceptance | |---|---|---|---| | W06 | Daily/weekly planning (workflows.md#planning-and-interruption) | Known work windows, live busy events, estimates and intended outcomes | Proposal respects reserve/outcome constraints; generation makes no calendar writes; exceptions require review | ``` ## What to build ### 1. Working Window & Capacity Calculator (`src/domain/policy/capacity.rs`) - Implement `WorkingWindow` resolution per weekday from configuration (e.g., Monday 09:00–17:00 in configured IANA timezone). - Fetch live `VEVENT` occurrences intersecting the planning window from Radicale. - Filter event occupancy: - If `STATUS: CANCELLED` -> ignores event (0 minutes consumed). - If `TRANSP: TRANSPARENT` -> ignores event for busy capacity (0 minutes consumed). - If `TRANSP: OPAQUE` (or missing transparency default) -> consumes interval between start and end. If `STATUS: TENTATIVE`, treat conservatively as busy. - Compute capacity metrics: $$\text{Gross Window} = \text{Window End} - \text{Window Start}$$ $$\text{Declared Available Capacity} = \max(0, \text{Gross Window} - \text{Opaque Busy Duration})$$ $$\text{Reserve Duration} = 0.40 \times \text{Declared Available Capacity}$$ $$\text{Allocatable Work Capacity} = \text{Declared Available Capacity} - \text{Reserve Duration} = 0.60 \times \text{Declared Available Capacity}$$ - If working windows are undefined or calendar response returns `complete: false`, set allocatable capacity to 0, emit `incomplete_calendar` warning, and do not attempt automatic free-time allocation. ### 2. Planning Policy Validator (`src/domain/policy/planning.rs`) - Check intended outcomes count: - Daily limit: maximum 3 intended outcomes. - If $> 3$, reject proposal generation unless an explicit `policy_exceptions` entry with code `exceed_daily_outcomes_cap` is supplied in the request (reason is optional and recorded if supplied, per `docs/technical/workflows.md:50`). - Check active discretionary projects: - Verify that tasks selected for planning belong to at most one active discretionary project. - If more than one active discretionary project is targeted, require explicit `policy_exceptions` with code `multiple_active_projects`. - Check task estimates: - If an intended task lacks `estimated_minutes` (`X-LIFE-ESTIMATED-MINUTES`), emit `unknown_estimate` warning. Do not fabricate an estimate. Such tasks may either remain unallocated or be allocated an explicit default block only when permitted by caller policy overrides. - Check task deferrals (Scenario W10): - If an intended task has two confirmed deferrals since its last review, emit a review recommendation prompting smaller scope, queue/someday, retain, or cancel; never decide or reschedule automatically (`docs/technical/workflows.md:52`). ### 3. Step Builder & Canonical Digest Generator (`src/domain/proposal.rs`) - Generate ordered mutation steps: - `step_id`: Stable alphanumeric identifier within the proposal (`step_1`, `step_2`, ...). - `action`: `create_event` (for scheduled work blocks) or `update_task` (to set `planned_start` or status). - `item_id`: For updates, the existing opaque item ID; for creates, a newly generated UUID prefixed with `evt_`. - `collection_id`: The target calendar or task collection. - `baseline_etag`: The live ETag retrieved during pre-planning read (must be null for `create_event`). - `payload`: Normalized JSON representing the component properties to create or patch. - `reason`: Explanation string (e.g., "Schedule 90m focus block for Task XYZ within allocatable window"). - Compute Canonical SHA-256 Digest: - Construct a canonical representation containing `schema_version`, sorted/ordered `steps`, `policy_exceptions`, and `baseline_etags`. - Calculate `digest = "sha256:" + hex(sha256(canonical_bytes))`. - Set `expires_at = created_at + 24 hours` (default TTL). ### 4. SQLite Proposal Repository (`src/storage/proposal_repo.rs`) - Store proposal record in `proposals` table: - `proposal_id`: Generated ID (`prop_...`). - `schema_version`: `1`. - `state`: `'pending'`. - `digest`: Computed SHA-256 digest string. - `created_at`: Current UTC ISO-8601 timestamp. - `expires_at`: ISO-8601 timestamp 24 hours in the future. - `steps_json`: Serialized JSON array of steps. - `policy_json`: Serialized JSON of applied policy rules, capacity calculations, and exceptions. - `operation_id`: Null until application attempt. - `approval_json`: Null. - Ensure ZERO writes are made to Radicale CalDAV storage during proposal creation. ### 5. Axum Handler & CLI Subcommand (`src/api/plans.rs`, `src/cli/plan.rs`) - Implement `POST /v1/plans`: - Request body: ```json { "period": "today", "intended_outcomes": ["itm_task1", "itm_task2"], "capacity_override": null, "policy_exceptions": [] } ``` - Response (HTTP 201 Created): ```json { "schema_version": 1, "request_id": "req_...", "status": "ok", "data": { "proposal": { "proposal_id": "prop_01HN8...", "digest": "sha256:8bd4e98ba7204612c8459ad1e38e25c2642c181a234c4fd617a3675f8d720c11", "state": "pending", "created_at": "2026-09-23T08:00:00Z", "expires_at": "2026-09-24T08:00:00Z", "steps": [ { "step_id": "step_1", "action": "create_event", "item_id": "evt_new_workblock_1", "collection_id": "personal", "baseline_etag": null, "payload": { "title": "Work on Issue #025", "start": {"kind": "instant", "value": "2026-09-23T09:00:00Z"}, "end": {"kind": "instant", "value": "2026-09-23T10:30:00Z"}, "related_task_id": "itm_task1" }, "reason": "Allocated 90m block within 60% allocatable capacity" } ] }, "coverage": [{"collection_id": "personal", "state": "complete", "as_of": "2026-09-23T08:00:00Z"}], "unallocated_outcomes": [] }, "warnings": [], "error": null } ``` - Implement CLI command: `life plan --period today|week [--input -] [--json]` - Exit code 0 on successful proposal generation. ## Acceptance Criteria - [x] Running `life plan --period today` reads live events and computes Gross Window, subtracts opaque busy events, and reserves exactly 40% of the remaining capacity as unscheduled buffer time (verifying Scenario W06). - [x] Transparent events (`TRANSP:TRANSPARENT`) and cancelled events (`STATUS:CANCELLED`) are verified not to consume busy capacity. - [x] Incomplete calendar observation (`complete: false`) returns `incomplete_calendar` warning and refuses confident free-time allocation. - [x] Attempting to plan 4 intended outcomes in one day without an explicit policy exception fails with policy warning or requires review. - [x] Attempting to plan tasks spanning more than 1 active discretionary project requires an explicit policy exception. - [x] Proposal generation creates an entry in SQLite `proposals` with `state: "pending"`, a valid SHA-256 digest, 24-hour expiration, and performs ZERO writes to Radicale. - [x] Output conforms to standard CLI JSON envelope with non-empty `proposal_id` and `digest`. - [x] Policy exception reasons are optional and recorded if supplied (`docs/technical/workflows.md:50`). - [x] A task with two confirmed deferrals triggers an explicit review prompt offering smaller scope, queue/someday, retain, or cancel, without automatic rescheduling (Scenario W10). ## Notes - The 40% reserve is `ceil(remaining × unscheduled_fraction)` where remaining = gross − busy; the test explicitly asserts `reserved != 192` (i.e. not 40% of gross). - Unknown capacity and incomplete coverage report **null** capacity fields (never 0) with zero steps. - Proposal digest is canonicalized (sorted-key) SHA-256 over the ordered steps + baselines. - `live_task_inventory` is now shared with the `#24` next command; the CLI unimplemented-command fixture moved from `plan` to `replan`. - No dependency added. ## Blocked by - #015: Scoped Recurring Mutations (Series vs Occurrence) - #024: Next Action CLI & Advisory Provenance
yuris changed title from Issue #025: Planning Proposal Engine & Capacity Reserve Calculations to Planning Proposal Engine & Capacity Reserve Calculations 2026-09-22 20:02:35 +00:00
yuris closed this issue 2026-09-23 01:37:09 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/life-service#25
No description provided.