Skip to content

Commit 54fbc90

Browse files
Add launch closure automation packet
1 parent 8f77863 commit 54fbc90

5 files changed

Lines changed: 468 additions & 0 deletions

app/secure_cells_api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,10 @@ func (app *AethelredApp) SecureCellsGetHandler() http.Handler {
13381338
return
13391339
}
13401340

1341+
if app.handleSecureCellGovernmentAgentExecutionLaunchClosureAutomationPacketGet(w, r) {
1342+
return
1343+
}
1344+
13411345
if app.handleSecureCellGovernmentAgentExecutionLaunchReceiptValidationGet(w, r) {
13421346
return
13431347
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package app
2+
3+
import (
4+
"encoding/csv"
5+
"fmt"
6+
"net/http"
7+
"strconv"
8+
"strings"
9+
"time"
10+
11+
securecellsintegration "github.com/aethelred/aethelred/pkg/integrations/securecells"
12+
)
13+
14+
type secureCellGovernmentAgentExecutionLaunchClosureAutomationPacketResponse struct {
15+
Packet *securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationPacket `json:"packet,omitempty"`
16+
}
17+
18+
func (app *AethelredApp) handleSecureCellGovernmentAgentExecutionLaunchClosureAutomationPacketGet(w http.ResponseWriter, r *http.Request) bool {
19+
if r.URL.Path == secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-packet" {
20+
filter, err := parseSecureCellGovernmentAgentExecutionLaunchClosureOverdueActionFilter(r)
21+
if err != nil {
22+
writeSecureCellAPIError(w, http.StatusBadRequest, err.Error())
23+
return true
24+
}
25+
packet, err := app.secureCellService.GetGovernmentAgentExecutionLaunchClosureAutomationPacket(r.Context(), filter)
26+
if err != nil {
27+
writeSecureCellAPIError(w, http.StatusInternalServerError, err.Error())
28+
return true
29+
}
30+
writeSecureCellJSON(w, http.StatusOK, secureCellGovernmentAgentExecutionLaunchClosureAutomationPacketResponse{Packet: packet})
31+
return true
32+
}
33+
34+
if r.URL.Path == secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-packet/export" {
35+
filter, err := parseSecureCellGovernmentAgentExecutionLaunchClosureOverdueActionFilter(r)
36+
if err != nil {
37+
writeSecureCellAPIError(w, http.StatusBadRequest, err.Error())
38+
return true
39+
}
40+
packet, err := app.secureCellService.GetGovernmentAgentExecutionLaunchClosureAutomationPacket(r.Context(), filter)
41+
if err != nil {
42+
writeSecureCellAPIError(w, http.StatusInternalServerError, err.Error())
43+
return true
44+
}
45+
if err := writeSecureCellGovernmentAgentExecutionLaunchClosureAutomationPacketExport(w, r, packet); err != nil {
46+
writeSecureCellAPIError(w, http.StatusBadRequest, err.Error())
47+
}
48+
return true
49+
}
50+
51+
return false
52+
}
53+
54+
func writeSecureCellGovernmentAgentExecutionLaunchClosureAutomationPacketExport(w http.ResponseWriter, r *http.Request, packet *securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationPacket) error {
55+
format := secureCellExportFormat(r)
56+
switch format {
57+
case "json":
58+
writeSecureCellJSON(w, http.StatusOK, secureCellGovernmentAgentExecutionLaunchClosureAutomationPacketResponse{Packet: packet})
59+
return nil
60+
case "csv":
61+
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
62+
w.Header().Set("Content-Disposition", `attachment; filename="secure-cell-government-agent-execution-launch-closure-automation-packet.csv"`)
63+
writer := csv.NewWriter(w)
64+
for _, row := range secureCellGovernmentAgentExecutionLaunchClosureAutomationPacketCSVRows(packet) {
65+
if err := writer.Write(row); err != nil {
66+
return fmt.Errorf("write government-agent-execution-launch-closure-automation-packet csv row: %w", err)
67+
}
68+
}
69+
writer.Flush()
70+
return writer.Error()
71+
default:
72+
return fmt.Errorf("unsupported export format %q", format)
73+
}
74+
}
75+
76+
func secureCellGovernmentAgentExecutionLaunchClosureAutomationPacketCSVRows(packet *securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationPacket) [][]string {
77+
rows := [][]string{{
78+
"packet_id",
79+
"board_id",
80+
"summary_id",
81+
"jurisdiction",
82+
"service_code_filter",
83+
"service_tier_filter",
84+
"evaluated_at",
85+
"focus_lane",
86+
"focus_action",
87+
"cell_count",
88+
"item_count",
89+
"step_sequence",
90+
"step_lane",
91+
"step_pending_action",
92+
"step_automation_action",
93+
"step_instruction",
94+
"step_cell_ids",
95+
"item_cell_id",
96+
"item_name",
97+
"item_lane",
98+
"item_pending_action",
99+
"item_automation_action",
100+
"item_action_priority",
101+
"item_due_at",
102+
"item_overdue_seconds",
103+
"item_escalation_needed",
104+
"item_action_digest",
105+
"packet_digest",
106+
"board_digest",
107+
"generated_at",
108+
}}
109+
if packet == nil {
110+
return rows
111+
}
112+
if len(packet.Items) == 0 && len(packet.Steps) == 0 {
113+
rows = append(rows, []string{
114+
packet.PacketID,
115+
packet.BoardID,
116+
packet.SummaryID,
117+
packet.Jurisdiction,
118+
packet.ServiceCode,
119+
packet.ServiceTier,
120+
packet.EvaluatedAt.UTC().Format(time.RFC3339Nano),
121+
string(packet.FocusLane),
122+
packet.FocusAction,
123+
strconv.Itoa(packet.CellCount),
124+
strconv.Itoa(packet.ItemCount),
125+
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
126+
packet.PacketDigest,
127+
packet.BoardDigest,
128+
packet.GeneratedAt.UTC().Format(time.RFC3339Nano),
129+
})
130+
return rows
131+
}
132+
steps := packet.Steps
133+
if len(steps) == 0 {
134+
steps = []securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationPacketStep{{}}
135+
}
136+
items := packet.Items
137+
if len(items) == 0 {
138+
items = []securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationBoardItem{{}}
139+
}
140+
maxLen := len(steps)
141+
if len(items) > maxLen {
142+
maxLen = len(items)
143+
}
144+
for i := 0; i < maxLen; i++ {
145+
var step securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationPacketStep
146+
var item securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationBoardItem
147+
if i < len(steps) {
148+
step = steps[i]
149+
}
150+
if i < len(items) {
151+
item = items[i]
152+
}
153+
dueAt := ""
154+
if item.DueAt != nil {
155+
dueAt = item.DueAt.UTC().Format(time.RFC3339Nano)
156+
}
157+
stepSequence := ""
158+
if step.Sequence > 0 {
159+
stepSequence = strconv.Itoa(step.Sequence)
160+
}
161+
rows = append(rows, []string{
162+
packet.PacketID,
163+
packet.BoardID,
164+
packet.SummaryID,
165+
packet.Jurisdiction,
166+
packet.ServiceCode,
167+
packet.ServiceTier,
168+
packet.EvaluatedAt.UTC().Format(time.RFC3339Nano),
169+
string(packet.FocusLane),
170+
packet.FocusAction,
171+
strconv.Itoa(packet.CellCount),
172+
strconv.Itoa(packet.ItemCount),
173+
stepSequence,
174+
step.Lane,
175+
step.PendingAction,
176+
step.AutomationAction,
177+
step.Instruction,
178+
strings.Join(step.CellIDs, "|"),
179+
item.CellID,
180+
item.Name,
181+
string(item.Lane),
182+
item.PendingAction,
183+
item.AutomationAction,
184+
string(item.ActionPriority),
185+
dueAt,
186+
strconv.FormatInt(item.OverdueSeconds, 10),
187+
strconv.FormatBool(item.EscalationNeeded),
188+
item.ActionDigest,
189+
packet.PacketDigest,
190+
packet.BoardDigest,
191+
packet.GeneratedAt.UTC().Format(time.RFC3339Nano),
192+
})
193+
}
194+
return rows
195+
}

