Portfolio Engineering Environment — Reusable Skill
Overview
This skill covers interacting with a shared portfolio-management REST API to produce:
- Portfolio mix reviews — classify closed work items into investment categories, compare actual mix against targets, identify gaps, and recommend rebalancing actions.
- SLA aging audits — identify primary SLA-eligible work, detect overdue items, compute aging distributions, surface owner/team hotspots, flag duplicate clusters, and calculate breach rates.
- Release readiness assessments — evaluate milestone completion, identify gating work items, count unresolved high-impact blockers, trace critical dependency chains, and compute readiness scores.
Environment Connection
Base URL
All API calls go to the base URL supplied in the task's <TASK_ENV_BASE_URL> placeholder (provided at runtime by environment_access.md). That file supplies:
base_url: e.g. http://task-env:9024/
credentials:
api_query_header: X-Env-Token
api_query_token: <token-value>
allowed_endpoints:
- GET /api/work-items
- GET /api/work-items/{item_id}
- GET /api/mix-targets
- GET /api/sla-policy
- GET /api/releases
- GET /api/releases/{release_id}
- GET /api/milestones
- GET /api/dependencies
- GET /api/blockers
- POST /api/query
Authentication
The only endpoint requiring authentication is POST /api/query. Send the header:
X-Env-Token: <api_query_token>
All GET endpoints are unauthenticated.
API Reference
GET /api/work-items
Returns all work items with pagination metadata.
Response shape:
{
"count": <integer>,
"work_items": [ <work_item_object>, ... ]
}
GET /api/work-items/{item_id}
Returns a single work item.
Response shape:
{
"work_item": { <work_item_object> }
}
Work Item Object Fields
| Field |
Type |
Description |
id |
string |
Unique identifier, e.g. WI-24024-P001 |
title |
string |
Human-readable title |
work_type |
string |
One of: Feature, Refactor, Incident, Security, Reliability, Compliance, Bug, Dependency, Chore, Enhancement |
status |
string |
One of: Closed, Done, Deployed, Verified, Review, In Progress, Backlog, Duplicate, Cancelled, Reopened |
team |
string |
Owning engineering team |
owner |
string or null |
Person assigned; null means unassigned |
product_area |
string |
Product area this work belongs to |
created_at |
string (date) |
Creation date, format YYYY-MM-DD |
due_at |
string (date) |
SLA due date, format YYYY-MM-DD |
closed_at |
string (date) or null |
Close date, format YYYY-MM-DD; null if still open |
severity |
string |
S1, S2, S3, or S4 |
priority |
integer |
1 (highest) to 5 (lowest) |
labels |
array of strings |
Free-form tags, e.g. ["security","cve","rollout"] |
story_points |
integer |
Effort estimate |
release_id |
string or null |
Release this item belongs to |
milestone_id |
string or null |
Milestone this item belongs to |
duplicate_of |
string or null |
If status is Duplicate, points to the canonical/primary work item id |
mirror_status |
string |
STALE FIELD — do not use as source of truth. Use status instead. |
legacy_category |
string |
STALE FIELD — do not use for portfolio classification. Use authoritative signals instead. |
GET /api/mix-targets
Returns all portfolio mix target rows.
Response shape:
{
"mix_targets": [
{
"scope_id": "train_001",
"quarter": "2025-Q4",
"team_group": "Platform Core + Identity Services",
"product_area": "Atlas Backend + Identity",
"new_feature_pct": 0.34,
"tech_debt_pct": 0.24,
"reliability_pct": 0.22,
"security_pct": 0.20
}
]
}
Target percentages are expressed as decimals (0.34 = 34%). When presenting results, convert to percentage points (multiply by 100) and round to 1 decimal place.
GET /api/sla-policy
Returns SLA due-date rules keyed by severity.
Response shape:
{
"sla_policy": [
{ "severity": "S1", "days_to_due": 3 },
{ "severity": "S2", "days_to_due": 10 },
{ "severity": "S3", "days_to_due": 21 },
{ "severity": "S4", "days_to_due": 45 }
]
}
An item is overdue when as_of_date - created_at > days_to_due for its severity AND the item is not closed (or was closed after the as-of date, or was closed recently within the recent_closed_window_days window — see SLA methodology below).
GET /api/releases
Returns all releases.
Response shape:
{
"releases": [
{
"id": "REL-ORION-2026-02",
"name": "Orion February portfolio train",
"target_date": "2026-02-20",
"train": "Orion"
}
]
}
GET /api/releases/{release_id}
Returns a single release with its embedded milestones and blockers.
Response shape:
{
"release": { <release_object> },
"milestones": [ <milestone_object>, ... ],
"blockers": [ <blocker_object>, ... ]
}
GET /api/milestones
Returns all milestones across all releases.
Response shape:
{
"milestones": [
{
"id": "MIL-ORION-BETA",
"name": "Orion beta freeze",
"owner_team": "Release Engineering",
"release_id": "REL-ORION-2026-02"
}
]
}
GET /api/dependencies
Returns all dependency edges between work items.
Response shape:
{
"dependencies": [
{
"blocked_id": "WI-24024-010",
"depends_on_id": "WI-24024-009",
"relation": "blocks-release-readiness"
}
]
}
Relation types include: depends-on, blocks-release-readiness, security-review-required, validation-required, implementation-dependency, audit-evidence-required.
GET /api/blockers
Returns all blocker records.
Response shape:
{
"blockers": [
{
"id": "BLK-24024-001",
"work_item_id": "WI-24024-010",
"release_id": "REL-ORION-2026-02",
"cause": "open reliability rehearsal gap",
"severity": "High",
"status": "Open",
"opened_at": "2026-02-06",
"resolved_at": null
}
]
}
Blocker severities: Critical, High, Medium, Low.
Blocker statuses: Open, Monitoring, Resolved.
POST /api/query
Run SQL queries against the work items data. Requires the auth header.
Request:
{
"sql": "SELECT id, status, team FROM work_items WHERE team = 'AppSec'"
}
Response shape:
{
"columns": ["id", "status", "team"],
"rows": [ [...], ... ],
"row_count": <integer>,
"truncated": false
}
The table name is work_items. Available columns match the work item object fields listed above. Use this endpoint for filtered queries that would be inefficient via the GET list endpoint.
Querying tip: The JSON label array is stored as a JSON string in the SQL backend. Use LIKE '%"security"%' patterns to filter by label value.
Portfolio Category Classification
Every included work item must be classified into exactly one of four portfolio categories:
| Category |
Code |
| New Feature |
NewFeature |
| Technical Debt |
TechDebt |
| Reliability |
Reliability |
| Security |
Security |
Signal Sources (in priority order)
- Labels array — the strongest signal. Each label token maps to a category convention.
- Work type — the second signal. Used as default when labels are ambiguous or silent.
- Title — can disambiguate when labels and work_type conflict (e.g., a title noting a label is "stale").
Classification Methodology
When classifying a work item:
- Extract all signals from labels, work_type, and title.
- Resolve conflicts using the portfolio category conventions. When multiple categories are signaled by labels, the one with the strongest signal wins. When labels give no clear category signal, fall back to work_type. When work_type is also ambiguous, inspect the title.
- Every item gets exactly one category. No item is double-counted or left unclassified.
- Verify your counts against the target mix. If the target says 34% NewFeature and you have 0%, re-examine your classification of items whose work_type is
Feature or Enhancement but whose labels push them elsewhere.
Common label-to-category associations visible in the training data:
- Labels containing
security or cve → Security
- Labels containing
reliability, incident, outage, or latency → Reliability
- Labels containing
refactor, cleanup, or migration → TechDebt
- Labels containing
feature → NewFeature (when no higher-priority signal exists)
- Labels like
auth, encryption, rollout, flaky appear across categories — resolve by priority and adjacent signals.
Task Type 1: Portfolio Mix Review
Purpose
Compare the count-based distribution of closed work items across the four portfolio categories against a target mix, identify under-invested categories, and recommend a follow-up action.
Step-by-Step Method
Fetch the mix target. Query /api/mix-targets and locate the row whose scope_id matches the task's scope. Convert the decimal target percentages to percentage points (multiply by 100, round to 1 decimal).
Identify in-scope work items. Use /api/work-items or POST /api/query to find items matching the scope's teams AND product areas. Filter to the correct quarter (closed_at within the quarter).
Separate primary from excluded records:
- Duplicates: Items with
status = "Duplicate" and a non-null duplicate_of field. Exclude from the portfolio count. Track their ids.
- Cancelled: Items with
status = "Cancelled". Exclude from the portfolio count. Track their ids.
- Distractors: Items that match scope on teams/product_area/quarter but have a
duplicate_of pointing to an item in a different scope, or items whose mirror_status contradicts status. Exclude these and track their ids.
Classify each included item into one of the four categories using the classification methodology above.
Compute counts and percentages:
category_counts: integer count per category.
category_percentages: (count / total_included) * 100, rounded to 1 decimal place.
Build the gap table. For each category (ordered: NewFeature, TechDebt, Reliability, Security):
target_pct: from the mix target row (as percentage points, 1 decimal).
actual_pct: from step 5.
gap_pct: actual_pct - target_pct, rounded to 1 decimal.
Identify under-invested categories. Categories where gap_pct < 0. Sort from most negative to least negative gap.
Determine the follow-up action:
- If any under-invested categories exist →
REBALANCE_CAPACITY, primary = category with largest negative gap, secondary = next largest, rationale = LARGEST_NEGATIVE_GAP.
- If no under-invested categories →
MAINTAIN_CURRENT_MIX, primary and secondary = null, rationale = NO_NEGATIVE_GAPS.
- If data conflicts are found (e.g., mirror_status contradicting status across multiple records) →
INVESTIGATE_DATA_QUALITY, rationale = DATA_CONFLICT.
Populate exclusion flags:
excluded_duplicate_ids: sorted list of duplicate record ids.
excluded_cancelled_ids: sorted list of cancelled record ids.
ignored_mirror_status_and_legacy_category: always true.
Verify. Confirm sum(category_counts) == total_included. Confirm percentages sum to approximately 100.0% (may be 99.9 or 100.1 due to rounding). Confirm gap_pct values: sum(gap_pct) should be approximately 0.0.
Ordering Conventions
- Work item IDs in
included_work_item_ids: sort by closed_at ascending, then by id ascending.
- Exclusion IDs: sort by
closed_at ascending, then by id ascending.
- Teams and product areas in scope: sort alphabetically.
- Gap table rows: NewFeature, TechDebt, Reliability, Security (fixed order).
- Under-invested categories: most negative gap first.
Task Type 2: SLA Aging Audit
Purpose
Audit SLA compliance for reliability and security work items, identifying overdue items, aging distributions, team/owner hotspots, duplicate clusters, and breach rates.
Step-by-Step Method
Fetch SLA policy. GET /api/sla-policy — maps severity to days_to_due.
Identify the SLA-eligible population. Query work items where:
team matches the scope's teams.
- The item belongs to the SLA-relevant categories (e.g., Reliability, Security). Use the portfolio category classification to determine category.
- Exclude items with
status = "Duplicate" or status = "Cancelled".
Separate primary from duplicate records:
- Primary: items where
duplicate_of is null (or status is not Duplicate).
- Duplicate clusters: group items where
duplicate_of points to a primary id, or where status = "Duplicate". Each cluster has one primary_id (the canonical item) and a list of duplicate_ids.
- The
included_primary_ids list contains only primary records.
Determine overdue status. For each primary item:
- Compute age:
as_of_date - created_at in days.
- Look up
days_to_due from SLA policy by the item's severity.
- An item is overdue if
age > days_to_due AND the item is not already closed. However, items closed within the recent_closed_window_days of the as-of date should be treated as still open for SLA purposes (they were recently resolved and count toward the active SLA population). Items closed before the recent window are considered closed and not overdue.
- Key distinction: Use
status (not mirror_status) to determine if an item is closed. A closed/completed status (Closed, Done, Deployed, Verified) with a closed_at date before as_of_date - recent_closed_window_days means the item is genuinely closed and not overdue.
Populate overdue_primary_ids. Subset of included_primary_ids that are overdue. Sort lexicographically.
Compute aging buckets. For each included primary item, compute age in days (from created_at to as_of_date). Count into buckets: 0-3, 4-7, 8-14, 15-30, 31+. Include ALL primary items, not just overdue ones.
Compute team overdue counts. For each team (alphabetical order), count overdue primary items assigned to that team.
Find the top hotspot. The (team, owner) pair with the most overdue primary items. When owner is null/absent, use UNASSIGNED. If multiple pairs tie, pick the first alphabetically by team, then by owner.
Identify duplicate clusters. Group duplicate records by their primary_id (the duplicate_of value). Each cluster has a primary_id and sorted duplicate_ids. Sort clusters by primary_id.
Identify missing-owner items. Primary included items where owner is null. Sort ids lexicographically.
Calculate breach rate. overdue_primary_ids.length / included_primary_ids.length. Round to exactly 3 decimal places.
Ordering Conventions
- All ID lists: sorted lexicographically (ascending).
- Teams in
team_overdue_counts: sorted alphabetically.
- Duplicate clusters: sorted by
primary_id. Within each cluster, duplicate_ids sorted lexicographically.
breach_rate: 3 decimal places.
Escalation Queue (when required)
When the answer template includes an escalation queue, order overdue primary items by:
- Severity (S1 first, then S2, S3, S4).
- Within the same severity, by age descending (oldest first).
- Within same age, by id ascending.
Task Type 3: Release Readiness Assessment
Purpose
Evaluate whether a release is ready to ship by assessing milestone completion, gating work items, blocker counts, and dependency chains.
Step-by-Step Method
Fetch release data. GET /api/releases/{release_id} returns the release object, its milestones array, and its blockers array.
Identify release work items. Query work items where release_id matches the target release. These are the primary work items for the release.
For each milestone, compute completion:
primary_total: count of primary release work items assigned to this milestone (milestone_id matches).
complete_primary: subset of those where status indicates completion. Completed statuses are: Closed, Done, Deployed, Verified. Items with status Duplicate, Cancelled, In Progress, Backlog, Review, Reopened are NOT complete.
completion_pct: (complete_primary / primary_total) * 100, rounded to 1 decimal place. If primary_total == 0, completion_pct = 0.0.
- Sort
milestone_completion by milestone_id ascending.
Identify gating work items. Primary release work items that are NOT complete. These gate the release readiness. Exclude duplicates and cancelled items. Sort ids ascending, no duplicates.
Count unresolved high-impact blockers. From the release's blockers, filter to:
severity is High or Critical.
status is NOT Resolved (i.e., Open or Monitoring).
- Count by exact
cause string. Use the cause text verbatim as the key.
Trace critical dependency chains. Use GET /api/dependencies to find chains where:
- The
blocked_id is a release work item (gating or otherwise).
- Follow
depends_on_id links until reaching a non-complete dependency or a terminal item.
- A chain is "critical" if it blocks a release work item and the dependency at the end is not complete.
- Each chain is an ordered array of work item ids:
[release_work_item, ..., non_complete_dependency].
- Sort chains lexicographically by the full path (join with a delimiter, sort, then split back).
Compute readiness score. total_complete_primary / total_primary_release_items, rounded to 3 decimal places. Count only primary items (exclude duplicates and cancelled).
Determine ship decision:
SHIP: readiness_score >= 0.95 AND no gating work items AND no unresolved Critical/High blockers.
SHIP_WITH_WATCH: readiness_score >= 0.80 AND ≤ 3 gating items AND no Critical unresolved blockers.
NO_SHIP: anything below SHIP_WITH_WATCH thresholds, OR any Critical unresolved blocker, OR any incomplete milestone where completion_pct < 50.0.
Ordering and Precision
milestone_completion: sorted by milestone_id ascending.
gating_work_item_ids: sorted ascending, no duplicates.
blocker_cause_counts: keys are exact cause strings (verbatim from the API).
critical_dependency_chains: sorted lexicographically by the string representation of the full path array.
completion_pct: 1 decimal place.
readiness_score: 3 decimal places.
Cross-Cutting Conventions
Stale Field Handling
Two fields in work items are not authoritative and must be ignored for decision-making:
mirror_status — This is a stale export/sync field that may not reflect the true current status. Always use status instead.
legacy_category — This is a deprecated classification. Always use the portfolio category classification methodology (labels + work_type + title) instead.
When an answer template includes ignored_mirror_status_and_legacy_category, set it to true to confirm you disregarded these fields.
Primary vs. Duplicate Records
- A work item is a duplicate when
status = "Duplicate" and duplicate_of is non-null.
- A work item is primary when it is not a duplicate and not cancelled.
- Duplicate items should never be counted in portfolio mixes, SLA populations, or release metrics. Track them separately in exclusion/cluster lists.
- Some items may have
status = "Closed" (or other non-Duplicate status) but still have a non-null duplicate_of — treat these as distractors/duplicates and exclude them.
Date Handling
- All dates are in
YYYY-MM-DD format.
- Compute day differences using Python's
datetime module: (date2 - date1).days.
- Quarter filtering:
closed_at must fall within the quarter's date range (e.g., 2025-Q4 = 2025-10-01 through 2025-12-31).
Rounding Rules
| Metric |
Precision |
| Portfolio percentages (actual, target, gap) |
1 decimal place |
| Milestone completion_pct |
1 decimal place |
| Breach rate |
3 decimal places |
| Readiness score |
3 decimal places |
Use standard rounding (round half up). In Python: round(value, decimals).
Sort Orderings
| List |
Order |
| Work item IDs (general) |
Lexicographic ascending (standard string sort) |
| Work item IDs (by close date) |
closed_at ascending, then id ascending |
| Teams |
Alphabetical ascending |
| Product areas |
Alphabetical ascending |
| Gap table / mix table rows |
Fixed order: NewFeature, TechDebt, Reliability, Security |
| Under-invested categories |
Most negative gap to least negative gap |
| Duplicate clusters |
By primary_id ascending |
| Duplicate ids within cluster |
Lexicographic ascending |
| Milestone completion |
By milestone_id ascending |
| Escalation queue |
Severity (S1→S4), then age descending, then id ascending |
Common Pitfalls
Trusting mirror_status over status. Always use status as the authoritative field. The mirror_status field is deliberately stale in the data.
Using legacy_category for classification. It does not follow the portfolio category conventions. Always classify from labels, work_type, and title.
Including duplicate or cancelled items in counts. These must be tracked separately and excluded from all primary metrics.
Double-counting items that appear in multiple queries. Deduplicate by id.
Misclassifying Feature/Enhancement work_type items. Not all Feature items are NewFeature — labels can override this. The same applies to Enhancement items.
Using the wrong denominator. Portfolio percentages use count of included items (not story points). Readiness score uses primary release items only.
Incorrect SLA overdue logic. Items closed very recently (within the recent window) still count as active for SLA purposes. Use status and closed_at together to determine if an item is truly closed.
Forgetting to sort. Every list field has a required sort order. Applying the wrong sort will cause validation failures.
Not fetching all data before computing. Some relationships (dependencies, blockers, duplicate_of chains) require joining data across multiple endpoints. Fetch all relevant data first, then compute.
Tools and Commands Cheat Sheet
# Fetch all work items
curl -s http://task-env:9024/api/work-items
# Fetch single work item
curl -s http://task-env:9024/api/work-items/WI-24024-P001
# Fetch mix targets
curl -s http://task-env:9024/api/mix-targets
# Fetch SLA policy
curl -s http://task-env:9024/api/sla-policy
# Fetch all releases
curl -s http://task-env:9024/api/releases
# Fetch single release with milestones and blockers
curl -s http://task-env:9024/api/releases/REL-ORION-2026-02
# Fetch all milestones
curl -s http://task-env:9024/api/milestones
# Fetch all dependencies
curl -s http://task-env:9024/api/dependencies
# Fetch all blockers
curl -s http://task-env:9024/api/blockers
# SQL query (requires auth header)
curl -s -X POST http://task-env:9024/api/query \
-H 'X-Env-Token: <token>' \
-H 'Content-Type: application/json' \
-d '{"sql": "SELECT id, status, team FROM work_items WHERE team = '''Platform Core'''"}'
# Filter by quarter via SQL
curl -s -X POST http://task-env:9024/api/query \
-H 'X-Env-Token: <token>' \
-H 'Content-Type: application/json' \
-d '{"sql": "SELECT * FROM work_items WHERE closed_at >= '''2025-10-01''' AND closed_at <= '''2025-12-31'''"}'
# Filter by label substring via SQL
curl -s -X POST http://task-env:9024/api/query \
-H 'X-Env-Token: <token>' \
-H 'Content-Type: application/json' \
-d '{"sql": "SELECT * FROM work_items WHERE labels LIKE '''%\"security\"%'''"}'
Output Format
All tasks require a single JSON object as output with no prose outside the JSON. Follow the answer template schema provided in the task's input/payloads/answer_template.json exactly. Every required field must be present. Every const value in the schema must match. Enum values must be selected from the allowed set. Array ordering must follow the conventions documented above.
1---2name: fewshot-attempt-01-513description: Portfolio Engineering Environment — Reusable Skill4---5# Portfolio Engineering Environment — Reusable Skill67## Overview89This skill covers interacting with a shared portfolio-management REST API to produce:10- **Portfolio mix reviews** — classify closed work items into investment categories, compare actual mix against targets, identify gaps, and recommend rebalancing actions.11- **SLA aging audits** — identify primary SLA-eligible work, detect overdue items, compute aging distributions, surface owner/team hotspots, flag duplicate clusters, and calculate breach rates.12- **Release readiness assessments** — evaluate milestone completion, identify gating work items, count unresolved high-impact blockers, trace critical dependency chains, and compute readiness scores.1314---1516## Environment Connection1718### Base URL1920All API calls go to the base URL supplied in the task's `<TASK_ENV_BASE_URL>` placeholder (provided at runtime by `environment_access.md`). That file supplies:2122```23base_url: e.g. http://task-env:9024/24credentials:25 api_query_header: X-Env-Token26 api_query_token: <token-value>27allowed_endpoints:28 - GET /api/work-items29 - GET /api/work-items/{item_id}30 - GET /api/mix-targets31 - GET /api/sla-policy32 - GET /api/releases33 - GET /api/releases/{release_id}34 - GET /api/milestones35 - GET /api/dependencies36 - GET /api/blockers37 - POST /api/query38```3940### Authentication4142The only endpoint requiring authentication is `POST /api/query`. Send the header:4344```45X-Env-Token: <api_query_token>46```4748All `GET` endpoints are unauthenticated.4950---5152## API Reference5354### GET /api/work-items5556Returns all work items with pagination metadata.5758**Response shape:**59```json60{61 "count": <integer>,62 "work_items": [ <work_item_object>, ... ]63}64```6566### GET /api/work-items/{item_id}6768Returns a single work item.6970**Response shape:**71```json72{73 "work_item": { <work_item_object> }74}75```7677### Work Item Object Fields7879| Field | Type | Description |80|-------|------|-------------|81| `id` | string | Unique identifier, e.g. `WI-24024-P001` |82| `title` | string | Human-readable title |83| `work_type` | string | One of: `Feature`, `Refactor`, `Incident`, `Security`, `Reliability`, `Compliance`, `Bug`, `Dependency`, `Chore`, `Enhancement` |84| `status` | string | One of: `Closed`, `Done`, `Deployed`, `Verified`, `Review`, `In Progress`, `Backlog`, `Duplicate`, `Cancelled`, `Reopened` |85| `team` | string | Owning engineering team |86| `owner` | string or null | Person assigned; `null` means unassigned |87| `product_area` | string | Product area this work belongs to |88| `created_at` | string (date) | Creation date, format `YYYY-MM-DD` |89| `due_at` | string (date) | SLA due date, format `YYYY-MM-DD` |90| `closed_at` | string (date) or null | Close date, format `YYYY-MM-DD`; null if still open |91| `severity` | string | `S1`, `S2`, `S3`, or `S4` |92| `priority` | integer | 1 (highest) to 5 (lowest) |93| `labels` | array of strings | Free-form tags, e.g. `["security","cve","rollout"]` |94| `story_points` | integer | Effort estimate |95| `release_id` | string or null | Release this item belongs to |96| `milestone_id` | string or null | Milestone this item belongs to |97| `duplicate_of` | string or null | If status is `Duplicate`, points to the canonical/primary work item id |98| `mirror_status` | string | **STALE FIELD** — do not use as source of truth. Use `status` instead. |99| `legacy_category` | string | **STALE FIELD** — do not use for portfolio classification. Use authoritative signals instead. |100101### GET /api/mix-targets102103Returns all portfolio mix target rows.104105**Response shape:**106```json107{108 "mix_targets": [109 {110 "scope_id": "train_001",111 "quarter": "2025-Q4",112 "team_group": "Platform Core + Identity Services",113 "product_area": "Atlas Backend + Identity",114 "new_feature_pct": 0.34,115 "tech_debt_pct": 0.24,116 "reliability_pct": 0.22,117 "security_pct": 0.20118 }119 ]120}121```122123Target percentages are expressed as decimals (0.34 = 34%). When presenting results, convert to percentage points (multiply by 100) and round to 1 decimal place.124125### GET /api/sla-policy126127Returns SLA due-date rules keyed by severity.128129**Response shape:**130```json131{132 "sla_policy": [133 { "severity": "S1", "days_to_due": 3 },134 { "severity": "S2", "days_to_due": 10 },135 { "severity": "S3", "days_to_due": 21 },136 { "severity": "S4", "days_to_due": 45 }137 ]138}139```140141An item is **overdue** when `as_of_date - created_at > days_to_due` for its severity AND the item is not closed (or was closed after the as-of date, or was closed recently within the `recent_closed_window_days` window — see SLA methodology below).142143### GET /api/releases144145Returns all releases.146147**Response shape:**148```json149{150 "releases": [151 {152 "id": "REL-ORION-2026-02",153 "name": "Orion February portfolio train",154 "target_date": "2026-02-20",155 "train": "Orion"156 }157 ]158}159```160161### GET /api/releases/{release_id}162163Returns a single release with its embedded milestones and blockers.164165**Response shape:**166```json167{168 "release": { <release_object> },169 "milestones": [ <milestone_object>, ... ],170 "blockers": [ <blocker_object>, ... ]171}172```173174### GET /api/milestones175176Returns all milestones across all releases.177178**Response shape:**179```json180{181 "milestones": [182 {183 "id": "MIL-ORION-BETA",184 "name": "Orion beta freeze",185 "owner_team": "Release Engineering",186 "release_id": "REL-ORION-2026-02"187 }188 ]189}190```191192### GET /api/dependencies193194Returns all dependency edges between work items.195196**Response shape:**197```json198{199 "dependencies": [200 {201 "blocked_id": "WI-24024-010",202 "depends_on_id": "WI-24024-009",203 "relation": "blocks-release-readiness"204 }205 ]206}207```208209Relation types include: `depends-on`, `blocks-release-readiness`, `security-review-required`, `validation-required`, `implementation-dependency`, `audit-evidence-required`.210211### GET /api/blockers212213Returns all blocker records.214215**Response shape:**216```json217{218 "blockers": [219 {220 "id": "BLK-24024-001",221 "work_item_id": "WI-24024-010",222 "release_id": "REL-ORION-2026-02",223 "cause": "open reliability rehearsal gap",224 "severity": "High",225 "status": "Open",226 "opened_at": "2026-02-06",227 "resolved_at": null228 }229 ]230}231```232233Blocker severities: `Critical`, `High`, `Medium`, `Low`. 234Blocker statuses: `Open`, `Monitoring`, `Resolved`.235236### POST /api/query237238Run SQL queries against the work items data. Requires the auth header.239240**Request:**241```json242{243 "sql": "SELECT id, status, team FROM work_items WHERE team = 'AppSec'"244}245```246247**Response shape:**248```json249{250 "columns": ["id", "status", "team"],251 "rows": [ [...], ... ],252 "row_count": <integer>,253 "truncated": false254}255```256257The table name is `work_items`. Available columns match the work item object fields listed above. Use this endpoint for filtered queries that would be inefficient via the GET list endpoint.258259**Querying tip:** The JSON label array is stored as a JSON string in the SQL backend. Use `LIKE '%"security"%'` patterns to filter by label value.260261---262263## Portfolio Category Classification264265Every included work item must be classified into exactly one of four portfolio categories:266267| Category | Code |268|----------|------|269| New Feature | `NewFeature` |270| Technical Debt | `TechDebt` |271| Reliability | `Reliability` |272| Security | `Security` |273274### Signal Sources (in priority order)2752761. **Labels array** — the strongest signal. Each label token maps to a category convention.2772. **Work type** — the second signal. Used as default when labels are ambiguous or silent.2783. **Title** — can disambiguate when labels and work_type conflict (e.g., a title noting a label is "stale").279280### Classification Methodology281282When classifying a work item:2832841. **Extract all signals** from labels, work_type, and title.2852. **Resolve conflicts** using the portfolio category conventions. When multiple categories are signaled by labels, the one with the strongest signal wins. When labels give no clear category signal, fall back to work_type. When work_type is also ambiguous, inspect the title.2863. **Every item gets exactly one category.** No item is double-counted or left unclassified.2874. **Verify your counts** against the target mix. If the target says 34% NewFeature and you have 0%, re-examine your classification of items whose work_type is `Feature` or `Enhancement` but whose labels push them elsewhere.288289Common label-to-category associations visible in the training data:290- Labels containing `security` or `cve` → Security291- Labels containing `reliability`, `incident`, `outage`, or `latency` → Reliability292- Labels containing `refactor`, `cleanup`, or `migration` → TechDebt293- Labels containing `feature` → NewFeature (when no higher-priority signal exists)294- Labels like `auth`, `encryption`, `rollout`, `flaky` appear across categories — resolve by priority and adjacent signals.295296---297298## Task Type 1: Portfolio Mix Review299300### Purpose301Compare the count-based distribution of closed work items across the four portfolio categories against a target mix, identify under-invested categories, and recommend a follow-up action.302303### Step-by-Step Method3043051. **Fetch the mix target.** Query `/api/mix-targets` and locate the row whose `scope_id` matches the task's scope. Convert the decimal target percentages to percentage points (multiply by 100, round to 1 decimal).3063072. **Identify in-scope work items.** Use `/api/work-items` or `POST /api/query` to find items matching the scope's teams AND product areas. Filter to the correct quarter (closed_at within the quarter).3083093. **Separate primary from excluded records:**310 - **Duplicates:** Items with `status = "Duplicate"` and a non-null `duplicate_of` field. Exclude from the portfolio count. Track their ids.311 - **Cancelled:** Items with `status = "Cancelled"`. Exclude from the portfolio count. Track their ids.312 - **Distractors:** Items that match scope on teams/product_area/quarter but have a `duplicate_of` pointing to an item in a different scope, or items whose `mirror_status` contradicts `status`. Exclude these and track their ids.3133144. **Classify each included item** into one of the four categories using the classification methodology above.3153165. **Compute counts and percentages:**317 - `category_counts`: integer count per category.318 - `category_percentages`: `(count / total_included) * 100`, rounded to 1 decimal place.3193206. **Build the gap table.** For each category (ordered: NewFeature, TechDebt, Reliability, Security):321 - `target_pct`: from the mix target row (as percentage points, 1 decimal).322 - `actual_pct`: from step 5.323 - `gap_pct`: `actual_pct - target_pct`, rounded to 1 decimal.3243257. **Identify under-invested categories.** Categories where `gap_pct < 0`. Sort from most negative to least negative gap.3263278. **Determine the follow-up action:**328 - If any under-invested categories exist → `REBALANCE_CAPACITY`, primary = category with largest negative gap, secondary = next largest, rationale = `LARGEST_NEGATIVE_GAP`.329 - If no under-invested categories → `MAINTAIN_CURRENT_MIX`, primary and secondary = null, rationale = `NO_NEGATIVE_GAPS`.330 - If data conflicts are found (e.g., mirror_status contradicting status across multiple records) → `INVESTIGATE_DATA_QUALITY`, rationale = `DATA_CONFLICT`.3313329. **Populate exclusion flags:**333 - `excluded_duplicate_ids`: sorted list of duplicate record ids.334 - `excluded_cancelled_ids`: sorted list of cancelled record ids.335 - `ignored_mirror_status_and_legacy_category`: always `true`.33633710. **Verify.** Confirm `sum(category_counts) == total_included`. Confirm percentages sum to approximately 100.0% (may be 99.9 or 100.1 due to rounding). Confirm gap_pct values: `sum(gap_pct)` should be approximately 0.0.338339### Ordering Conventions340- Work item IDs in `included_work_item_ids`: sort by `closed_at` ascending, then by `id` ascending.341- Exclusion IDs: sort by `closed_at` ascending, then by `id` ascending.342- Teams and product areas in scope: sort alphabetically.343- Gap table rows: NewFeature, TechDebt, Reliability, Security (fixed order).344- Under-invested categories: most negative gap first.345346---347348## Task Type 2: SLA Aging Audit349350### Purpose351Audit SLA compliance for reliability and security work items, identifying overdue items, aging distributions, team/owner hotspots, duplicate clusters, and breach rates.352353### Step-by-Step Method3543551. **Fetch SLA policy.** `GET /api/sla-policy` — maps severity to `days_to_due`.3563572. **Identify the SLA-eligible population.** Query work items where:358 - `team` matches the scope's teams.359 - The item belongs to the SLA-relevant categories (e.g., Reliability, Security). Use the portfolio category classification to determine category.360 - Exclude items with `status = "Duplicate"` or `status = "Cancelled"`.3613623. **Separate primary from duplicate records:**363 - Primary: items where `duplicate_of` is null (or status is not Duplicate).364 - Duplicate clusters: group items where `duplicate_of` points to a primary id, or where `status = "Duplicate"`. Each cluster has one `primary_id` (the canonical item) and a list of `duplicate_ids`.365 - The `included_primary_ids` list contains only primary records.3663674. **Determine overdue status.** For each primary item:368 - Compute age: `as_of_date - created_at` in days.369 - Look up `days_to_due` from SLA policy by the item's severity.370 - An item is **overdue** if `age > days_to_due` AND the item is not already closed. However, items closed within the `recent_closed_window_days` of the as-of date should be treated as **still open** for SLA purposes (they were recently resolved and count toward the active SLA population). Items closed before the recent window are considered closed and not overdue.371 - **Key distinction:** Use `status` (not `mirror_status`) to determine if an item is closed. A closed/completed status (`Closed`, `Done`, `Deployed`, `Verified`) with a `closed_at` date before `as_of_date - recent_closed_window_days` means the item is genuinely closed and not overdue.3723735. **Populate `overdue_primary_ids`.** Subset of `included_primary_ids` that are overdue. Sort lexicographically.3743756. **Compute aging buckets.** For each included primary item, compute age in days (from `created_at` to `as_of_date`). Count into buckets: `0-3`, `4-7`, `8-14`, `15-30`, `31+`. Include ALL primary items, not just overdue ones.3763777. **Compute team overdue counts.** For each team (alphabetical order), count overdue primary items assigned to that team.3783798. **Find the top hotspot.** The (team, owner) pair with the most overdue primary items. When owner is null/absent, use `UNASSIGNED`. If multiple pairs tie, pick the first alphabetically by team, then by owner.3803819. **Identify duplicate clusters.** Group duplicate records by their `primary_id` (the `duplicate_of` value). Each cluster has a `primary_id` and sorted `duplicate_ids`. Sort clusters by `primary_id`.38238310. **Identify missing-owner items.** Primary included items where `owner` is null. Sort ids lexicographically.38438511. **Calculate breach rate.** `overdue_primary_ids.length / included_primary_ids.length`. Round to exactly 3 decimal places.386387### Ordering Conventions388- All ID lists: sorted lexicographically (ascending).389- Teams in `team_overdue_counts`: sorted alphabetically.390- Duplicate clusters: sorted by `primary_id`. Within each cluster, `duplicate_ids` sorted lexicographically.391- `breach_rate`: 3 decimal places.392393### Escalation Queue (when required)394When the answer template includes an escalation queue, order overdue primary items by:3951. Severity (S1 first, then S2, S3, S4).3962. Within the same severity, by age descending (oldest first).3973. Within same age, by id ascending.398399---400401## Task Type 3: Release Readiness Assessment402403### Purpose404Evaluate whether a release is ready to ship by assessing milestone completion, gating work items, blocker counts, and dependency chains.405406### Step-by-Step Method4074081. **Fetch release data.** `GET /api/releases/{release_id}` returns the release object, its milestones array, and its blockers array.4094102. **Identify release work items.** Query work items where `release_id` matches the target release. These are the primary work items for the release.4114123. **For each milestone, compute completion:**413 - `primary_total`: count of primary release work items assigned to this milestone (`milestone_id` matches).414 - `complete_primary`: subset of those where status indicates completion. Completed statuses are: `Closed`, `Done`, `Deployed`, `Verified`. Items with status `Duplicate`, `Cancelled`, `In Progress`, `Backlog`, `Review`, `Reopened` are NOT complete.415 - `completion_pct`: `(complete_primary / primary_total) * 100`, rounded to 1 decimal place. If `primary_total == 0`, `completion_pct = 0.0`.416 - Sort `milestone_completion` by `milestone_id` ascending.4174184. **Identify gating work items.** Primary release work items that are NOT complete. These gate the release readiness. Exclude duplicates and cancelled items. Sort ids ascending, no duplicates.4194205. **Count unresolved high-impact blockers.** From the release's blockers, filter to:421 - `severity` is `High` or `Critical`.422 - `status` is NOT `Resolved` (i.e., `Open` or `Monitoring`).423 - Count by exact `cause` string. Use the cause text verbatim as the key.4244256. **Trace critical dependency chains.** Use `GET /api/dependencies` to find chains where:426 - The `blocked_id` is a release work item (gating or otherwise).427 - Follow `depends_on_id` links until reaching a non-complete dependency or a terminal item.428 - A chain is "critical" if it blocks a release work item and the dependency at the end is not complete.429 - Each chain is an ordered array of work item ids: `[release_work_item, ..., non_complete_dependency]`.430 - Sort chains lexicographically by the full path (join with a delimiter, sort, then split back).4314327. **Compute readiness score.** `total_complete_primary / total_primary_release_items`, rounded to 3 decimal places. Count only primary items (exclude duplicates and cancelled).4334348. **Determine ship decision:**435 - `SHIP`: readiness_score >= 0.95 AND no gating work items AND no unresolved Critical/High blockers.436 - `SHIP_WITH_WATCH`: readiness_score >= 0.80 AND ≤ 3 gating items AND no Critical unresolved blockers.437 - `NO_SHIP`: anything below SHIP_WITH_WATCH thresholds, OR any Critical unresolved blocker, OR any incomplete milestone where completion_pct < 50.0.438439### Ordering and Precision440- `milestone_completion`: sorted by `milestone_id` ascending.441- `gating_work_item_ids`: sorted ascending, no duplicates.442- `blocker_cause_counts`: keys are exact cause strings (verbatim from the API).443- `critical_dependency_chains`: sorted lexicographically by the string representation of the full path array.444- `completion_pct`: 1 decimal place.445- `readiness_score`: 3 decimal places.446447---448449## Cross-Cutting Conventions450451### Stale Field Handling452453Two fields in work items are **not authoritative** and must be ignored for decision-making:4544551. **`mirror_status`** — This is a stale export/sync field that may not reflect the true current status. Always use `status` instead.4562. **`legacy_category`** — This is a deprecated classification. Always use the portfolio category classification methodology (labels + work_type + title) instead.457458When an answer template includes `ignored_mirror_status_and_legacy_category`, set it to `true` to confirm you disregarded these fields.459460### Primary vs. Duplicate Records461462- A work item is a **duplicate** when `status = "Duplicate"` and `duplicate_of` is non-null.463- A work item is **primary** when it is not a duplicate and not cancelled.464- Duplicate items should never be counted in portfolio mixes, SLA populations, or release metrics. Track them separately in exclusion/cluster lists.465- Some items may have `status = "Closed"` (or other non-Duplicate status) but still have a non-null `duplicate_of` — treat these as distractors/duplicates and exclude them.466467### Date Handling468469- All dates are in `YYYY-MM-DD` format.470- Compute day differences using Python's `datetime` module: `(date2 - date1).days`.471- Quarter filtering: `closed_at` must fall within the quarter's date range (e.g., 2025-Q4 = 2025-10-01 through 2025-12-31).472473### Rounding Rules474475| Metric | Precision |476|--------|-----------|477| Portfolio percentages (actual, target, gap) | 1 decimal place |478| Milestone completion_pct | 1 decimal place |479| Breach rate | 3 decimal places |480| Readiness score | 3 decimal places |481482Use standard rounding (round half up). In Python: `round(value, decimals)`.483484### Sort Orderings485486| List | Order |487|------|-------|488| Work item IDs (general) | Lexicographic ascending (standard string sort) |489| Work item IDs (by close date) | `closed_at` ascending, then `id` ascending |490| Teams | Alphabetical ascending |491| Product areas | Alphabetical ascending |492| Gap table / mix table rows | Fixed order: NewFeature, TechDebt, Reliability, Security |493| Under-invested categories | Most negative gap to least negative gap |494| Duplicate clusters | By `primary_id` ascending |495| Duplicate ids within cluster | Lexicographic ascending |496| Milestone completion | By `milestone_id` ascending |497| Escalation queue | Severity (S1→S4), then age descending, then id ascending |498499---500501## Common Pitfalls5025031. **Trusting `mirror_status` over `status`.** Always use `status` as the authoritative field. The mirror_status field is deliberately stale in the data.5045052. **Using `legacy_category` for classification.** It does not follow the portfolio category conventions. Always classify from labels, work_type, and title.5065073. **Including duplicate or cancelled items in counts.** These must be tracked separately and excluded from all primary metrics.5085094. **Double-counting items that appear in multiple queries.** Deduplicate by `id`.5105115. **Misclassifying Feature/Enhancement work_type items.** Not all `Feature` items are `NewFeature` — labels can override this. The same applies to `Enhancement` items.5125136. **Using the wrong denominator.** Portfolio percentages use count of included items (not story points). Readiness score uses primary release items only.5145157. **Incorrect SLA overdue logic.** Items closed very recently (within the recent window) still count as active for SLA purposes. Use `status` and `closed_at` together to determine if an item is truly closed.5165178. **Forgetting to sort.** Every list field has a required sort order. Applying the wrong sort will cause validation failures.5185199. **Not fetching all data before computing.** Some relationships (dependencies, blockers, duplicate_of chains) require joining data across multiple endpoints. Fetch all relevant data first, then compute.520521---522523## Tools and Commands Cheat Sheet524525```bash526# Fetch all work items527curl -s http://task-env:9024/api/work-items528529# Fetch single work item530curl -s http://task-env:9024/api/work-items/WI-24024-P001531532# Fetch mix targets533curl -s http://task-env:9024/api/mix-targets534535# Fetch SLA policy536curl -s http://task-env:9024/api/sla-policy537538# Fetch all releases539curl -s http://task-env:9024/api/releases540541# Fetch single release with milestones and blockers542curl -s http://task-env:9024/api/releases/REL-ORION-2026-02543544# Fetch all milestones545curl -s http://task-env:9024/api/milestones546547# Fetch all dependencies548curl -s http://task-env:9024/api/dependencies549550# Fetch all blockers551curl -s http://task-env:9024/api/blockers552553# SQL query (requires auth header)554curl -s -X POST http://task-env:9024/api/query \555 -H 'X-Env-Token: <token>' \556 -H 'Content-Type: application/json' \557 -d '{"sql": "SELECT id, status, team FROM work_items WHERE team = '''Platform Core'''"}'558559# Filter by quarter via SQL560curl -s -X POST http://task-env:9024/api/query \561 -H 'X-Env-Token: <token>' \562 -H 'Content-Type: application/json' \563 -d '{"sql": "SELECT * FROM work_items WHERE closed_at >= '''2025-10-01''' AND closed_at <= '''2025-12-31'''"}'564565# Filter by label substring via SQL566curl -s -X POST http://task-env:9024/api/query \567 -H 'X-Env-Token: <token>' \568 -H 'Content-Type: application/json' \569 -d '{"sql": "SELECT * FROM work_items WHERE labels LIKE '''%\"security\"%'''"}'570```571572---573574## Output Format575576All tasks require a **single JSON object** as output with no prose outside the JSON. Follow the answer template schema provided in the task's `input/payloads/answer_template.json` exactly. Every required field must be present. Every `const` value in the schema must match. Enum values must be selected from the allowed set. Array ordering must follow the conventions documented above.