Skip to content

Commit 950fd01

Browse files
Add launch closure acknowledgement receipt manifest
1 parent 6387674 commit 950fd01

5 files changed

Lines changed: 504 additions & 0 deletions

app/secure_cells_api.go

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

1369+
if app.handleSecureCellLaunchClosureAutomationAckReceiptManifestGet(w, r) {
1370+
return
1371+
}
1372+
13691373
if app.handleSecureCellGovernmentAgentExecutionLaunchReceiptValidationGet(w, r) {
13701374
return
13711375
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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 launchClosureAutomationAckReceiptManifest = securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationAcknowledgementReceiptManifest
15+
type launchClosureAutomationAckReceiptManifestItem = securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationAcknowledgementReceiptManifestItem
16+
17+
type secureCellLaunchClosureAutomationAckReceiptManifestResponse struct {
18+
Manifest *launchClosureAutomationAckReceiptManifest `json:"manifest,omitempty"`
19+
}
20+
21+
func (app *AethelredApp) handleSecureCellLaunchClosureAutomationAckReceiptManifestGet(w http.ResponseWriter, r *http.Request) bool {
22+
if r.URL.Path == secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-acknowledgement-receipt-manifest" {
23+
filter, err := parseSecureCellGovernmentAgentExecutionLaunchClosureOverdueActionFilter(r)
24+
if err != nil {
25+
writeSecureCellAPIError(w, http.StatusBadRequest, err.Error())
26+
return true
27+
}
28+
manifest, err := app.secureCellService.GetGovernmentAgentExecutionLaunchClosureAutomationAcknowledgementReceiptManifest(r.Context(), filter)
29+
if err != nil {
30+
writeSecureCellAPIError(w, http.StatusInternalServerError, err.Error())
31+
return true
32+
}
33+
writeSecureCellJSON(w, http.StatusOK, secureCellLaunchClosureAutomationAckReceiptManifestResponse{Manifest: manifest})
34+
return true
35+
}
36+
37+
if r.URL.Path == secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-acknowledgement-receipt-manifest/export" {
38+
filter, err := parseSecureCellGovernmentAgentExecutionLaunchClosureOverdueActionFilter(r)
39+
if err != nil {
40+
writeSecureCellAPIError(w, http.StatusBadRequest, err.Error())
41+
return true
42+
}
43+
manifest, err := app.secureCellService.GetGovernmentAgentExecutionLaunchClosureAutomationAcknowledgementReceiptManifest(r.Context(), filter)
44+
if err != nil {
45+
writeSecureCellAPIError(w, http.StatusInternalServerError, err.Error())
46+
return true
47+
}
48+
if err := writeSecureCellLaunchClosureAutomationAckReceiptManifestExport(w, r, manifest); err != nil {
49+
writeSecureCellAPIError(w, http.StatusBadRequest, err.Error())
50+
}
51+
return true
52+
}
53+
54+
return false
55+
}
56+
57+
func writeSecureCellLaunchClosureAutomationAckReceiptManifestExport(w http.ResponseWriter, r *http.Request, manifest *launchClosureAutomationAckReceiptManifest) error {
58+
format := secureCellExportFormat(r)
59+
switch format {
60+
case "json":
61+
writeSecureCellJSON(w, http.StatusOK, secureCellLaunchClosureAutomationAckReceiptManifestResponse{Manifest: manifest})
62+
return nil
63+
case "csv":
64+
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
65+
w.Header().Set("Content-Disposition", `attachment; filename="secure-cell-government-agent-execution-launch-closure-automation-acknowledgement-receipt-manifest.csv"`)
66+
writer := csv.NewWriter(w)
67+
for _, row := range secureCellLaunchClosureAutomationAckReceiptManifestCSVRows(manifest) {
68+
if err := writer.Write(row); err != nil {
69+
return fmt.Errorf("write government-agent-execution-launch-closure-automation-acknowledgement-receipt-manifest csv row: %w", err)
70+
}
71+
}
72+
writer.Flush()
73+
return writer.Error()
74+
default:
75+
return fmt.Errorf("unsupported export format %q", format)
76+
}
77+
}
78+
79+
func secureCellLaunchClosureAutomationAckReceiptManifestCSVRows(manifest *launchClosureAutomationAckReceiptManifest) [][]string {
80+
rows := [][]string{{
81+
"manifest_id",
82+
"acknowledgement_receipt_id",
83+
"acknowledgement_id",
84+
"directive_id",
85+
"dispatch_id",
86+
"brief_id",
87+
"runbook_id",
88+
"packet_id",
89+
"board_id",
90+
"summary_id",
91+
"jurisdiction",
92+
"service_code_filter",
93+
"service_tier_filter",
94+
"evaluated_at",
95+
"focus_lane",
96+
"focus_action",
97+
"severity",
98+
"receipt_status",
99+
"receipt_action",
100+
"receipt_due_at",
101+
"receipt_overdue_seconds",
102+
"evidence_count",
103+
"overdue_evidence_count",
104+
"escalation_evidence_count",
105+
"item_sequence",
106+
"item_evidence",
107+
"item_status",
108+
"item_responsible_role",
109+
"item_pending_actions",
110+
"item_cell_ids",
111+
"receipt_digest",
112+
"manifest_digest",
113+
"generated_at",
114+
}}
115+
if manifest == nil {
116+
return rows
117+
}
118+
items := manifest.Items
119+
if len(items) == 0 {
120+
items = []launchClosureAutomationAckReceiptManifestItem{{}}
121+
}
122+
for _, item := range items {
123+
rows = append(rows, secureCellLaunchClosureAutomationAckReceiptManifestCSVRow(manifest, item))
124+
}
125+
return rows
126+
}
127+
128+
func secureCellLaunchClosureAutomationAckReceiptManifestCSVRow(
129+
manifest *launchClosureAutomationAckReceiptManifest,
130+
item launchClosureAutomationAckReceiptManifestItem,
131+
) []string {
132+
receiptDueAt := ""
133+
if manifest.ReceiptDueAt != nil {
134+
receiptDueAt = manifest.ReceiptDueAt.UTC().Format(time.RFC3339Nano)
135+
}
136+
itemSequence := ""
137+
if item.Sequence > 0 {
138+
itemSequence = strconv.Itoa(item.Sequence)
139+
}
140+
return []string{
141+
manifest.ManifestID,
142+
manifest.AcknowledgementReceiptID,
143+
manifest.AcknowledgementID,
144+
manifest.DirectiveID,
145+
manifest.DispatchID,
146+
manifest.BriefID,
147+
manifest.RunbookID,
148+
manifest.PacketID,
149+
manifest.BoardID,
150+
manifest.SummaryID,
151+
manifest.Jurisdiction,
152+
manifest.ServiceCode,
153+
manifest.ServiceTier,
154+
manifest.EvaluatedAt.UTC().Format(time.RFC3339Nano),
155+
string(manifest.FocusLane),
156+
manifest.FocusAction,
157+
string(manifest.Severity),
158+
string(manifest.ReceiptStatus),
159+
manifest.ReceiptAction,
160+
receiptDueAt,
161+
strconv.FormatInt(manifest.ReceiptOverdueSeconds, 10),
162+
strconv.Itoa(manifest.EvidenceCount),
163+
strconv.Itoa(manifest.OverdueEvidenceCount),
164+
strconv.Itoa(manifest.EscalationEvidenceCount),
165+
itemSequence,
166+
item.Evidence,
167+
string(item.Status),
168+
item.ResponsibleRole,
169+
strings.Join(item.PendingActions, "|"),
170+
strings.Join(item.CellIDs, "|"),
171+
manifest.ReceiptDigest,
172+
manifest.ManifestDigest,
173+
manifest.GeneratedAt.UTC().Format(time.RFC3339Nano),
174+
}
175+
}