app/secure_cells_api_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,29 @@ func TestSecureCellGovernmentAgentExecutionLaunchClosureAutomationBoardCSVRows_E
9292
}
9393
}
9494

95+
func TestSecureCellGovernmentAgentExecutionLaunchClosureAutomationPacketCSVRows_EmptyPacketColumnCount(t *testing.T) {
96+
packet := &securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationPacket{
97+
PacketID: "government-agent-execution-launch-closure-automation-packet:UAE:abcdef123456",
98+
BoardID: "government-agent-execution-launch-closure-automation-board:UAE:abcdef123456",
99+
SummaryID: "government-agent-execution-launch-closure-automation-summary:UAE:abcdef123456",
100+
Jurisdiction: "UAE",
101+
EvaluatedAt: time.Unix(1, 0).UTC(),
102+
FocusLane: securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationBoardLaneDue,
103+
FocusAction: "work_next_due_closure_actions",
104+
BoardDigest: strings.Repeat("a", 64),
105+
PacketDigest: strings.Repeat("b", 64),
106+
GeneratedAt: time.Unix(2, 0).UTC(),
107+
}
108+
109+
rows := secureCellGovernmentAgentExecutionLaunchClosureAutomationPacketCSVRows(packet)
110+
if len(rows) != 2 {
111+
t.Fatalf("expected header plus empty-packet row, got %d rows", len(rows))
112+
}
113+
if got, want := len(rows[1]), len(rows[0]); got != want {
114+
t.Fatalf("expected empty-packet csv row to have %d columns, got %d: %#v", want, got, rows[1])
115+
}
116+
}
117+
95118
func TestSecureCellsHandlers_BearerCreateGetArtifactsFlow(t *testing.T) {
96119
app := newAuditEnabledTestApp(t, sims.AppOptionsMap{
97120
"aethelred.pqc.mode": "simulated",
@@ -1489,6 +1512,36 @@ func TestSecureCellsHandlers_GovernmentAgentReadinessSurfaces(t *testing.T) {
14891512
if !strings.Contains(launchClosureAutomationBoardExportRec.Body.String(), "board_digest") || !strings.Contains(launchClosureAutomationBoardExportRec.Body.String(), createResp.Result.CellID) {
14901513
t.Fatalf("expected government-agent execution launch closure automation board csv export, got %s", launchClosureAutomationBoardExportRec.Body.String())
14911514
}
1515+
1516+
launchClosureAutomationPacketReq := httptest.NewRequest(http.MethodGet, secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-packet?jurisdiction=UAE&before="+url.QueryEscape(overdueBefore), nil)
1517+
launchClosureAutomationPacketRec := httptest.NewRecorder()
1518+
app.SecureCellsGetHandler().ServeHTTP(launchClosureAutomationPacketRec, launchClosureAutomationPacketReq)
1519+
if launchClosureAutomationPacketRec.Code != http.StatusOK {
1520+
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, launchClosureAutomationPacketRec.Code, launchClosureAutomationPacketRec.Body.String())
1521+
}
1522+
var launchClosureAutomationPacketResp secureCellGovernmentAgentExecutionLaunchClosureAutomationPacketResponse
1523+
if err := json.Unmarshal(launchClosureAutomationPacketRec.Body.Bytes(), &launchClosureAutomationPacketResp); err != nil {
1524+
t.Fatalf("unmarshal government-agent execution launch closure automation packet response: %v", err)
1525+
}
1526+
if launchClosureAutomationPacketResp.Packet == nil || launchClosureAutomationPacketResp.Packet.ItemCount != 1 {
1527+
t.Fatalf("unexpected government-agent execution launch closure automation packet response: %+v", launchClosureAutomationPacketResp.Packet)
1528+
}
1529+
if launchClosureAutomationPacketResp.Packet.PacketDigest == "" || len(launchClosureAutomationPacketResp.Packet.Steps) == 0 || launchClosureAutomationPacketResp.Packet.FocusAction == "" {
1530+
t.Fatalf("expected digest-bound execution launch closure automation packet, got %+v", launchClosureAutomationPacketResp.Packet)
1531+
}
1532+
if launchClosureAutomationPacketResp.Packet.BoardID != launchClosureAutomationBoardResp.Board.BoardID || launchClosureAutomationPacketResp.Packet.FocusAction == "" || launchClosureAutomationPacketResp.Packet.Items[0].CellID != createResp.Result.CellID || !slices.Contains(launchClosureAutomationPacketResp.Packet.Steps[0].CellIDs, createResp.Result.CellID) {
1533+
t.Fatalf("expected execution launch closure automation packet tied to board and cell, got %+v", launchClosureAutomationPacketResp.Packet)
1534+
}
1535+
1536+
launchClosureAutomationPacketExportReq := httptest.NewRequest(http.MethodGet, secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-packet/export?format=csv&jurisdiction=UAE&before="+url.QueryEscape(overdueBefore), nil)
1537+
launchClosureAutomationPacketExportRec := httptest.NewRecorder()
1538+
app.SecureCellsGetHandler().ServeHTTP(launchClosureAutomationPacketExportRec, launchClosureAutomationPacketExportReq)
1539+
if launchClosureAutomationPacketExportRec.Code != http.StatusOK {
1540+
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, launchClosureAutomationPacketExportRec.Code, launchClosureAutomationPacketExportRec.Body.String())
1541+
}
1542+
if !strings.Contains(launchClosureAutomationPacketExportRec.Body.String(), "packet_digest") || !strings.Contains(launchClosureAutomationPacketExportRec.Body.String(), createResp.Result.CellID) {
1543+
t.Fatalf("expected government-agent execution launch closure automation packet csv export, got %s", launchClosureAutomationPacketExportRec.Body.String())
1544+
}
14921545
}
14931546

14941547
func TestSecureCellsHandlers_BearerFederationLifecycleFlow(t *testing.T) {

0 commit comments

Comments
 (0)