feat(ticket): persist Orchestrator assignment tracking and audit
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:ba017607e313ba3eb500e4eb78f94c2bd95a08acde96688b8d2a0014abd94ea3)
|
||||
// Input contract: 0.1.0 (sha256:982cc7edc8fd8655a14e943b9d0022df56e941107def02251186d8ddebfd6774)
|
||||
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/companion-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:ba017607e313ba3eb500e4eb78f94c2bd95a08acde96688b8d2a0014abd94ea3)
|
||||
// Input contract: 0.1.0 (sha256:982cc7edc8fd8655a14e943b9d0022df56e941107def02251186d8ddebfd6774)
|
||||
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/repository-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:ba017607e313ba3eb500e4eb78f94c2bd95a08acde96688b8d2a0014abd94ea3)
|
||||
// Input contract: 0.1.0 (sha256:982cc7edc8fd8655a14e943b9d0022df56e941107def02251186d8ddebfd6774)
|
||||
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/runtime-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:ba017607e313ba3eb500e4eb78f94c2bd95a08acde96688b8d2a0014abd94ea3)
|
||||
// Input contract: 0.1.0 (sha256:982cc7edc8fd8655a14e943b9d0022df56e941107def02251186d8ddebfd6774)
|
||||
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/ticket-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
|
||||
|
||||
export type Array_of_TicketAssignmentTrackingProjection = Array<TicketAssignmentTrackingProjection>;
|
||||
|
||||
export type Array_of_WorkspaceTag = Array<WorkspaceTag>;
|
||||
|
||||
export type BrowserAppendTicketEventRequest = { author?: string | null; body: string; role: BrowserTicketThreadRole; };
|
||||
@@ -87,10 +89,28 @@ export type TicketActionEligibility = { blockers: Array<string>; can_assign_orch
|
||||
|
||||
export type TicketAssignmentPrincipal = { account_id: string; kind: "user"; } | { kind: "worker"; runtime_id: string; worker_id: string; } | { agent_key: string; kind: "workspace_agent"; };
|
||||
|
||||
export type TicketAssignmentPrincipalSnapshot = { account_id: string; kind: "user"; } | { kind: "worker"; runtime_id: string; worker_id: string; } | { agent_key: string; kind: "workspace_agent"; };
|
||||
|
||||
export type TicketAssignmentRelease = { action: TicketAssignmentReleaseAction; actor: string; at: string; event_id: string; last_tracking?: TicketAssignmentTracking | null; operation_key?: string | null; reason?: string | null; replacement_assignment_id?: string | null; };
|
||||
|
||||
export type TicketAssignmentReleaseAction = "unassigned" | "replaced";
|
||||
|
||||
export type TicketAssignmentRole = "orchestrator" | "worker" | "owner" | "contributor";
|
||||
|
||||
export type TicketAssignmentSummary = { assignment_id: string; runtime_id: string; worker_id: string; worker_resource_key?: string | null; };
|
||||
|
||||
export type TicketAssignmentTracking = { next_action?: string | null; progress?: string | null; result?: string | null; status: TicketAssignmentTrackingStatus; waiting_reason?: string | null; };
|
||||
|
||||
export type TicketAssignmentTrackingHistoryEntry = { at: string; event_id: string; operation_key: string; source_context: unknown; tracking: TicketAssignmentTracking; };
|
||||
|
||||
export type TicketAssignmentTrackingHistoryPage = { items: Array<TicketAssignmentTrackingHistoryEntry>; next_event_id?: string | null; };
|
||||
|
||||
export type TicketAssignmentTrackingProjection = { assigned_at: string; assigned_by: string; assignment_id: string; current: boolean; last_event_id?: string | null; migration_diagnostic?: string | null; principal: TicketAssignmentPrincipalSnapshot; release?: TicketAssignmentRelease | null; role: string; ticket_id: string; tracking?: TicketAssignmentTracking | null; updated_at?: string | null; workspace_id: string; };
|
||||
|
||||
export type TicketAssignmentTrackingStatus = "waiting" | "inprogress" | "done";
|
||||
|
||||
export type TicketAssignmentTrackingUpdate = { assignment_id: string; expected_last_event_id: string; operation_key: string; tracking: TicketAssignmentTracking; };
|
||||
|
||||
export type TicketAttention = "unresolved_review" | "missing_commit" | "blocked" | "unblocked" | "awaiting_review" | "unresolved_changes" | "stale_after_rescope" | "missing_evidence";
|
||||
|
||||
export type TicketCaseAction = { action: "close"; reason: TicketClosureReason; } | { action: "reopen"; } | { action: "correct_closure"; reason: TicketClosureReason; };
|
||||
@@ -103,7 +123,7 @@ export type TicketClosureJudgment = { at: string; author: string; explanation: s
|
||||
|
||||
export type TicketClosureReason = "completed" | "not_planned" | "duplicate" | "unknown" | { other: string; };
|
||||
|
||||
export type TicketDetail = { action_eligibility: TicketActionEligibility; artifact_count: number; artifacts: Array<string>; assignment_diagnostics: Array<string>; assignments: Array<TicketRoleAssignmentSummary>; body: string; body_truncated: boolean; closure_history: Array<TicketCaseHistoryEntry>; closure_operation_key?: string | null; content_digest: string; created_at?: string | null; current_closure?: TicketClosureJudgment | null; current_worker?: TicketAssignmentSummary | null; event_count: number; event_page: QueryPage; events: Array<TicketEventDetail>; evidence: TicketEvidenceSummary; id: string; implementation_reports: Array<TicketEvidenceEvent>; legacy_handoff?: TicketLegacyWorkflowSnapshot | null; linked_objectives: Array<ObjectiveLinkSummary>; merge_request?: TicketMergeRequestSummary | null; merge_requests: Array<TicketMergeRequestSummary>; priority: string; queued_at?: string | null; queued_by?: string | null; readiness?: string | null; record_source: string; relations: TicketDetailRelationView; resolution?: string | null; resource_key: string; risk_flags: Array<string>; state: BrowserTicketState; tags: Array<WorkspaceTag>; targets: Array<TicketTarget>; title: string; updated_at?: string | null; };
|
||||
export type TicketDetail = { action_eligibility: TicketActionEligibility; artifact_count: number; artifacts: Array<string>; assignment_diagnostics: Array<string>; assignment_tracking: Array<TicketAssignmentTrackingProjection>; assignments: Array<TicketRoleAssignmentSummary>; body: string; body_truncated: boolean; closure_history: Array<TicketCaseHistoryEntry>; closure_operation_key?: string | null; content_digest: string; created_at?: string | null; current_closure?: TicketClosureJudgment | null; current_worker?: TicketAssignmentSummary | null; event_count: number; event_page: QueryPage; events: Array<TicketEventDetail>; evidence: TicketEvidenceSummary; id: string; implementation_reports: Array<TicketEvidenceEvent>; legacy_handoff?: TicketLegacyWorkflowSnapshot | null; linked_objectives: Array<ObjectiveLinkSummary>; merge_request?: TicketMergeRequestSummary | null; merge_requests: Array<TicketMergeRequestSummary>; priority: string; queued_at?: string | null; queued_by?: string | null; readiness?: string | null; record_source: string; relations: TicketDetailRelationView; resolution?: string | null; resource_key: string; risk_flags: Array<string>; state: BrowserTicketState; tags: Array<WorkspaceTag>; targets: Array<TicketTarget>; title: string; updated_at?: string | null; };
|
||||
|
||||
export type TicketDetailDerivedRelation = { at: string; author: string; forward_kind: string; inverse_kind: string; note?: string | null; source_resource_key?: string | null; source_ticket: string; };
|
||||
|
||||
@@ -133,7 +153,7 @@ export type TicketMarkReadyRequest = { intake_summary?: TicketIntakeSummary | nu
|
||||
|
||||
export type TicketMergeRequestSummary = { current_subject_ref?: string | null; integration_evidence_error?: string | null; merge_request_id: string; repository_key: string; review_excerpt?: string | null; review_requested_at?: string | null; review_status: string; review_subject_ref?: string | null; review_submitted_at?: string | null; selector_from?: string | null; selector_to: string; source_ref_observation: TicketSourceRefObservation; state: string; updated_at: string; };
|
||||
|
||||
export type TicketQueryItem = { blocker_count: number; closure_operation_key?: string | null; content_digest: string; created_at?: string | null; current_closure?: TicketClosureJudgment | null; current_worker?: TicketAssignmentSummary | null; evidence: TicketEvidenceSummary; id: string; legacy_handoff?: TicketLegacyWorkflowSnapshot | null; linked_objective_ids: Array<string>; linked_objective_keys: Array<string>; matched_fields: Array<string>; matching_event?: TicketEvidenceEvent | null; merge_request?: TicketMergeRequestSummary | null; merge_requests: Array<TicketMergeRequestSummary>; priority: string; readiness?: string | null; relation_count: number; resource_key: string; snippet?: string | null; state: BrowserTicketState; tags: Array<WorkspaceTag>; title: string; unresolved_blocker_count: number; unresolved_review_count: number; updated_at?: string | null; workspace_action_priority: string; };
|
||||
export type TicketQueryItem = { assignment_tracking: Array<TicketAssignmentTrackingProjection>; blocker_count: number; closure_operation_key?: string | null; content_digest: string; created_at?: string | null; current_closure?: TicketClosureJudgment | null; current_worker?: TicketAssignmentSummary | null; evidence: TicketEvidenceSummary; id: string; legacy_handoff?: TicketLegacyWorkflowSnapshot | null; linked_objective_ids: Array<string>; linked_objective_keys: Array<string>; matched_fields: Array<string>; matching_event?: TicketEvidenceEvent | null; merge_request?: TicketMergeRequestSummary | null; merge_requests: Array<TicketMergeRequestSummary>; priority: string; readiness?: string | null; relation_count: number; resource_key: string; snippet?: string | null; state: BrowserTicketState; tags: Array<WorkspaceTag>; title: string; unresolved_blocker_count: number; unresolved_review_count: number; updated_at?: string | null; workspace_action_priority: string; };
|
||||
|
||||
export type TicketQueryRequest = { attention?: Array<TicketAttention>; cursor?: string | null; event_kinds?: Array<string>; evidence?: Array<string>; limit?: number & { readonly "__yoiIntegerRange": "0..1000"; } | null; linked_objective_id?: string | null; query?: string | null; related_ticket_id?: string | null; relation_kind?: string | null; review_status?: string | null; sort?: string | null; states?: Array<TicketState>; tag_ids?: Array<string>; tag_ids_any?: Array<string>; tag_ids_none?: Array<string>; updated_after?: string | null; updated_before?: string | null; };
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:ba017607e313ba3eb500e4eb78f94c2bd95a08acde96688b8d2a0014abd94ea3)
|
||||
// Input contract: 0.1.0 (sha256:982cc7edc8fd8655a14e943b9d0022df56e941107def02251186d8ddebfd6774)
|
||||
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check
|
||||
|
||||
@@ -139,6 +139,7 @@ function ticketDetailFixture() {
|
||||
artifact_count: 0,
|
||||
artifacts: [],
|
||||
assignment_diagnostics: [],
|
||||
assignment_tracking: [],
|
||||
assignments: [],
|
||||
closure_history: [],
|
||||
closure_operation_key: null,
|
||||
@@ -195,6 +196,52 @@ function ticketDetailFixture() {
|
||||
};
|
||||
}
|
||||
|
||||
Deno.test("Ticket Browser keeps self-reported delegation independent of case and Worker state", () => {
|
||||
const tracking = {
|
||||
workspace_id: "workspace-a",
|
||||
ticket_id: "ticket-id",
|
||||
assignment_id: "delegation",
|
||||
role: "orchestrator",
|
||||
principal: { kind: "workspace_agent", agent_key: "workspace-orchestrator" },
|
||||
assigned_by: "workspace-user:alice",
|
||||
assigned_at: "2026-01-01T00:00:00Z",
|
||||
release: null,
|
||||
current: true,
|
||||
last_event_id: "report-event",
|
||||
updated_at: null,
|
||||
migration_diagnostic: null,
|
||||
tracking: {
|
||||
status: "done",
|
||||
waiting_reason: null,
|
||||
progress: "Investigated",
|
||||
next_action: "Human decision",
|
||||
result: "Alternatives returned",
|
||||
},
|
||||
};
|
||||
for (const state of ["open", "closed"]) {
|
||||
const parsed = parseTicketDetail({
|
||||
...ticketDetailFixture(),
|
||||
state,
|
||||
assignment_tracking: [tracking],
|
||||
});
|
||||
assertEquals(parsed.assignment_tracking[0]?.tracking?.status, "done");
|
||||
assertEquals(parsed.assignment_tracking[0]?.last_event_id, "report-event");
|
||||
assertEquals(parsed.state, state);
|
||||
assertEquals(parsed.current_worker, null);
|
||||
}
|
||||
assertThrows(
|
||||
() =>
|
||||
parseTicketDetail({
|
||||
...ticketDetailFixture(),
|
||||
assignment_tracking: [{
|
||||
...tracking,
|
||||
tracking: { ...tracking.tracking, status: "running" },
|
||||
}],
|
||||
}),
|
||||
".status is invalid",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Ticket Browser parser accepts target collections and rejects singular target authority", () => {
|
||||
const fixture = ticketDetailFixture();
|
||||
const parsed = parseTicketDetail(fixture);
|
||||
|
||||
@@ -20,7 +20,10 @@ import type {
|
||||
ReviewFinding,
|
||||
TicketActionEligibility,
|
||||
TicketAssignmentPrincipal,
|
||||
TicketAssignmentRelease,
|
||||
TicketAssignmentSummary,
|
||||
TicketAssignmentTracking,
|
||||
TicketAssignmentTrackingProjection,
|
||||
TicketCaseAction,
|
||||
TicketCaseHistoryEntry,
|
||||
TicketClosureJudgment,
|
||||
@@ -649,6 +652,130 @@ function parseTicketRoleAssignmentRecord(
|
||||
};
|
||||
}
|
||||
|
||||
function parseAssignmentTracking(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): TicketAssignmentTracking {
|
||||
const item = object(value, label);
|
||||
exact(
|
||||
item,
|
||||
["status", "waiting_reason", "progress", "next_action", "result"],
|
||||
label,
|
||||
);
|
||||
const status = string(item.status, `${label}.status`);
|
||||
if (status !== "waiting" && status !== "inprogress" && status !== "done") {
|
||||
throw new Error(`${label}.status is invalid`);
|
||||
}
|
||||
return {
|
||||
status,
|
||||
waiting_reason: optionalNullableString(
|
||||
item.waiting_reason,
|
||||
`${label}.waiting_reason`,
|
||||
),
|
||||
progress: optionalNullableString(item.progress, `${label}.progress`),
|
||||
next_action: optionalNullableString(
|
||||
item.next_action,
|
||||
`${label}.next_action`,
|
||||
),
|
||||
result: optionalNullableString(item.result, `${label}.result`),
|
||||
};
|
||||
}
|
||||
|
||||
function parseAssignmentRelease(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): TicketAssignmentRelease {
|
||||
const item = object(value, label);
|
||||
exact(item, [
|
||||
"action",
|
||||
"event_id",
|
||||
"operation_key",
|
||||
"actor",
|
||||
"at",
|
||||
"reason",
|
||||
"replacement_assignment_id",
|
||||
"last_tracking",
|
||||
], label);
|
||||
const action = string(item.action, `${label}.action`);
|
||||
if (action !== "unassigned" && action !== "replaced") {
|
||||
throw new Error(`${label}.action is invalid`);
|
||||
}
|
||||
return {
|
||||
action,
|
||||
event_id: string(item.event_id, `${label}.event_id`),
|
||||
operation_key: optionalNullableString(
|
||||
item.operation_key,
|
||||
`${label}.operation_key`,
|
||||
),
|
||||
actor: string(item.actor, `${label}.actor`),
|
||||
at: string(item.at, `${label}.at`),
|
||||
reason: optionalNullableString(item.reason, `${label}.reason`),
|
||||
replacement_assignment_id: optionalNullableString(
|
||||
item.replacement_assignment_id,
|
||||
`${label}.replacement_assignment_id`,
|
||||
),
|
||||
last_tracking: item.last_tracking === undefined
|
||||
? undefined
|
||||
: item.last_tracking === null
|
||||
? null
|
||||
: parseAssignmentTracking(item.last_tracking, `${label}.last_tracking`),
|
||||
};
|
||||
}
|
||||
|
||||
function parseAssignmentTrackingProjection(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): TicketAssignmentTrackingProjection {
|
||||
const item = object(value, label);
|
||||
exact(item, [
|
||||
"workspace_id",
|
||||
"ticket_id",
|
||||
"assignment_id",
|
||||
"role",
|
||||
"principal",
|
||||
"assigned_by",
|
||||
"assigned_at",
|
||||
"release",
|
||||
"current",
|
||||
"tracking",
|
||||
"last_event_id",
|
||||
"updated_at",
|
||||
"migration_diagnostic",
|
||||
], label);
|
||||
return {
|
||||
workspace_id: string(item.workspace_id, `${label}.workspace_id`),
|
||||
ticket_id: string(item.ticket_id, `${label}.ticket_id`),
|
||||
assignment_id: string(item.assignment_id, `${label}.assignment_id`),
|
||||
role: string(item.role, `${label}.role`),
|
||||
principal: parseTicketAssignmentPrincipal(
|
||||
item.principal,
|
||||
`${label}.principal`,
|
||||
),
|
||||
assigned_by: string(item.assigned_by, `${label}.assigned_by`),
|
||||
assigned_at: string(item.assigned_at, `${label}.assigned_at`),
|
||||
release: item.release === undefined
|
||||
? undefined
|
||||
: item.release === null
|
||||
? null
|
||||
: parseAssignmentRelease(item.release, `${label}.release`),
|
||||
current: boolean(item.current, `${label}.current`),
|
||||
tracking: item.tracking === undefined
|
||||
? undefined
|
||||
: item.tracking === null
|
||||
? null
|
||||
: parseAssignmentTracking(item.tracking, `${label}.tracking`),
|
||||
last_event_id: optionalNullableString(
|
||||
item.last_event_id,
|
||||
`${label}.last_event_id`,
|
||||
),
|
||||
updated_at: optionalNullableString(item.updated_at, `${label}.updated_at`),
|
||||
migration_diagnostic: optionalNullableString(
|
||||
item.migration_diagnostic,
|
||||
`${label}.migration_diagnostic`,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function parseActionEligibility(
|
||||
value: unknown,
|
||||
label: string,
|
||||
@@ -961,6 +1088,7 @@ export function parseTicketDetail(value: unknown): TicketDetail {
|
||||
"artifact_count",
|
||||
"artifacts",
|
||||
"assignment_diagnostics",
|
||||
"assignment_tracking",
|
||||
"assignments",
|
||||
"closure_history",
|
||||
"body",
|
||||
@@ -1012,6 +1140,11 @@ export function parseTicketDetail(value: unknown): TicketDetail {
|
||||
item.assignment_diagnostics,
|
||||
`${label}.assignment_diagnostics`,
|
||||
),
|
||||
assignment_tracking: array(
|
||||
item.assignment_tracking,
|
||||
`${label}.assignment_tracking`,
|
||||
parseAssignmentTrackingProjection,
|
||||
),
|
||||
assignments: array(
|
||||
item.assignments,
|
||||
`${label}.assignments`,
|
||||
|
||||
Reference in New Issue
Block a user