app/secure_cells_api_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,42 @@ func TestSecureCellLaunchClosureAutomationAckReceiptCSVRows_EmptyColumnCount(t *
304304
}
305305
}
306306

307+
func TestSecureCellLaunchClosureAutomationAckReceiptManifestCSVRows_EmptyColumnCount(t *testing.T) {
308+
receiptDueAt := time.Unix(1, 0).UTC()
309+
manifest := &securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationAcknowledgementReceiptManifest{
310+
ManifestID: "government-agent-execution-launch-closure-automation-acknowledgement-receipt-manifest:UAE:abcdef123456",
311+
AcknowledgementReceiptID: "government-agent-execution-launch-closure-automation-acknowledgement-receipt:UAE:abcdef123456",
312+
AcknowledgementID: "government-agent-execution-launch-closure-automation-acknowledgement:UAE:abcdef123456",
313+
DirectiveID: "government-agent-execution-launch-closure-automation-directive:UAE:abcdef123456",
314+
DispatchID: "government-agent-execution-launch-closure-automation-dispatch:UAE:abcdef123456",
315+
BriefID: "government-agent-execution-launch-closure-automation-brief:UAE:abcdef123456",
316+
RunbookID: "government-agent-execution-launch-closure-automation-runbook:UAE:abcdef123456",
317+
PacketID: "government-agent-execution-launch-closure-automation-packet:UAE:abcdef123456",
318+
BoardID: "government-agent-execution-launch-closure-automation-board:UAE:abcdef123456",
319+
SummaryID: "government-agent-execution-launch-closure-automation-summary:UAE:abcdef123456",
320+
Jurisdiction: "UAE",
321+
EvaluatedAt: time.Unix(2, 0).UTC(),
322+
FocusLane: securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationBoardLaneDue,
323+
FocusAction: "work_next_due_closure_actions",
324+
Severity: securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationBriefSeverityMedium,
325+
ReceiptStatus: securecellsintegration.SecureCellGovernmentAgentExecutionLaunchClosureAutomationAcknowledgementReceiptStatusOverdue,
326+
ReceiptAction: "recover_missing_launch_closure_ack_receipt",
327+
ReceiptDueAt: &receiptDueAt,
328+
ReceiptOverdueSeconds: 1,
329+
ReceiptDigest: strings.Repeat("a", 64),
330+
ManifestDigest: strings.Repeat("b", 64),
331+
GeneratedAt: time.Unix(3, 0).UTC(),
332+
}
333+
334+
rows := secureCellLaunchClosureAutomationAckReceiptManifestCSVRows(manifest)
335+
if len(rows) != 2 {
336+
t.Fatalf("expected header plus empty-acknowledgement-receipt-manifest row, got %d rows", len(rows))
337+
}
338+
if got, want := len(rows[1]), len(rows[0]); got != want {
339+
t.Fatalf("expected empty-acknowledgement-receipt-manifest csv row to have %d columns, got %d: %#v", want, got, rows[1])
340+
}
341+
}
342+
307343
func TestSecureCellsHandlers_BearerCreateGetArtifactsFlow(t *testing.T) {
308344
app := newAuditEnabledTestApp(t, sims.AppOptionsMap{
309345
"aethelred.pqc.mode": "simulated",
@@ -1911,6 +1947,36 @@ func TestSecureCellsHandlers_GovernmentAgentReadinessSurfaces(t *testing.T) {
19111947
if !strings.Contains(launchClosureAutomationAckReceiptExportRec.Body.String(), "receipt_digest") || !strings.Contains(launchClosureAutomationAckReceiptExportRec.Body.String(), createResp.Result.CellID) {
19121948
t.Fatalf("expected government-agent execution launch closure automation acknowledgement receipt csv export, got %s", launchClosureAutomationAckReceiptExportRec.Body.String())
19131949
}
1950+
1951+
launchClosureAutomationAckReceiptManifestReq := httptest.NewRequest(http.MethodGet, secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-acknowledgement-receipt-manifest?jurisdiction=UAE&before="+url.QueryEscape(overdueBefore), nil)
1952+
launchClosureAutomationAckReceiptManifestRec := httptest.NewRecorder()
1953+
app.SecureCellsGetHandler().ServeHTTP(launchClosureAutomationAckReceiptManifestRec, launchClosureAutomationAckReceiptManifestReq)
1954+
if launchClosureAutomationAckReceiptManifestRec.Code != http.StatusOK {
1955+
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, launchClosureAutomationAckReceiptManifestRec.Code, launchClosureAutomationAckReceiptManifestRec.Body.String())
1956+
}
1957+
var launchClosureAutomationAckReceiptManifestResp secureCellLaunchClosureAutomationAckReceiptManifestResponse
1958+
if err := json.Unmarshal(launchClosureAutomationAckReceiptManifestRec.Body.Bytes(), &launchClosureAutomationAckReceiptManifestResp); err != nil {
1959+
t.Fatalf("unmarshal government-agent execution launch closure automation acknowledgement receipt manifest response: %v", err)
1960+
}
1961+
if launchClosureAutomationAckReceiptManifestResp.Manifest == nil || launchClosureAutomationAckReceiptManifestResp.Manifest.OverdueEvidenceCount == 0 {
1962+
t.Fatalf("unexpected government-agent execution launch closure automation acknowledgement receipt manifest response: %+v", launchClosureAutomationAckReceiptManifestResp.Manifest)
1963+
}
1964+
if launchClosureAutomationAckReceiptManifestResp.Manifest.ManifestDigest == "" || launchClosureAutomationAckReceiptManifestResp.Manifest.EvidenceCount == 0 || len(launchClosureAutomationAckReceiptManifestResp.Manifest.Items) == 0 {
1965+
t.Fatalf("expected digest-bound execution launch closure automation acknowledgement receipt manifest, got %+v", launchClosureAutomationAckReceiptManifestResp.Manifest)
1966+
}
1967+
if launchClosureAutomationAckReceiptManifestResp.Manifest.AcknowledgementReceiptID == "" || len(launchClosureAutomationAckReceiptManifestResp.Manifest.Items[0].CellIDs) == 0 {
1968+
t.Fatalf("expected populated execution launch closure automation acknowledgement receipt manifest, got %+v", launchClosureAutomationAckReceiptManifestResp.Manifest)
1969+
}
1970+
1971+
launchClosureAutomationAckReceiptManifestExportReq := httptest.NewRequest(http.MethodGet, secureCellsCollectionRoute+"/government-agent-execution-launch-closure-automation-acknowledgement-receipt-manifest/export?format=csv&jurisdiction=UAE&before="+url.QueryEscape(overdueBefore), nil)
1972+
launchClosureAutomationAckReceiptManifestExportRec := httptest.NewRecorder()
1973+
app.SecureCellsGetHandler().ServeHTTP(launchClosureAutomationAckReceiptManifestExportRec, launchClosureAutomationAckReceiptManifestExportReq)
1974+
if launchClosureAutomationAckReceiptManifestExportRec.Code != http.StatusOK {
1975+
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, launchClosureAutomationAckReceiptManifestExportRec.Code, launchClosureAutomationAckReceiptManifestExportRec.Body.String())
1976+
}
1977+
if !strings.Contains(launchClosureAutomationAckReceiptManifestExportRec.Body.String(), "manifest_digest") || !strings.Contains(launchClosureAutomationAckReceiptManifestExportRec.Body.String(), createResp.Result.CellID) {
1978+
t.Fatalf("expected government-agent execution launch closure automation acknowledgement receipt manifest csv export, got %s", launchClosureAutomationAckReceiptManifestExportRec.Body.String())
1979+
}
19141980
}
19151981

19161982
func TestSecureCellsHandlers_BearerFederationLifecycleFlow(t *testing.T) {

0 commit comments

Comments
 (0)