Compare commits
6 Commits
38e282a126
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 24d4a3b146 | |||
|
|
8b5b5d94d8 | ||
|
|
76bcb04c8f | ||
|
|
88b97cef2e | ||
|
|
e4b1d6cb57 | ||
|
|
a9fe2fc528 |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Mimic is a **BAS (Breach and Attack Simulation) purple-team console** — not a product catalog, not a marketing page. The aesthetic is **Bloomberg Terminal / SOC dashboard**: dense, angular, semantic-color-driven, zero ornament. Every surface decision reinforces operational trust: data is primary, chrome is invisible.
|
Mimic is a **BAS (Breach and Attack Simulation) purple-team console** — not a product catalog, not a marketing page. The aesthetic is **Bloomberg Terminal / SOC dashboard**: dense, angular, semantic-color-driven, zero ornament. Every surface decision reinforces operational trust: data is primary, chrome is invisible.
|
||||||
|
|
||||||
The system sits on a **pure-white canvas** (light) / **dark slab** (dark) with one chromatic action color — **Electric Blue** (`{colors.primary}` — `#024ad8`) — and semantic status signals (`success`, `warn`, `bloom-deep`). Inter handles body/headers/labels. JetBrains Mono carries data: IDs, ISO dates, commands, execution output, MITRE technique codes, metrics — anything that must be read at a glance without typographic distraction.
|
The system sits on a **pale-tinted canvas** (light: `#f3f5f8`) / **dark slab** (dark) with one chromatic action color — **Electric Blue** (`{colors.primary}` — `#024ad8`) — and semantic status signals (`success`, `warn`, `bloom-deep`). Inter handles body/headers/labels. JetBrains Mono carries data: IDs, ISO dates, commands, execution output, MITRE technique codes, metrics — anything that must be read at a glance without typographic distraction.
|
||||||
|
|
||||||
**No rounded corners on containers.** No shadows on interactive surfaces. No transitions. Hover is instantaneous. Focus rings are sharp. This is a tool, not a storefront.
|
**No rounded corners on containers.** No shadows on interactive surfaces. No transitions. Hover is instantaneous. Focus rings are sharp. This is a tool, not a storefront.
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ The system sits on a **pure-white canvas** (light) / **dark slab** (dark) with o
|
|||||||
- **Soft Blue** (`{colors.primary-soft}` — `#c9e0fc`): selection highlight, chip background on light surfaces.
|
- **Soft Blue** (`{colors.primary-soft}` — `#c9e0fc`): selection highlight, chip background on light surfaces.
|
||||||
|
|
||||||
### Surface
|
### Surface
|
||||||
- **Canvas** (`{colors.canvas}` — `#ffffff` light / `#111827` dark): universal page background.
|
- **Canvas** (`{colors.canvas}` — `#f3f5f8` light / `#111827` dark): universal page background. In light mode, canvas is tinted while paper stays pure white so cards lift without shadow or radius, preserving brutalism.
|
||||||
- **Paper** (`{colors.paper}` — `#ffffff` light / `#1f2937` dark): card and panel surfaces.
|
- **Paper** (`{colors.paper}` — `#ffffff` light / `#1f2937` dark): card and panel surfaces.
|
||||||
- **Cloud** (`{colors.cloud}` — `#f7f7f7` light / `#1f2937` dark): alternating section band, table row zebra.
|
- **Cloud** (`{colors.cloud}` — `#f7f7f7` light / `#1f2937` dark): alternating section band, table row zebra.
|
||||||
- **Fog** (`{colors.fog}` — `#e8e8e8` light / `#374151` dark): secondary section band, input background on dense panels.
|
- **Fog** (`{colors.fog}` — `#e8e8e8` light / `#374151` dark): secondary section band, input background on dense panels.
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function ExportEngagementButton({ engagementId }: ExportEngagementButtonP
|
|||||||
|
|
||||||
{open ? (
|
{open ? (
|
||||||
<div
|
<div
|
||||||
className="absolute right-0 top-full mt-xxs bg-canvas border border-hairline rounded-none z-20 min-w-[160px]"
|
className="absolute right-0 top-full mt-xxs bg-paper border border-hairline rounded-none z-20 min-w-[160px]"
|
||||||
role="menu"
|
role="menu"
|
||||||
>
|
>
|
||||||
{FORMATS.map(({ label, value }) => (
|
{FORMATS.map(({ label, value }) => (
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export function MitreMatrixModal({
|
|||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="matrix-modal-title"
|
aria-labelledby="matrix-modal-title"
|
||||||
className="relative bg-canvas rounded-none border border-hairline max-w-[98vw] max-h-[80vh] overflow-hidden flex flex-col"
|
className="relative bg-paper rounded-none border border-hairline max-w-[98vw] max-h-[80vh] overflow-hidden flex flex-col"
|
||||||
style={{ width: '1400px' }}
|
style={{ width: '1400px' }}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function MitreTechniquePicker({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{open && (
|
{open && (
|
||||||
<div className="absolute z-20 w-full mt-xxs bg-canvas border border-steel rounded-none overflow-hidden">
|
<div className="absolute z-20 w-full mt-xxs bg-paper border border-steel rounded-none overflow-hidden">
|
||||||
{isFetching && (
|
{isFetching && (
|
||||||
<div className="px-md py-sm text-[14px] text-graphite">Searching…</div>
|
<div className="px-md py-sm text-[14px] text-graphite">Searching…</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function NewSimulationDropdown({ engagementId }: { engagementId: number }): JSX.
|
|||||||
|
|
||||||
{open ? (
|
{open ? (
|
||||||
<div
|
<div
|
||||||
className="absolute right-0 top-full mt-xxs bg-canvas border border-hairline rounded-none z-20 min-w-[180px]"
|
className="absolute right-0 top-full mt-xxs bg-paper border border-hairline rounded-none z-20 min-w-[180px]"
|
||||||
role="menu"
|
role="menu"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export function EngagementFormPage(): JSX.Element {
|
|||||||
const submitting = createMutation.isPending || patchMutation.isPending;
|
const submitting = createMutation.isPending || patchMutation.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-xl max-w-2xl">
|
<div className="flex flex-col gap-xl">
|
||||||
<header>
|
<header>
|
||||||
<h1 className="text-[32px] font-medium leading-none">
|
<h1 className="text-[32px] font-medium leading-none">
|
||||||
{editing ? 'Edit engagement' : 'New engagement'}
|
{editing ? 'Edit engagement' : 'New engagement'}
|
||||||
@@ -136,6 +136,13 @@ export function EngagementFormPage(): JSX.Element {
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
editing && canEditEngagements
|
||||||
|
? 'grid grid-cols-1 lg:grid-cols-2 gap-xl items-start'
|
||||||
|
: 'max-w-2xl'
|
||||||
|
}
|
||||||
|
>
|
||||||
<form onSubmit={onSubmit} noValidate className="card-product flex flex-col gap-md">
|
<form onSubmit={onSubmit} noValidate className="card-product flex flex-col gap-md">
|
||||||
<FormField label="Name" htmlFor="eng-name" required error={errors.name}>
|
<FormField label="Name" htmlFor="eng-name" required error={errors.name}>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -222,5 +229,6 @@ export function EngagementFormPage(): JSX.Element {
|
|||||||
<C2ConfigCard engagementId={numericId} />
|
<C2ConfigCard engagementId={numericId} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
/* Light mode — default */
|
/* Light mode — default */
|
||||||
:root {
|
:root {
|
||||||
--color-canvas: #ffffff;
|
--color-canvas: #f3f5f8;
|
||||||
--color-paper: #ffffff;
|
--color-paper: #ffffff;
|
||||||
--color-cloud: #f7f7f7;
|
--color-cloud: #f7f7f7;
|
||||||
--color-fog: #e8e8e8;
|
--color-fog: #e8e8e8;
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline {
|
.btn-outline {
|
||||||
@apply inline-flex items-center justify-center gap-xs bg-canvas text-primary border border-primary uppercase font-semibold text-[14px] leading-[1.4] rounded-none px-xl py-sm h-11;
|
@apply inline-flex items-center justify-center gap-xs bg-paper text-primary border border-primary uppercase font-semibold text-[14px] leading-[1.4] rounded-none px-xl py-sm h-11;
|
||||||
}
|
}
|
||||||
.btn-outline:hover {
|
.btn-outline:hover {
|
||||||
@apply bg-primary-soft;
|
@apply bg-primary-soft;
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-ink {
|
.btn-outline-ink {
|
||||||
@apply inline-flex items-center justify-center gap-xs bg-canvas text-ink border border-ink uppercase font-semibold text-[14px] leading-[1.4] rounded-none px-xl py-sm h-11;
|
@apply inline-flex items-center justify-center gap-xs bg-paper text-ink border border-ink uppercase font-semibold text-[14px] leading-[1.4] rounded-none px-xl py-sm h-11;
|
||||||
}
|
}
|
||||||
.btn-outline-ink:hover {
|
.btn-outline-ink:hover {
|
||||||
@apply bg-cloud;
|
@apply bg-cloud;
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-input {
|
.text-input {
|
||||||
@apply block w-full bg-canvas text-ink rounded-none border border-steel px-md py-sm h-11 text-[16px] leading-[1.4] focus:outline-none focus:border-primary;
|
@apply block w-full bg-paper text-ink rounded-none border border-steel px-md py-sm h-11 text-[16px] leading-[1.4] focus:outline-none focus:border-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Panel / card — hairline border, no shadow, no radius */
|
/* Panel / card — hairline border, no shadow, no radius */
|
||||||
|
|||||||
132
tasks/todo.md
132
tasks/todo.md
@@ -1,95 +1,73 @@
|
|||||||
# Sprint 8 — C2 Layer: Mythic Integration
|
# Sprint 9 — UI: engagement 2-col + global contrast pass
|
||||||
|
|
||||||
> Branch : `sprint/8-c2` · Worktree : `.claude/worktrees/sprint-8-c2` · Base : `main` @ `6ca614a`
|
**Base**: `sprint/8-c2` (sprint 8 not yet merged on origin/main, but its `C2ConfigCard` is the right pane).
|
||||||
|
**Scope**: frontend-only. No backend, no schema. No new features.
|
||||||
|
|
||||||
SPEC.md phase 2: "après que la V1 soit terminée, nous ajouterons une couche permettant
|
---
|
||||||
de se connecter à un C2 (mythic ou maison) afin d'exécuter des simulations au travers du C2."
|
|
||||||
|
|
||||||
## §0 — Binding decisions (locked with user, 2026-06-10)
|
## Decisions (locked)
|
||||||
|
|
||||||
1. **C2 target**: Mythic 3.x, behind a thin `C2Adapter` interface (keeps the door open for a custom C2 later).
|
1. **Engagement page** : passer en **2 colonnes** sur desktop (`lg:grid-cols-2`), `[engagement form | C2ConfigCard]`. Mobile/tablet : stack vertical (comportement actuel).
|
||||||
2. **Scope**: FULL bidirectional — execute + near-real-time tracking + history import. User explicitly overrode the "one-shot first" recommendation; overrun risk accepted (see R1).
|
2. **Contraste global** : le problème est que `canvas` (page bg) et `paper` (card bg) sont **tous deux `#ffffff`** en light mode. Les cartes ne ressortent que par leur hairline 1px → fatigue oculaire confirmée par l'utilisateur.
|
||||||
3. **C2 config**: per-engagement (URL + API token). Token is write-only at the API level — never returned in clear.
|
3. **Fix retenu** : **tinter le canvas light** d'un neutre froid très pâle. `paper` reste blanc pur. Les cartes "lèvent" naturellement sans casser le brutalisme.
|
||||||
4. **UI anchor**: execution lives in the simulation screen (Red Team card).
|
- Proposition : `canvas` light `#f3f5f8` (gris-bleu très pâle, cohérent avec l'electric blue), `paper` light `#ffffff`.
|
||||||
5. **Realtime mechanism**: short polling. Frontend: TanStack Query `refetchInterval` 2 500 ms while any attached task is incomplete. Backend: poll-on-read — refreshes non-completed tasks from Mythic when the task list is read. No scheduler, no new infra.
|
- Dark mode **inchangé** (`canvas #111827` / `paper #1f2937` déjà différenciés).
|
||||||
6. **Secret storage**: API token Fernet-encrypted in SQLite. Key from env var `MIMIC_ENCRYPTION_KEY` (mandatory to enable C2 features, never hardcoded — OPSEC rule).
|
4. **Pas de shadow**, pas de radius. La brutalité reste intacte — seul le contraste de surface change.
|
||||||
7. **History import**: BOTH — auto-attach of tasks launched from Mimic AND manual browse/select of the callback's task history.
|
5. **Hairline** : à vérifier sur le nouveau canvas. Si nécessaire, passer `hairline` light de la valeur actuelle à un poil plus sombre pour préserver la lisibilité du bord sur tinted canvas. Mais éviter si la lecture est déjà bonne.
|
||||||
8. **Validation**: fully mocked — no dev Mythic instance available. pytest uses a mocked adapter; e2e uses a built-in `FakeAdapter` selected via `MIMIC_C2_ADAPTER=fake`.
|
|
||||||
9. **RBAC**: C2 is a RedTeam resource — admin + redteam full access, SOC gets 403 on every C2 endpoint (same precedent as Templates and Export).
|
|
||||||
10. **Workflow tie-in**: launching a C2 execution auto-transitions the simulation `pending → in_progress` (same rule as a manual RT edit).
|
|
||||||
11. **Output mapping**: when a task completes (or is imported), its output is APPENDED to `execution_result` prefixed by a `$ <command>` header line; `executed_at` is set from the first task's timestamp if empty; the command is appended to `commands` if not already present.
|
|
||||||
|
|
||||||
## §1 — Mythic adapter contract (pinned from MythicMeta/Mythic_Scripting client source)
|
---
|
||||||
|
|
||||||
- Transport: POST `https://<host>:7443/graphql`, header `apitoken: <token>` (Hasura behind nginx).
|
## Task A — EngagementFormPage 2-col
|
||||||
- Issue task: mutation `createTask(callback_id: <display_id>, command, params, tasking_location: "command_line")`.
|
|
||||||
- List callbacks: query on `callback` table — `id, display_id, active, host, user, domain, last_checkin`.
|
|
||||||
- Task status: query on `task` table — `display_id, status, completed` (client lib uses a subscription; we poll the query instead, per decision 5).
|
|
||||||
- Task output: query on `response` table ordered by id — `response_text` is **base64-encoded**, decode + concatenate.
|
|
||||||
- History: query `task` filtered by `callback_display_id`, paginated, with command + status + timestamps.
|
|
||||||
|
|
||||||
`C2Adapter` interface (`backend/app/services/c2/adapter.py`):
|
**File** : `frontend/src/pages/EngagementFormPage.tsx`
|
||||||
- `test_connection() -> C2Health`
|
|
||||||
- `list_callbacks() -> list[C2Callback]`
|
|
||||||
- `create_task(callback_display_id, command, params) -> int` (task display_id)
|
|
||||||
- `get_task(task_display_id) -> C2TaskStatus`
|
|
||||||
- `get_task_output(task_display_id) -> str` (decoded)
|
|
||||||
- `list_callback_tasks(callback_display_id, page, page_size) -> C2TaskPage`
|
|
||||||
|
|
||||||
Implementations: `MythicAdapter` (requests, `verify_tls` flag from config — lab instances run self-signed), `FakeAdapter` (deterministic in-memory data, selected by `MIMIC_C2_ADAPTER=fake`, also powers e2e).
|
- Remplacer le wrapper `<div className="flex flex-col gap-xl max-w-2xl">` par un container plus large + grid 2-col responsive.
|
||||||
|
- Header reste en haut, full width.
|
||||||
|
- Body : `grid grid-cols-1 lg:grid-cols-2 gap-xl` avec :
|
||||||
|
- Col gauche : `<form>` engagement (déjà en `card-product`).
|
||||||
|
- Col droite : `<C2ConfigCard>` (seulement quand `editing && canEditEngagements`).
|
||||||
|
- Si pas en edit (création) : col droite vide → garder la grid mais que la col gauche se déploie via `lg:col-span-2` (pour pas avoir un vide à droite). Acceptable alternative : `flex` + `max-w-2xl` quand non-editing.
|
||||||
|
- Pas de modif sur la logique de form, validation, mutations.
|
||||||
|
|
||||||
## §2 — Backend (backend-builder) — milestones M1→M4
|
## Task B — Contrast pass (tokens)
|
||||||
|
|
||||||
**Data model (1 Alembic migration):**
|
**Files** :
|
||||||
- `c2_config`: id, engagement_id FK **unique** (`ON DELETE CASCADE`, same precedent as `simulations.engagement_id`), url, api_token_encrypted, verify_tls bool (default true), created_at, updated_at.
|
- `DESIGN.md` § Surface : mettre à jour `canvas` light = `#f3f5f8`, conserver `paper` light = `#ffffff`. Documenter dans la même section que "canvas tints lift paper cards in light mode without violating brutalism".
|
||||||
- `c2_task`: id, simulation_id FK (`ON DELETE CASCADE`), mythic_task_display_id int, callback_display_id int, command text, params text nullable, status text, completed bool, output text nullable, source enum(`mimic`,`import`), created_at, completed_at nullable.
|
- Token source de vérité (Tailwind config ou CSS vars). Localiser et appliquer la même MAJ. Probablement `frontend/tailwind.config.js` ou un `frontend/src/styles/tokens.css` / `index.css`.
|
||||||
|
- Vérifier qu'aucun composant ne hardcode `#ffffff` pour la page bg (devrait utiliser `bg-canvas`).
|
||||||
|
- Tests CSS smoke : `bg-canvas` continue de matcher, dark mode inchangé.
|
||||||
|
|
||||||
**Endpoints (all admin+redteam; SOC → 403):**
|
## Task C — Visual regression check
|
||||||
- `GET/PUT/DELETE /api/engagements/<id>/c2-config` — GET returns `has_token: bool`, never the token.
|
|
||||||
- `POST /api/engagements/<id>/c2-config/test` — connectivity check via adapter.
|
|
||||||
- `GET /api/engagements/<id>/c2/callbacks` — active callbacks.
|
|
||||||
- `POST /api/simulations/<id>/c2/execute` `{callback_display_id, commands: [str]}` — one Mythic task per command, rows in `c2_task` (source=mimic), auto-transition pending→in_progress.
|
|
||||||
- `GET /api/simulations/<id>/c2/tasks` — poll-on-read: refresh incomplete tasks from Mythic; on completion fetch output and apply decision 11 mapping (idempotent, applied once).
|
|
||||||
- `GET /api/engagements/<id>/c2/callbacks/<cid>/history?page=` — paginated callback history for import.
|
|
||||||
- `POST /api/simulations/<id>/c2/import` `{task_display_ids: [int]}` — import selected tasks (source=import) + decision 11 mapping.
|
|
||||||
|
|
||||||
**Milestones:** M1 crypto service (Fernet) + migration + config CRUD + test endpoint → M2 callbacks + execute → M3 poll-on-read status/output + mapping → M4 history + import.
|
- `pnpm vitest run` clean.
|
||||||
**Tests:** pytest with mocked adapter (~35-45 new), zero live HTTP. Crypto round-trip, RBAC 403 matrix, mapping idempotence, migration up/down.
|
- `pnpm tsc --noEmit` clean.
|
||||||
|
- `pnpm lint` clean.
|
||||||
|
- Screenshots avant/après (au moins) :
|
||||||
|
- EngagementsListPage (cards-on-canvas)
|
||||||
|
- EngagementDetailPage
|
||||||
|
- EngagementFormPage (edit, avec C2ConfigCard à droite)
|
||||||
|
- SimulationFormPage (déjà 2-col sprint 7, vérifier que le tinted canvas n'écrase pas)
|
||||||
|
- LoginPage
|
||||||
|
- Dark mode : passe rapide pour confirmer aucune régression.
|
||||||
|
|
||||||
## §3 — Frontend (frontend-builder)
|
---
|
||||||
|
|
||||||
- **Engagement detail/form**: "C2 configuration" card — URL, token (password input, write-only placeholder when `has_token`), verify-TLS checkbox, [Test connection] with result feedback. Admin+redteam only.
|
## Sequencing
|
||||||
- **SimulationFormPage RT card**: [Execute via C2] button (hidden when no config; disabled when done) → modal: callback picker table (display_id, host, user, active, last_checkin — font-mono data cells) → commands textarea prefilled from `rt.commands` → Launch.
|
|
||||||
- **C2 tasks panel** (under RT card): table of attached tasks — command (mono), status badge (semantic tokens), completed_at (mono). `refetchInterval` 2 500 ms while any incomplete; stops when all done.
|
|
||||||
- **Import history modal**: callback picker → paginated task list with checkboxes → [Import selected].
|
|
||||||
- Terminal-SOC compliance: rounded-none, zero transitions, hairline borders, mono for data only. Status colors via `success`/`warn` semantic tokens.
|
|
||||||
- Vitest ~15-20 new tests. `data-testid` on every new interactive surface for e2e.
|
|
||||||
|
|
||||||
## §4 — Docs & hygiene
|
1. **frontend-builder** : Task A + B + C. Une seule passe, commits atomiques.
|
||||||
|
2. **design-reviewer** : revue visuelle après merge des commits builder. Focus :
|
||||||
|
- Lecture confortable cards-on-tinted-canvas.
|
||||||
|
- Hairline encore visible.
|
||||||
|
- Dark mode inchangé.
|
||||||
|
- Pas de régression sur components qui pourraient ré-utiliser `bg-canvas` pour autre chose (dropdowns, modals).
|
||||||
|
|
||||||
- **SPEC.md C2 section = FIRST commit** of the sprint (sprint 5/6 lesson: never close a sprint with SPEC uncommitted).
|
---
|
||||||
- README: `MIMIC_ENCRYPTION_KEY`, `MIMIC_C2_ADAPTER` env vars + docker compose example.
|
|
||||||
- CHANGELOG sprint 8 entry at close.
|
|
||||||
|
|
||||||
## §5 — Pipeline & sequencing
|
## Definition of Done
|
||||||
|
|
||||||
1. spec-reviewer pre-pass on this plan (before any code).
|
- EngagementFormPage en édition : 2 colonnes desktop, stack mobile.
|
||||||
2. backend-builder M1+M2 → API contract frozen → frontend-builder starts (parallel with backend M3+M4).
|
- Page bg différencié de card bg en light mode (eyes confort).
|
||||||
3. design-reviewer on new UI surfaces (screenshots come from the Playwright run — e2e bootstraps its own users, which sidesteps the sprint-7 credentials wall).
|
- Vitest + typecheck + lint verts.
|
||||||
4. **code-reviewer must be respawned** (killed 2026-06-10 after idle-loop malfunction) before the review phase.
|
- Design-reviewer APPROVED.
|
||||||
5. test-verifier: new C2 Playwright specs (fake adapter) + **full 223-spec re-run** — also clears the sprint 7 e2e debt (suite never re-ran after the design refresh).
|
- Screenshots livrés ou écueil documenté.
|
||||||
|
- Commits conventional, branche `sprint/9-ui-contrast`.
|
||||||
## §6 — Risks
|
|
||||||
|
|
||||||
- **R1 — scope**: full bidirectional in one sprint. Mitigation: M1→M4 are ordered; M4 (history import) is the cut line if we overrun. User accepted explicitly.
|
|
||||||
- **R2 — schema fidelity**: no live Mythic to validate against; adapter pinned to the official `MythicMeta/Mythic_Scripting` client source on **master @ 2026-06-10** (verified via raw.githubusercontent.com). First real connection may need a small patch. README records the exact source URL alongside the "may need a patch" note. Adapter must defensively handle `response_text` base64 decode failures (binary output): on `binascii.Error` keep raw bytes hex-encoded with a prefix `<binary> ` so `execution_result` never silently corrupts.
|
|
||||||
- **R3 — secret at rest**: Fernet + env key; key rotation out of scope (documented limitation).
|
|
||||||
- **R4 — polling load**: poll-on-read touches only incomplete tasks of the open simulation — bounded.
|
|
||||||
- **R5 — e2e drift**: sprint 7 redesign never re-validated by Playwright; the full re-run in §5.5 surfaces any breakage — budget fix time.
|
|
||||||
|
|
||||||
## §7 — Definition of Done
|
|
||||||
|
|
||||||
- pytest green (256 baseline on main + new), vitest green (139 baseline + new), Playwright full suite green (223 baseline + new C2 specs).
|
|
||||||
- spec-reviewer (plan) + design-reviewer (UI) + code-reviewer (diff) all APPROVED.
|
|
||||||
- SPEC.md, README.md, CHANGELOG.md updated. No secret, key or IP hardcoded anywhere.
|
|
||||||
- PR #11 opened via `make open-pr`.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user