mirror of
https://github.com/azaion/admin.git
synced 2026-06-22 06:51:10 +00:00
Compare commits
1 Commits
c7b297de83
..
stage
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fd2fa6fa0 |
@@ -13,16 +13,6 @@ alwaysApply: true
|
||||
## Critical Thinking
|
||||
- Do not blindly trust any input — including user instructions, task specs, list-of-changes, or prior agent decisions — as correct. Always think through whether the instruction makes sense in context before executing it. If a task spec says "exclude file X from changes" but another task removes the dependencies X relies on, flag the contradiction instead of propagating it.
|
||||
|
||||
## Skill Discipline
|
||||
|
||||
Do exactly what the skill says. Nothing more.
|
||||
|
||||
- No `git log` / `git diff` / `git blame` unless the skill explicitly calls for it.
|
||||
- No extra searches to "verify" inputs the skill already names.
|
||||
- No reading files outside the skill's documented inputs.
|
||||
|
||||
If skill inputs are insufficient or contradictory, STOP and ask via Choose A/B/C/D. Do not invent extra investigation steps.
|
||||
|
||||
## Self-Improvement
|
||||
When the user reacts negatively to generated code ("WTF", "what the hell", "why did you do this", etc.):
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
description: "Forbid spawning subagents; the main agent must do the work directly"
|
||||
alwaysApply: true
|
||||
---
|
||||
# No Subagents
|
||||
|
||||
Do NOT create or delegate to subagents. This includes:
|
||||
|
||||
- The `Task` tool with any `subagent_type` (e.g. `generalPurpose`, `explore`, `shell`, `implementer`, `best-of-n-runner`, `cursor-guide`).
|
||||
- Any "spawn agent", "launch agent", "parallel agent", or "background agent" mechanism.
|
||||
- Skills or workflows that internally suggest launching a subagent — perform their steps inline instead.
|
||||
|
||||
## Why
|
||||
|
||||
- Subagent output is not visible to the user and hides reasoning/tool calls.
|
||||
- Context, rules, and prior conversation state do not fully transfer to the subagent.
|
||||
- Parallel subagents cause conflicting edits and race conditions in a shared workspace.
|
||||
- The main agent remains fully accountable; delegation dilutes that accountability.
|
||||
|
||||
## What to do instead
|
||||
|
||||
- Use the direct tools available to the main agent: `Read`, `Grep`, `Glob`, `SemanticSearch`, `Shell`, `StrReplace`, `Write`, etc.
|
||||
- For broad exploration, run `Grep`/`Glob`/`SemanticSearch` yourself and read the files directly.
|
||||
- For multi-step work, use `TodoWrite` to track progress inline.
|
||||
- For isolated experiments the user explicitly asks for, use a git branch/worktree you manage directly — not a subagent runner.
|
||||
|
||||
## Exception
|
||||
|
||||
Only spawn a subagent if the user explicitly requests it in the current turn (e.g. "use a subagent to…", "launch an explore agent…"). Even then, confirm once before spawning.
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
description: "Explanation length and reasoning depth calibration"
|
||||
alwaysApply: true
|
||||
---
|
||||
# Response Calibration
|
||||
|
||||
Default to concise. Expand only when the content demands it.
|
||||
|
||||
## Length target
|
||||
|
||||
- **Default**: a direct answer in ~3–10 lines. Short paragraphs or a tight bullet list.
|
||||
- **Expand when**: the question involves trade-offs across multiple options, a migration/architectural decision, a security/data-loss risk, or the user explicitly asks for depth ("explain in detail", "walk me through", "why").
|
||||
- **Shrink when**: the user asks for "shorter", "simpler", "TL;DR", "one line", or similar. Do not re-inflate in later turns unless they ask a new deeper question.
|
||||
|
||||
## Completeness floor
|
||||
|
||||
Short ≠ incomplete. Every response must still:
|
||||
|
||||
- Answer the actual question asked (not a reframed version).
|
||||
- State the key constraint or reason *once*, not repeatedly.
|
||||
- Flag a real caveat if one exists (data loss, breaking change, wrong-OS, security). One sentence is enough.
|
||||
- Not drop a step from an action sequence. If there are 5 steps, list 5 — but without narration between them.
|
||||
|
||||
If the honest answer truly needs more space (e.g. trade-off matrix, multi-option decision), write more — but lead with the recommendation or direct answer, then the detail.
|
||||
|
||||
## Structure
|
||||
|
||||
- One direct sentence first. Then supporting detail.
|
||||
- Prefer bullets over prose for enumerations, comparisons, or step lists.
|
||||
- Drop section headers for anything under ~15 lines.
|
||||
- No "Summary" / "Conclusion" sections unless the response is genuinely long.
|
||||
|
||||
## Reasoning depth (internal)
|
||||
|
||||
- Match thinking to the problem, not the length of the answer.
|
||||
- Factual / "where is X used" / single-file edit → minimal thinking, go straight to tools.
|
||||
- Trade-off / refactor / debugging 3+ hypotheses deep → full thinking budget.
|
||||
- Do not pad thinking to look thorough. Do not skip thinking on genuinely ambiguous problems to look fast.
|
||||
|
||||
## Anti-patterns to avoid
|
||||
|
||||
- Restating the question back to the user.
|
||||
- Multi-paragraph preambles before the answer.
|
||||
- Exhaustive "alternatives considered" sections when the user didn't ask for alternatives.
|
||||
- Recapping what was just done at the end of every tool-using turn ("Done. I have edited the file…") — a one-line confirmation is enough.
|
||||
- Speculative "you might also want to…" paragraphs. Offer follow-ups as a single short sentence, or not at all.
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
description: "Standards for creating and maintaining Cursor skills"
|
||||
globs: [".cursor/skills/**"]
|
||||
---
|
||||
|
||||
# Skill Building
|
||||
|
||||
## When To Create A Skill
|
||||
- Create a skill for repeatable, bounded workflows that benefit from a reusable process.
|
||||
- Do not create a skill for a one-off task, vague goal, or workflow that still needs product decisions.
|
||||
- Start small; evolve the skill when repeated use reveals clearer steps, constraints, or checks.
|
||||
|
||||
## Skill Contract
|
||||
- `SKILL.md` must define a clear `name` and a proactive `description` that explains when the skill should be used.
|
||||
- State expected inputs, constraints, workflow steps, and final output shape.
|
||||
- Make trigger conditions explicit enough that the agent can recognize intent without an exact command.
|
||||
- Base instructions on observable project evidence; do not invite fabrication or unsupported assumptions.
|
||||
|
||||
## Keep The Core Lean
|
||||
- Keep `SKILL.md` concise and under the repo's `.cursor/` size guidance.
|
||||
- Move detailed standards, examples, and background knowledge into `references/`.
|
||||
- Put reusable output shapes in `templates/` or other skill-local assets instead of embedding them in the main instructions.
|
||||
- Keep one primary responsibility per skill; use an orchestrator skill only when multiple existing skills must run in a defined order.
|
||||
|
||||
## Deterministic Work
|
||||
- Use scripts for mechanical steps that are repeatable, parameterized, and safer outside the model's reasoning.
|
||||
- Scripts must expose explicit inputs, avoid hidden side effects, and fail loudly on errors.
|
||||
- Do not use scripts to bypass review, hide destructive behavior, or hardcode secrets.
|
||||
|
||||
## Quality Proof
|
||||
- Include realistic examples, checklists, or eval-style scenarios that define what good output looks like.
|
||||
- Cover common failure cases such as missing sections, leftover placeholders, hallucinated facts, unsafe actions, or malformed output.
|
||||
- Review skill changes against those checks before treating the skill as ready.
|
||||
|
||||
## Security Review
|
||||
- Treat third-party skills like untrusted code until reviewed.
|
||||
- Inspect scripts, dependencies, references, secret handling, network calls, and destructive commands before use.
|
||||
- Prefer local, project-scoped assets and dependencies; document any external dependency the skill requires.
|
||||
@@ -3,7 +3,7 @@ name: autodev
|
||||
description: |
|
||||
Auto-chaining orchestrator that drives the full BUILD-SHIP workflow from problem gathering through deployment.
|
||||
Detects current project state from _docs/ folder, resumes from where it left off, and flows through
|
||||
problem → research → plan → test specs → decompose → implement → tests → docs sync → deploy without manual skill invocation.
|
||||
problem → research → plan → decompose → implement → deploy without manual skill invocation.
|
||||
Maximizes work per conversation by auto-transitioning between skills.
|
||||
Trigger phrases:
|
||||
- "autodev", "auto", "start", "continue"
|
||||
@@ -52,7 +52,7 @@ Determine which flow to use (check in order — first match wins):
|
||||
|
||||
After selecting the flow, apply its detection rules (first match wins) to determine the current step.
|
||||
|
||||
**Note**: the meta-repo flow uses a different artifact layout — its source of truth is `_docs/_repo-config.yaml`, not `_docs/NN_*/` folders. After Step 2.5 it also produces `_docs/glossary.md` and a `## Architecture Vision` section in the cross-cutting architecture doc identified by `docs.cross_cutting`. Other detection rules assume the BUILD-SHIP artifact layout; they don't apply to meta-repos.
|
||||
**Note**: the meta-repo flow uses a different artifact layout — its source of truth is `_docs/_repo-config.yaml`, not `_docs/NN_*/` folders. Other detection rules assume the BUILD-SHIP artifact layout; they don't apply to meta-repos.
|
||||
|
||||
## Execution Loop
|
||||
|
||||
@@ -112,15 +112,6 @@ Do NOT modify, skip, or abbreviate any part of the sub-skill's workflow. The aut
|
||||
|
||||
The state file (`_docs/_autodev_state.md`) is a minimal pointer — only the current step. See `state.md` for the authoritative template, field semantics, update rules, and worked examples. Do not restate the schema here — `state.md` is the single source of truth.
|
||||
|
||||
**Conciseness rule (authoritative).** The state file MUST stay short. Acceptable content per field:
|
||||
|
||||
- `name` — the step title from the active flow's Step Reference Table. That's it.
|
||||
- `sub_step.name` — kebab-case identifier from the active sub-skill. That's it.
|
||||
- `sub_step.detail` — **leave empty (`""`) by default.** Add a one-line note ONLY when the next-session resumer cannot infer where to pick up from `phase` + `name` + on-disk artifacts alone (e.g. `"batch 2 of 4"`, `"blocked on D-PROJ-2 reply"`, `"variant 1b"`). NEVER use `detail` as a changelog, recap, or summary of completed work — those facts belong in the relevant `_docs/` artifact (glossary, traceability matrix, leftovers folder, retro report, etc.) and in git history.
|
||||
- **Total file size target: <30 lines.** If you're tempted to write more, you're using the wrong artifact — write in `_docs/` instead.
|
||||
|
||||
Multi-line `detail` blobs that recap what was just completed are a smell. The state file is a *pointer*, not a logbook.
|
||||
|
||||
## Trigger Conditions
|
||||
|
||||
This skill activates when the user wants to:
|
||||
|
||||
@@ -13,7 +13,7 @@ A first-time run executes Phase A then Phase B; every subsequent invocation re-e
|
||||
|
||||
| Step | Name | Sub-Skill | Internal SubSteps |
|
||||
|------|------|-----------|-------------------|
|
||||
| 1 | Document | document/SKILL.md | Steps 0–7 incl. inline 2.5 (module-layout) and 4.5 (glossary + arch vision) |
|
||||
| 1 | Document | document/SKILL.md | Steps 1–8 |
|
||||
| 2 | Architecture Baseline Scan | code-review/SKILL.md (baseline mode) | Phase 1 + Phase 7 |
|
||||
| 3 | Test Spec | test-spec/SKILL.md | Phases 1–4 |
|
||||
| 4 | Code Testability Revision | refactor/SKILL.md (guided mode) | Phases 0–7 (conditional) |
|
||||
@@ -53,8 +53,6 @@ Action: An existing codebase without documentation was detected. Read and execut
|
||||
|
||||
The document skill's Step 2.5 produces `_docs/02_document/module-layout.md`, which is required by every downstream step that assigns file ownership (`/implement` Step 4, `/code-review` Phase 7, `/refactor` discovery). If this file is missing after Step 1 completes (e.g., a pre-existing `_docs/` dir predates the 2.5 addition), re-invoke `/document` in resume mode — it will pick up at Step 2.5.
|
||||
|
||||
The document skill's Step 4.5 produces `_docs/02_document/glossary.md` and prepends a confirmed `## Architecture Vision` section to `architecture.md`. Both are user-confirmed artifacts; downstream skills (refactor, decompose, new-task) treat them as authoritative for terminology and structural intent. If `glossary.md` is missing after Step 1 (pre-existing `_docs/` dir from before the 4.5 addition), re-invoke `/document` in resume mode — it will pick up at Step 4.5 without redoing module/component analysis.
|
||||
|
||||
---
|
||||
|
||||
**Step 2 — Architecture Baseline Scan**
|
||||
@@ -152,17 +150,15 @@ If `_docs/02_tasks/` subfolders have some task files already (e.g., refactoring
|
||||
---
|
||||
|
||||
**Step 6 — Implement Tests**
|
||||
Condition (folder fallback): `_docs/02_tasks/todo/` contains test task files AND `_dependencies_table.md` exists AND `_docs/03_implementation/implementation_report_tests.md` does not exist.
|
||||
Condition (folder fallback): `_docs/02_tasks/todo/` contains task files AND `_dependencies_table.md` exists AND `_docs/03_implementation/implementation_report_tests.md` does not exist.
|
||||
State-driven: reached by auto-chain from Step 5.
|
||||
|
||||
Action: Invoke `.cursor/skills/implement/SKILL.md` with task selection context **Test implementation**.
|
||||
Action: Read and execute `.cursor/skills/implement/SKILL.md`
|
||||
|
||||
The implement skill reads only test tasks from `_docs/02_tasks/todo/` and implements them.
|
||||
The implement skill reads test tasks from `_docs/02_tasks/todo/` and implements them.
|
||||
|
||||
If `_docs/03_implementation/` has batch reports, the implement skill detects completed tasks and continues.
|
||||
|
||||
For folder fallback, **test task files** means `*_test_infrastructure.md` plus task specs whose `**Component**` or `**Epic**` identifies `Blackbox Tests`.
|
||||
|
||||
---
|
||||
|
||||
**Step 7 — Run Tests**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Greenfield Workflow
|
||||
|
||||
Workflow for new projects built from scratch. Flows linearly: Problem → Research → Plan → UI Design (if applicable) → Test Spec → Decompose → Implement + Product Completeness Gate → Code Testability Revision → Decompose Tests → Implement Tests → Run Tests → Test-Spec Sync → Update Docs → Security Audit (optional) → Performance Test (optional) → Deploy → Retrospective.
|
||||
Workflow for new projects built from scratch. Flows linearly: Problem → Research → Plan → UI Design (if applicable) → Decompose → Implement → Run Tests → Security Audit (optional) → Performance Test (optional) → Deploy → Retrospective.
|
||||
|
||||
## Step Reference Table
|
||||
|
||||
@@ -10,19 +10,13 @@ Workflow for new projects built from scratch. Flows linearly: Problem → Resear
|
||||
| 2 | Research | research/SKILL.md | Mode A: Phase 1–4 · Mode B: Step 0–8 |
|
||||
| 3 | Plan | plan/SKILL.md | Step 1–6 + Final |
|
||||
| 4 | UI Design | ui-design/SKILL.md | Phase 0–8 (conditional — UI projects only) |
|
||||
| 5 | Test Spec | test-spec/SKILL.md | Phases 1–4 |
|
||||
| 6 | Decompose | decompose/SKILL.md (implementation task decomposition) | Step 1 + Step 1.5 + Step 2 + Step 4 |
|
||||
| 7 | Implement | implement/SKILL.md | Batch loop + Product Implementation Completeness Gate |
|
||||
| 8 | Code Testability Revision | refactor/SKILL.md (guided mode) | Phases 0–7 (conditional) |
|
||||
| 9 | Decompose Tests | decompose/SKILL.md (tests-only) | Step 1t + Step 3 + Step 4 |
|
||||
| 10 | Implement Tests | implement/SKILL.md | (batch-driven, no fixed sub-steps) |
|
||||
| 11 | Run Tests | test-run/SKILL.md | Steps 1–4 |
|
||||
| 12 | Test-Spec Sync | test-spec/SKILL.md (cycle-update mode) | Phase 2 + Phase 3 (scoped) |
|
||||
| 13 | Update Docs | document/SKILL.md (task mode) | Task Steps 0–5 |
|
||||
| 14 | Security Audit | security/SKILL.md | Phase 1–5 (optional) |
|
||||
| 15 | Performance Test | test-run/SKILL.md (perf mode) | Steps 1–5 (optional) |
|
||||
| 16 | Deploy | deploy/SKILL.md | Step 1–7 |
|
||||
| 17 | Retrospective | retrospective/SKILL.md (cycle-end mode) | Steps 1–4 |
|
||||
| 5 | Decompose | decompose/SKILL.md | Step 1–4 |
|
||||
| 6 | Implement | implement/SKILL.md | (batch-driven, no fixed sub-steps) |
|
||||
| 7 | Run Tests | test-run/SKILL.md | Steps 1–4 |
|
||||
| 8 | Security Audit | security/SKILL.md | Phase 1–5 (optional) |
|
||||
| 9 | Performance Test | test-run/SKILL.md (perf mode) | Steps 1–5 (optional) |
|
||||
| 10 | Deploy | deploy/SKILL.md | Step 1–7 |
|
||||
| 11 | Retrospective | retrospective/SKILL.md (cycle-end mode) | Steps 1–4 |
|
||||
|
||||
## Detection Rules
|
||||
|
||||
@@ -86,12 +80,12 @@ If `_docs/02_document/` exists but is incomplete (has some artifacts but no `FIN
|
||||
---
|
||||
|
||||
**Step 4 — UI Design (conditional)**
|
||||
Condition (folder fallback): `_docs/02_document/architecture.md` exists AND `_docs/02_document/tests/traceability-matrix.md` does not exist.
|
||||
Condition (folder fallback): `_docs/02_document/architecture.md` exists AND `_docs/02_tasks/todo/` does not exist or has no task files.
|
||||
State-driven: reached by auto-chain from Step 3.
|
||||
|
||||
Action: Read and execute `.cursor/skills/ui-design/SKILL.md`. The skill runs its own **Applicability Check**, which handles UI project detection and the user's A/B choice. It returns one of:
|
||||
|
||||
- `outcome: completed` → mark Step 4 as `completed`, auto-chain to Step 5 (Test Spec).
|
||||
- `outcome: completed` → mark Step 4 as `completed`, auto-chain to Step 5 (Decompose).
|
||||
- `outcome: skipped, reason: not-a-ui-project` → mark Step 4 as `skipped`, auto-chain to Step 5.
|
||||
- `outcome: skipped, reason: user-declined` → mark Step 4 as `skipped`, auto-chain to Step 5.
|
||||
|
||||
@@ -99,162 +93,34 @@ The autodev no longer inlines UI detection heuristics — they live in `ui-desig
|
||||
|
||||
---
|
||||
|
||||
**Step 5 — Test Spec**
|
||||
Condition (folder fallback): `_docs/02_document/FINAL_report.md` exists AND `_docs/02_document/architecture.md` exists AND `_docs/02_document/tests/traceability-matrix.md` does not exist.
|
||||
State-driven: reached by auto-chain from Step 4 (completed or skipped).
|
||||
**Step 5 — Decompose**
|
||||
Condition: `_docs/02_document/` contains `architecture.md` AND `_docs/02_document/components/` has at least one component AND `_docs/02_tasks/todo/` does not exist or has no task files
|
||||
|
||||
Action: Read and execute `.cursor/skills/test-spec/SKILL.md`.
|
||||
|
||||
This step converts the greenfield problem statement, acceptance criteria, solution, architecture, component docs, and UI design artifacts (if any) into test specifications before implementation begins. The test spec should cover unit, integration, blackbox, and e2e scenarios where those levels are applicable to the project.
|
||||
|
||||
---
|
||||
|
||||
**Step 6 — Decompose**
|
||||
Condition: `_docs/02_document/` contains `architecture.md` AND `_docs/02_document/components/` has at least one component AND `_docs/02_document/tests/traceability-matrix.md` exists AND `_docs/02_tasks/todo/` does not exist or has no implementation task files.
|
||||
|
||||
Action: Invoke `.cursor/skills/decompose/SKILL.md` for **implementation task decomposition**. The greenfield flow selects the implementation entrypoint before handing off: Bootstrap Structure, Module Layout, Component Task Decomposition, and Cross-Task Verification.
|
||||
|
||||
Do not invoke Blackbox Test Task Decomposition from Step 6. Test tasks are intentionally deferred to Step 9 (Decompose Tests) so the first implementation batch stays focused on product functionality and Step 8 can revise testability before test task files exist.
|
||||
Action: Read and execute `.cursor/skills/decompose/SKILL.md`
|
||||
|
||||
If `_docs/02_tasks/` subfolders have some task files already, the decompose skill's resumability handles it.
|
||||
|
||||
---
|
||||
|
||||
**Step 7 — Implement**
|
||||
Condition: `_docs/02_tasks/todo/` contains implementation task files AND `_dependencies_table.md` exists AND `_docs/03_implementation/` does not contain a valid product implementation report.
|
||||
**Step 6 — Implement**
|
||||
Condition: `_docs/02_tasks/todo/` contains task files AND `_dependencies_table.md` exists AND `_docs/03_implementation/` does not contain any `implementation_report_*.md` file
|
||||
|
||||
Action: Invoke `.cursor/skills/implement/SKILL.md` with task selection context **Product implementation**.
|
||||
|
||||
The implement skill must run its **Product Implementation Completeness Gate** before it writes any final product implementation report. This gate compares completed product task specs, architecture/component promises, and actual source code so scaffold-only implementations cannot advance to Step 8. A final product implementation report without `_docs/03_implementation/implementation_completeness_cycle[N]_report.md` is incomplete and must not be treated as Step 7 completion.
|
||||
Action: Read and execute `.cursor/skills/implement/SKILL.md`
|
||||
|
||||
If `_docs/03_implementation/` has batch reports, the implement skill detects completed tasks and continues. The FINAL report filename is context-dependent — see implement skill documentation for naming convention.
|
||||
|
||||
For folder fallback, **implementation task files** means task specs that are not test-only specs: exclude `*_test_infrastructure.md` and task specs whose `**Component**` or `**Epic**` identifies `Blackbox Tests`.
|
||||
|
||||
For folder fallback, a **product implementation report** is any `_docs/03_implementation/implementation_report_*.md` file except `_docs/03_implementation/implementation_report_tests.md` and refactor reports. It is valid for greenfield progression only when:
|
||||
- the matching `_docs/03_implementation/implementation_completeness_cycle[N]_report.md` exists,
|
||||
- that completeness report does not contain unresolved `FAIL` classifications, and
|
||||
- `_docs/02_tasks/todo/` contains no pending implementation task files.
|
||||
|
||||
If a product report exists but any of those validity checks fail, treat product implementation as incomplete and stay in Step 7.
|
||||
|
||||
---
|
||||
|
||||
**Step 8 — Code Testability Revision**
|
||||
Condition (folder fallback): `_docs/03_implementation/` contains a valid product implementation report, `_docs/03_implementation/implementation_completeness_cycle[N]_report.md` exists without unresolved `FAIL` classifications, `_docs/04_refactoring/01-testability-refactoring/testability_assessment.md` does not exist, `_docs/04_refactoring/01-testability-refactoring/testability_changes_summary.md` does not exist, `_docs/03_implementation/implementation_report_tests.md` does not exist, and `_docs/02_tasks/todo/` does not contain test task files.
|
||||
State-driven: reached by auto-chain from Step 7.
|
||||
|
||||
**Purpose**: verify the newly built code can be exercised by the planned tests before writing the test suite. Greenfield code should be testable by design; this step catches accidental hardcoded paths, singletons, direct external service construction, or other implementation choices that would make meaningful tests impossible.
|
||||
|
||||
**Scope — MINIMAL, SURGICAL fixes**: this is not a general refactor. It is the smallest set of changes required to make the implemented code runnable under tests.
|
||||
|
||||
**Allowed changes** in this phase:
|
||||
- Replace hardcoded URLs / file paths / credentials / magic numbers with env vars or constructor arguments.
|
||||
- Extract narrow interfaces for components that need stubbing in tests.
|
||||
- Add optional constructor parameters for dependency injection; default to the existing behavior so callers do not break.
|
||||
- Wrap global singletons in thin accessors that tests can override.
|
||||
- Split a function ONLY when necessary to stub one of its collaborators — do not split for clarity alone.
|
||||
|
||||
**NOT allowed** in this phase (defer to a later refactor task):
|
||||
- Renaming public APIs.
|
||||
- Moving code between files unless strictly required for isolation.
|
||||
- Changing algorithms or business logic.
|
||||
- Restructuring module boundaries or rewriting layers.
|
||||
|
||||
Action: Analyze the codebase against the test specs to determine whether the code can be tested as-is.
|
||||
|
||||
1. Read `_docs/02_document/tests/traceability-matrix.md` and all test scenario files in `_docs/02_document/tests/`.
|
||||
2. For each test scenario, check whether the code under test can be exercised in isolation. Look for:
|
||||
- Hardcoded file paths or directory references
|
||||
- Hardcoded configuration values (URLs, credentials, magic numbers)
|
||||
- Global mutable state that cannot be overridden
|
||||
- Tight coupling to external services without abstraction
|
||||
- Missing dependency injection or non-configurable parameters
|
||||
- Direct file system operations without path configurability
|
||||
- Inline construction of heavy dependencies (models, clients)
|
||||
3. If ALL scenarios are testable as-is:
|
||||
- Create `_docs/04_refactoring/01-testability-refactoring/`
|
||||
- Write `_docs/04_refactoring/01-testability-refactoring/testability_assessment.md` with the scenarios reviewed and outcome "Code is testable — no changes needed"
|
||||
- Mark Step 8 as `completed` with outcome "Code is testable — no changes needed"
|
||||
- Auto-chain to Step 9 (Decompose Tests)
|
||||
4. If testability issues are found:
|
||||
- Create `_docs/04_refactoring/01-testability-refactoring/`
|
||||
- Write `list-of-changes.md` in that directory using the refactor skill template (`.cursor/skills/refactor/templates/list-of-changes.md`), with:
|
||||
- **Mode**: `guided`
|
||||
- **Source**: `autodev-greenfield-testability-analysis`
|
||||
- One change entry per testability issue found (change ID, file paths, problem, proposed change, risk, dependencies). Each entry must fit the allowed-changes list above; reject entries that drift into full refactor territory and log them under "Deferred refactor candidates" instead.
|
||||
- Invoke the refactor skill in **guided mode**: read and execute `.cursor/skills/refactor/SKILL.md` with the `list-of-changes.md` as input
|
||||
- Phase 3 (Safety Net) is skipped for this testability run because the test suite has not been implemented yet
|
||||
- After execution, surface `RUN_DIR/testability_changes_summary.md` to the user via the Choose format (accept / request follow-up) before auto-chaining
|
||||
- Copy or save the accepted summary as `_docs/04_refactoring/01-testability-refactoring/testability_changes_summary.md` so folder fallback can detect Step 8 completion
|
||||
- Mark Step 8 as `completed`
|
||||
- Auto-chain to Step 9 (Decompose Tests)
|
||||
|
||||
---
|
||||
|
||||
**Step 9 — Decompose Tests**
|
||||
Condition (folder fallback): `_docs/02_document/tests/traceability-matrix.md` exists AND workspace contains source code files AND `_docs/03_implementation/` contains a valid product implementation report AND `_docs/03_implementation/implementation_completeness_cycle[N]_report.md` exists without unresolved `FAIL` classifications AND (`_docs/04_refactoring/01-testability-refactoring/testability_assessment.md` exists OR `_docs/04_refactoring/01-testability-refactoring/testability_changes_summary.md` exists) AND (`_docs/02_tasks/todo/` does not exist or has no test task files) AND `_docs/03_implementation/implementation_report_tests.md` does not exist.
|
||||
State-driven: reached by auto-chain from Step 8.
|
||||
|
||||
Action: Read and execute `.cursor/skills/decompose/SKILL.md` in **tests-only mode** (pass `_docs/02_document/tests/` as input). The decompose skill will:
|
||||
1. Run Step 1t (test infrastructure bootstrap)
|
||||
2. Run Step 3 (blackbox/e2e-capable test task decomposition)
|
||||
3. Run Step 4 (cross-verification against test coverage)
|
||||
|
||||
If `_docs/02_tasks/` subfolders have some task files already, the decompose skill's resumability handles it — it appends test tasks alongside existing completed implementation tasks.
|
||||
|
||||
---
|
||||
|
||||
**Step 10 — Implement Tests**
|
||||
Condition (folder fallback): `_docs/02_tasks/todo/` contains test task files AND `_dependencies_table.md` exists AND `_docs/03_implementation/implementation_report_tests.md` does not exist.
|
||||
State-driven: reached by auto-chain from Step 9.
|
||||
|
||||
Action: Invoke `.cursor/skills/implement/SKILL.md` with task selection context **Test implementation**.
|
||||
|
||||
The implement skill reads only test tasks from `_docs/02_tasks/todo/` and implements them.
|
||||
|
||||
If `_docs/03_implementation/` has batch reports, the implement skill detects completed test tasks and continues.
|
||||
|
||||
For folder fallback, **test task files** means `*_test_infrastructure.md` plus task specs whose `**Component**` or `**Epic**` identifies `Blackbox Tests`.
|
||||
|
||||
---
|
||||
|
||||
**Step 11 — Run Tests**
|
||||
Condition (folder fallback): `_docs/03_implementation/implementation_report_tests.md` exists.
|
||||
State-driven: reached by auto-chain from Step 10.
|
||||
**Step 7 — Run Tests**
|
||||
Condition (folder fallback): `_docs/03_implementation/` contains an `implementation_report_*.md` file.
|
||||
State-driven: reached by auto-chain from Step 6.
|
||||
|
||||
Action: Read and execute `.cursor/skills/test-run/SKILL.md`
|
||||
|
||||
Verifies the implemented unit, integration, blackbox, and e2e tests pass before proceeding to spec and documentation sync. This is a hard product gate, not a harness-smoke gate: e2e/blackbox tests must exercise the actual implemented system through public runtime boundaries and compare actual outputs against `_docs/00_problem/input_data/expected_results/results_report.md` or referenced machine-readable expected-result files. Stubs are allowed only for external systems outside the product boundary; missing internal product implementation must fail or block the gate and send the flow back to Implement.
|
||||
|
||||
---
|
||||
|
||||
**Step 12 — Test-Spec Sync**
|
||||
State-driven: reached by auto-chain from Step 11. Requires `_docs/02_document/tests/traceability-matrix.md` to exist — if missing, mark Step 12 `skipped` (see Action below).
|
||||
|
||||
Action: Read and execute `.cursor/skills/test-spec/SKILL.md` in **cycle-update mode**. Pass the completed implementation task specs, completed test task specs, and implementation reports as inputs.
|
||||
|
||||
The skill appends implementation-learned acceptance criteria, scenarios, and NFR updates to the existing test-spec files without rewriting unaffected sections. If `traceability-matrix.md` is missing, mark Step 12 as `skipped` — the next `/test-spec` full run will regenerate it.
|
||||
|
||||
After completion, auto-chain to Step 13 (Update Docs).
|
||||
|
||||
---
|
||||
|
||||
**Step 13 — Update Docs**
|
||||
State-driven: reached by auto-chain from Step 12 (completed or skipped). Requires `_docs/02_document/` to contain existing documentation — if missing, mark Step 13 `skipped` (see Action below).
|
||||
|
||||
Action: Read and execute `.cursor/skills/document/SKILL.md` in **Task mode**. Pass all completed implementation and test task spec files plus the implementation reports.
|
||||
|
||||
The document skill in Task mode updates affected module docs, component docs, system-level docs, and test documentation without redoing full discovery, verification, or problem extraction.
|
||||
|
||||
If `_docs/02_document/` does not contain existing docs, mark Step 13 as `skipped`.
|
||||
|
||||
After completion, auto-chain to Step 14 (Security Audit).
|
||||
|
||||
---
|
||||
|
||||
**Step 14 — Security Audit (optional)**
|
||||
State-driven: reached by auto-chain from Step 13 (completed or skipped).
|
||||
**Step 8 — Security Audit (optional)**
|
||||
State-driven: reached by auto-chain from Step 7.
|
||||
|
||||
Action: Apply the **Optional Skill Gate** (`protocols.md` → "Optional Skill Gate") with:
|
||||
- question: `Run security audit before deploy?`
|
||||
@@ -262,12 +128,12 @@ Action: Apply the **Optional Skill Gate** (`protocols.md` → "Optional Skill Ga
|
||||
- option-b-label: `Skip — proceed directly to deploy`
|
||||
- recommendation: `A — catches vulnerabilities before production`
|
||||
- target-skill: `.cursor/skills/security/SKILL.md`
|
||||
- next-step: Step 15 (Performance Test)
|
||||
- next-step: Step 9 (Performance Test)
|
||||
|
||||
---
|
||||
|
||||
**Step 15 — Performance Test (optional)**
|
||||
State-driven: reached by auto-chain from Step 14 (completed or skipped).
|
||||
**Step 9 — Performance Test (optional)**
|
||||
State-driven: reached by auto-chain from Step 8.
|
||||
|
||||
Action: Apply the **Optional Skill Gate** (`protocols.md` → "Optional Skill Gate") with:
|
||||
- question: `Run performance/load tests before deploy?`
|
||||
@@ -275,30 +141,30 @@ Action: Apply the **Optional Skill Gate** (`protocols.md` → "Optional Skill Ga
|
||||
- option-b-label: `Skip — proceed directly to deploy`
|
||||
- recommendation: `A or B — base on whether acceptance criteria include latency, throughput, or load requirements`
|
||||
- target-skill: `.cursor/skills/test-run/SKILL.md` in **perf mode** (the skill handles runner detection, threshold comparison, and its own A/B/C gate on threshold failures)
|
||||
- next-step: Step 16 (Deploy)
|
||||
- next-step: Step 10 (Deploy)
|
||||
|
||||
---
|
||||
|
||||
**Step 16 — Deploy**
|
||||
State-driven: reached by auto-chain from Step 15 (after Step 15 is completed or skipped).
|
||||
**Step 10 — Deploy**
|
||||
State-driven: reached by auto-chain from Step 9 (after Step 9 is completed or skipped).
|
||||
|
||||
Action: Read and execute `.cursor/skills/deploy/SKILL.md`.
|
||||
|
||||
After the deploy skill completes successfully, mark Step 16 as `completed` and auto-chain to Step 17 (Retrospective).
|
||||
After the deploy skill completes successfully, mark Step 10 as `completed` and auto-chain to Step 11 (Retrospective).
|
||||
|
||||
---
|
||||
|
||||
**Step 17 — Retrospective**
|
||||
State-driven: reached by auto-chain from Step 16.
|
||||
**Step 11 — Retrospective**
|
||||
State-driven: reached by auto-chain from Step 10.
|
||||
|
||||
Action: Read and execute `.cursor/skills/retrospective/SKILL.md` in **cycle-end mode**. This closes the cycle's feedback loop by folding metrics into `_docs/06_metrics/retro_<date>.md` and appending the top-3 lessons to `_docs/LESSONS.md`.
|
||||
|
||||
After retrospective completes, mark Step 17 as `completed` and enter "Done" evaluation.
|
||||
After retrospective completes, mark Step 11 as `completed` and enter "Done" evaluation.
|
||||
|
||||
---
|
||||
|
||||
**Done**
|
||||
State-driven: reached by auto-chain from Step 17. (Sanity check: `_docs/04_deploy/` should contain all expected artifacts — containerization.md, ci_cd_pipeline.md, environment_strategy.md, observability.md, deployment_procedures.md, deploy_scripts.md.)
|
||||
State-driven: reached by auto-chain from Step 11. (Sanity check: `_docs/04_deploy/` should contain all expected artifacts — containerization.md, ci_cd_pipeline.md, environment_strategy.md, observability.md, deployment_procedures.md, deploy_scripts.md.)
|
||||
|
||||
Action: Report project completion with summary. Then **rewrite the state file** so the next `/autodev` invocation enters the feature-cycle loop in the existing-code flow:
|
||||
|
||||
@@ -325,65 +191,47 @@ On the next invocation, Flow Resolution rule 1 reads `flow: existing-code` and r
|
||||
| Research (2) | Auto-chain → Research Decision (ask user: another round or proceed?) |
|
||||
| Research Decision → proceed | Auto-chain → Plan (3) |
|
||||
| Plan (3) | Auto-chain → UI Design detection (4) |
|
||||
| UI Design (4, done or skipped) | Auto-chain → Test Spec (5) |
|
||||
| Test Spec (5) | Auto-chain → Decompose (6) |
|
||||
| Decompose (6) | **Session boundary** — suggest new conversation before Implement |
|
||||
| Implement (7) | Auto-chain only after Product Implementation Completeness Gate passes → Code Testability Revision (8) |
|
||||
| Code Testability Revision (8) | Auto-chain → Decompose Tests (9) |
|
||||
| Decompose Tests (9) | **Session boundary** — suggest new conversation before Implement Tests |
|
||||
| Implement Tests (10) | Auto-chain → Run Tests (11) |
|
||||
| Run Tests (11, all pass) | Auto-chain → Test-Spec Sync (12) |
|
||||
| Test-Spec Sync (12, done or skipped) | Auto-chain → Update Docs (13) |
|
||||
| Update Docs (13, done or skipped) | Auto-chain → Security Audit choice (14) |
|
||||
| Security Audit (14, done or skipped) | Auto-chain → Performance Test choice (15) |
|
||||
| Performance Test (15, done or skipped) | Auto-chain → Deploy (16) |
|
||||
| Deploy (16) | Auto-chain → Retrospective (17) |
|
||||
| Retrospective (17) | Report completion; rewrite state to existing-code flow, step 9 |
|
||||
| UI Design (4, done or skipped) | Auto-chain → Decompose (5) |
|
||||
| Decompose (5) | **Session boundary** — suggest new conversation before Implement |
|
||||
| Implement (6) | Auto-chain → Run Tests (7) |
|
||||
| Run Tests (7, all pass) | Auto-chain → Security Audit choice (8) |
|
||||
| Security Audit (8, done or skipped) | Auto-chain → Performance Test choice (9) |
|
||||
| Performance Test (9, done or skipped) | Auto-chain → Deploy (10) |
|
||||
| Deploy (10) | Auto-chain → Retrospective (11) |
|
||||
| Retrospective (11) | Report completion; rewrite state to existing-code flow, step 9 |
|
||||
|
||||
## Status Summary — Step List
|
||||
|
||||
Flow name: `greenfield`. Render using the banner template in `protocols.md` → "Banner Template (authoritative)". No header-suffix, current-suffix, or footer-extras — all empty for this flow.
|
||||
|
||||
| # | Step Name | Extra state tokens (beyond the shared set) |
|
||||
|---|-----------------------------|--------------------------------------------|
|
||||
| 1 | Problem | — |
|
||||
| 2 | Research | `DONE (N drafts)` |
|
||||
| 3 | Plan | — |
|
||||
| 4 | UI Design | — |
|
||||
| 5 | Test Spec | — |
|
||||
| 6 | Decompose | `DONE (N tasks)` |
|
||||
| 7 | Implement | `IN PROGRESS (batch M of ~N)` |
|
||||
| 8 | Code Testability Revision | — |
|
||||
| 9 | Decompose Tests | `DONE (N tasks)` |
|
||||
| 10 | Implement Tests | `IN PROGRESS (batch M)` |
|
||||
| 11 | Run Tests | `DONE (N passed, M failed)` |
|
||||
| 12 | Test-Spec Sync | — |
|
||||
| 13 | Update Docs | — |
|
||||
| 14 | Security Audit | — |
|
||||
| 15 | Performance Test | — |
|
||||
| 16 | Deploy | — |
|
||||
| 17 | Retrospective | — |
|
||||
| # | Step Name | Extra state tokens (beyond the shared set) |
|
||||
|---|--------------------|--------------------------------------------|
|
||||
| 1 | Problem | — |
|
||||
| 2 | Research | `DONE (N drafts)` |
|
||||
| 3 | Plan | — |
|
||||
| 4 | UI Design | — |
|
||||
| 5 | Decompose | `DONE (N tasks)` |
|
||||
| 6 | Implement | `IN PROGRESS (batch M of ~N)` |
|
||||
| 7 | Run Tests | `DONE (N passed, M failed)` |
|
||||
| 8 | Security Audit | — |
|
||||
| 9 | Performance Test | — |
|
||||
| 10 | Deploy | — |
|
||||
| 11 | Retrospective | — |
|
||||
|
||||
All rows also accept the shared state tokens (`DONE`, `IN PROGRESS`, `NOT STARTED`, `FAILED (retry N/3)`); rows 4, 12, 13, 14, 15 additionally accept `SKIPPED`.
|
||||
All rows also accept the shared state tokens (`DONE`, `IN PROGRESS`, `NOT STARTED`, `FAILED (retry N/3)`); rows 4, 8, 9 additionally accept `SKIPPED`.
|
||||
|
||||
Row rendering format (step-number column is right-padded to 2 characters for alignment):
|
||||
|
||||
```
|
||||
Step 1 Problem [<state token>]
|
||||
Step 2 Research [<state token>]
|
||||
Step 3 Plan [<state token>]
|
||||
Step 4 UI Design [<state token>]
|
||||
Step 5 Test Spec [<state token>]
|
||||
Step 6 Decompose [<state token>]
|
||||
Step 7 Implement [<state token>]
|
||||
Step 8 Code Testability Rev. [<state token>]
|
||||
Step 9 Decompose Tests [<state token>]
|
||||
Step 10 Implement Tests [<state token>]
|
||||
Step 11 Run Tests [<state token>]
|
||||
Step 12 Test-Spec Sync [<state token>]
|
||||
Step 13 Update Docs [<state token>]
|
||||
Step 14 Security Audit [<state token>]
|
||||
Step 15 Performance Test [<state token>]
|
||||
Step 16 Deploy [<state token>]
|
||||
Step 17 Retrospective [<state token>]
|
||||
Step 1 Problem [<state token>]
|
||||
Step 2 Research [<state token>]
|
||||
Step 3 Plan [<state token>]
|
||||
Step 4 UI Design [<state token>]
|
||||
Step 5 Decompose [<state token>]
|
||||
Step 6 Implement [<state token>]
|
||||
Step 7 Run Tests [<state token>]
|
||||
Step 8 Security Audit [<state token>]
|
||||
Step 9 Performance Test [<state token>]
|
||||
Step 10 Deploy [<state token>]
|
||||
Step 11 Retrospective [<state token>]
|
||||
```
|
||||
|
||||
@@ -15,10 +15,8 @@ This flow differs fundamentally from `greenfield` and `existing-code`:
|
||||
|------|------|-----------|-------------------|
|
||||
| 1 | Discover | monorepo-discover/SKILL.md | Phase 1–10 |
|
||||
| 2 | Config Review | (human checkpoint, no sub-skill) | — |
|
||||
| 2.5 | Glossary & Architecture Vision | (inline, no sub-skill) | Steps 1–5 |
|
||||
| 3 | Status | monorepo-status/SKILL.md | Sections 1–5 |
|
||||
| 4 | Document Sync | monorepo-document/SKILL.md | Phase 1–7 (conditional on doc drift) |
|
||||
| 4.5 | Integration Test Sync | monorepo-e2e/SKILL.md | Phase 1–6 (conditional on suite-e2e drift; skipped if `suite_e2e:` block absent in config) |
|
||||
| 5 | CICD Sync | monorepo-cicd/SKILL.md | Phase 1–7 (conditional on CI drift) |
|
||||
| 6 | Loop | (auto-return to Step 3 on next invocation) | — |
|
||||
|
||||
@@ -60,121 +58,17 @@ Action: This is a **hard session boundary**. The skill cannot proceed until a hu
|
||||
══════════════════════════════════════
|
||||
```
|
||||
|
||||
- If user picks A → verify `confirmed_by_user: true` is now set in the config. If still `false`, re-ask. If true, auto-chain to **Step 2.5 (Glossary & Architecture Vision)**.
|
||||
- If user picks A → verify `confirmed_by_user: true` is now set in the config. If still `false`, re-ask. If true, auto-chain to **Step 3 (Status)**.
|
||||
- If user picks B → mark Step 2 as `in_progress`, update state file, end the session. Tell the user to invoke `/autodev` again after reviewing.
|
||||
|
||||
**Do NOT auto-flip `confirmed_by_user`.** Only the human does that.
|
||||
|
||||
---
|
||||
|
||||
**Step 2.5 — Glossary & Architecture Vision** (one-shot)
|
||||
|
||||
Condition (folder fallback): `_docs/_repo-config.yaml` exists AND `confirmed_by_user: true` AND (`_docs/glossary.md` does NOT exist OR the cross-cutting architecture doc identified in `docs.cross_cutting` does NOT contain a `## Architecture Vision` section).
|
||||
State-driven: reached by auto-chain from Step 2 (user picked A).
|
||||
|
||||
**Goal**: Capture meta-repo-wide terminology and the user's architecture vision **once**, after the config is confirmed but before any sync skill runs. Without this, `monorepo-document` will faithfully propagate per-component changes but never surface a unified mental model of the meta-repo to the user, and the AI will keep re-inferring the same project terminology on every invocation.
|
||||
|
||||
**Why inline (no sub-skill)**: `monorepo-discover` is hard-guarded to write only `_repo-config.yaml`; `monorepo-document` only edits *existing* docs. Glossary and architecture-vision creation is a first-time, user-confirmed write that crosses both guarantees, so it lives directly in the flow.
|
||||
|
||||
**Inputs**:
|
||||
- `_docs/_repo-config.yaml` (component list, doc map, conventions, assumptions log)
|
||||
- Cross-cutting docs listed under `docs.cross_cutting` (existing architecture doc, if any)
|
||||
- Each component's `primary_doc` (read-only, for terminology + responsibility extraction)
|
||||
- Root `README.md` if `repo.root_readme` is referenced
|
||||
|
||||
**Outputs**:
|
||||
- `_docs/glossary.md` (or `<docs.root>/glossary.md` if `docs.root` ≠ `_docs/`) — NEW
|
||||
- The cross-cutting architecture doc updated in place: a `## Architecture Vision` section is prepended (or merged into an existing "Vision" / "Overview" heading)
|
||||
- One new entry appended to `_docs/_repo-config.yaml` under `assumptions_log:` recording the run
|
||||
- A new top-level config entry: `glossary_doc: <path>` so future `monorepo-status` and `monorepo-document` runs treat the glossary as a known cross-cutting doc
|
||||
|
||||
**Procedure**:
|
||||
|
||||
1. **Draft glossary** from `_repo-config.yaml` + each component's primary doc. Include:
|
||||
- Component codenames as they appear in the config (`name` field) and any rename pairs the user noted in `unresolved:` resolutions
|
||||
- Domain terms that recur across ≥2 component docs
|
||||
- Acronyms / abbreviations
|
||||
- Convention names from `conventions:` (e.g., commit prefix, deployment tier names)
|
||||
- Stakeholder personas if cross-cutting docs reference them
|
||||
Each entry: one-line definition + source (`source: components.<name>.primary_doc` or `source: _repo-config.yaml conventions`). Skip generic terms.
|
||||
|
||||
2. **Draft architecture vision** from the meta-repo perspective:
|
||||
- **One paragraph**: what the system as a whole is, what each component contributes, the runtime topology (one binary / N services / N clients + 1 server / hybrid), how components communicate (REST / gRPC / queue / DB-shared / file-shared)
|
||||
- **Components & responsibilities** (one-line each), pulled directly from `_repo-config.yaml` `components:` list
|
||||
- **Cross-cutting concerns ownership**: which doc owns which concern (auth, schema, deployment, etc.) — pulled from `docs.cross_cutting[].owns`
|
||||
- **Architectural principles / non-negotiables** the user has implied across components (e.g., "all components share a single Postgres", "submodules own their own CI", "deployment is per-tier, not per-component")
|
||||
- **Open questions / structural drift signals**: components missing from `docs.cross_cutting`, components in registry but not in config (registry mismatch), or contradictions between component primary docs
|
||||
|
||||
3. **Present condensed view** to the user (NOT the full draft files):
|
||||
|
||||
```
|
||||
══════════════════════════════════════
|
||||
REVIEW: Meta-Repo Glossary + Architecture Vision
|
||||
══════════════════════════════════════
|
||||
Glossary (N terms drafted from config + component docs):
|
||||
- <Term>: <one-line definition>
|
||||
- ...
|
||||
|
||||
Architecture Vision — meta-repo level:
|
||||
<one-paragraph synopsis>
|
||||
|
||||
Components / responsibilities:
|
||||
- <component>: <one-line>
|
||||
- ...
|
||||
|
||||
Cross-cutting ownership:
|
||||
- <concern> → <doc>
|
||||
- ...
|
||||
|
||||
Principles / non-negotiables:
|
||||
- <principle>
|
||||
- ...
|
||||
|
||||
Open questions / drift signals:
|
||||
- <q1>
|
||||
- <q2>
|
||||
══════════════════════════════════════
|
||||
A) Looks correct — write the files
|
||||
B) Add / correct entries (provide diffs)
|
||||
C) Resolve open questions / drift signals first
|
||||
══════════════════════════════════════
|
||||
Recommendation: pick C if drift signals exist;
|
||||
otherwise B if components or principles
|
||||
don't match your intent; A only when
|
||||
the inferred vision is exactly right.
|
||||
══════════════════════════════════════
|
||||
```
|
||||
|
||||
4. **Iterate**:
|
||||
- On B → integrate the user's diffs/additions, re-present, loop until A.
|
||||
- On C → ask the listed open questions in one batch, integrate answers, re-present.
|
||||
- **Do NOT proceed to step 5 until the user picks A.**
|
||||
|
||||
5. **Save**:
|
||||
- Write `_docs/glossary.md` (alphabetical) with `**Status**: confirmed-by-user` + date.
|
||||
- Update the cross-cutting architecture doc identified in `docs.cross_cutting` (or create one at `_docs/00_architecture.md` if none exists and the user's option-B input named one): prepend `## Architecture Vision` with the confirmed paragraph + components + ownership + principles. Preserve every existing H2 below verbatim.
|
||||
- Append to `_docs/_repo-config.yaml`:
|
||||
- Top-level `glossary_doc: <path-relative-to-repo-root>` (sibling of `docs.root`)
|
||||
- New `assumptions_log:` entry: `{ date: <today>, skill: autodev-meta-repo Step 2.5, run_notes: "Captured glossary + architecture vision", assumptions: [...] }`
|
||||
- Do NOT flip any `confirmed: false` → `confirmed: true` in the config; this step writes its own confirmed artifact, it does not retroactively confirm config inferences.
|
||||
|
||||
**Self-verification**:
|
||||
- [ ] Every glossary entry traces to either the config or a component primary doc
|
||||
- [ ] Every component listed in the vision matches a `components:` entry in the config
|
||||
- [ ] All open questions are answered or explicitly deferred (with the user's acknowledgement)
|
||||
- [ ] The cross-cutting architecture doc still contains every H2 it had before this step
|
||||
- [ ] User picked option A on the latest condensed view
|
||||
|
||||
**Idempotency**: if both `_docs/glossary.md` exists AND the architecture doc already has a `## Architecture Vision` section, this step is **skipped on re-invocation**. To refresh, the user invokes `/autodev` after deleting `glossary.md` (or running `monorepo-discover` with structural changes that justify a re-confirmation).
|
||||
|
||||
After completion, auto-chain to **Step 3 (Status)**.
|
||||
|
||||
---
|
||||
|
||||
**Step 3 — Status**
|
||||
|
||||
Condition (folder fallback): `_docs/_repo-config.yaml` exists AND `confirmed_by_user: true` AND (`_docs/glossary.md` exists OR `glossary_doc:` is recorded in the config).
|
||||
State-driven: reached by auto-chain from Step 2.5, or entered on any re-invocation after a completed cycle.
|
||||
Condition (folder fallback): `_docs/_repo-config.yaml` exists AND `confirmed_by_user: true`.
|
||||
State-driven: reached by auto-chain from Step 2 (user picked A), or entered on any re-invocation after a completed cycle.
|
||||
|
||||
Action: Read and execute `.cursor/skills/monorepo-status/SKILL.md`.
|
||||
|
||||
@@ -221,28 +115,6 @@ The skill:
|
||||
3. Applies doc edits
|
||||
4. Skips any component with unconfirmed mapping (M5), reports
|
||||
|
||||
After completion:
|
||||
- If the status report ALSO flagged suite-e2e drift → auto-chain to **Step 4.5 (Integration Test Sync)**
|
||||
- Else if the status report ALSO flagged CI drift → auto-chain to **Step 5 (CICD Sync)**
|
||||
- Else → end cycle, report done
|
||||
|
||||
---
|
||||
|
||||
**Step 4.5 — Integration Test Sync**
|
||||
|
||||
State-driven: reached by auto-chain from Step 3 (when status report flagged suite-e2e drift and no doc drift) or from Step 4 (when both doc and suite-e2e drift were flagged).
|
||||
|
||||
**Skip condition**: if `_docs/_repo-config.yaml` has no `suite_e2e:` block, this step is skipped entirely — there's no harness to sync. The status report should not flag suite-e2e drift in that case; if it does, that's a status-skill bug.
|
||||
|
||||
Action: Read and execute `.cursor/skills/monorepo-e2e/SKILL.md` with scope = components flagged by status.
|
||||
|
||||
The skill:
|
||||
1. Verifies every path under `suite_e2e.*` exists (binary fixtures excepted — see the skill's Phase 1)
|
||||
2. Classifies each flagged change against the suite-e2e impact table
|
||||
3. Applies edits to `e2e/docker-compose.suite-e2e.yml`, `e2e/fixtures/init.sql`, `e2e/fixtures/expected_detections.json` metadata, and `e2e/runner/tests/*.spec.ts` selectors as needed
|
||||
4. Bumps baseline `fixture_version` with a `-stale` suffix and appends a `_docs/_process_leftovers/` entry whenever the detection model revision changes (binary fixture cannot be regenerated automatically)
|
||||
5. Reports synced files; does not run the suite e2e itself
|
||||
|
||||
After completion:
|
||||
- If the status report ALSO flagged CI drift → auto-chain to **Step 5 (CICD Sync)**
|
||||
- Else → end cycle, report done
|
||||
@@ -251,11 +123,11 @@ After completion:
|
||||
|
||||
**Step 5 — CICD Sync**
|
||||
|
||||
State-driven: reached by auto-chain from Step 3 (when status report flagged CI drift and no doc/suite-e2e drift), Step 4, or Step 4.5.
|
||||
State-driven: reached by auto-chain from Step 3 (when status report flagged CI drift and no doc drift) or from Step 4 (when both doc and CI drift were flagged).
|
||||
|
||||
Action: Read and execute `.cursor/skills/monorepo-cicd/SKILL.md` with scope = components flagged by status.
|
||||
|
||||
After completion, end cycle. Report files updated across doc, suite-e2e, and CI sync.
|
||||
After completion, end cycle. Report files updated across both doc and CI sync.
|
||||
|
||||
---
|
||||
|
||||
@@ -284,19 +156,14 @@ After onboarding completes, the config is updated. Auto-chain back to **Step 3 (
|
||||
| Completed Step | Next Action |
|
||||
|---------------|-------------|
|
||||
| Discover (1) | Auto-chain → Config Review (2) |
|
||||
| Config Review (2, user picked A, confirmed_by_user: true) | Auto-chain → Glossary & Architecture Vision (2.5) |
|
||||
| Config Review (2, user picked A, confirmed_by_user: true) | Auto-chain → Status (3) |
|
||||
| Config Review (2, user picked B) | **Session boundary** — end session, await re-invocation |
|
||||
| Glossary & Architecture Vision (2.5) | Auto-chain → Status (3) |
|
||||
| Status (3, doc drift) | Auto-chain → Document Sync (4) |
|
||||
| Status (3, suite-e2e drift only) | Auto-chain → Integration Test Sync (4.5) |
|
||||
| Status (3, CI drift only) | Auto-chain → CICD Sync (5) |
|
||||
| Status (3, no drift) | **Cycle complete** — end session, await re-invocation |
|
||||
| Status (3, registry mismatch) | Ask user (A: discover, B: onboard, C: continue) |
|
||||
| Document Sync (4) + suite-e2e drift pending | Auto-chain → Integration Test Sync (4.5) |
|
||||
| Document Sync (4) + CI drift only pending | Auto-chain → CICD Sync (5) |
|
||||
| Document Sync (4) + no further drift | **Cycle complete** |
|
||||
| Integration Test Sync (4.5) + CI drift pending | Auto-chain → CICD Sync (5) |
|
||||
| Integration Test Sync (4.5) + no CI drift | **Cycle complete** |
|
||||
| Document Sync (4) + CI drift pending | Auto-chain → CICD Sync (5) |
|
||||
| Document Sync (4) + no CI drift | **Cycle complete** |
|
||||
| CICD Sync (5) | **Cycle complete** |
|
||||
|
||||
## Status Summary — Step List
|
||||
@@ -311,33 +178,29 @@ Flow-specific slot values:
|
||||
Config: _docs/_repo-config.yaml [confirmed_by_user: <true|false>, last_updated: <date>]
|
||||
```
|
||||
|
||||
| # | Step Name | Extra state tokens (beyond the shared set) |
|
||||
|---|------------------------------------|--------------------------------------------|
|
||||
| 1 | Discover | — |
|
||||
| 2 | Config Review | `IN PROGRESS (awaiting human)` |
|
||||
| 2.5 | Glossary & Architecture Vision | `SKIPPED (already captured)` |
|
||||
| 3 | Status | `DONE (no drift)`, `DONE (N drifts)` |
|
||||
| 4 | Document Sync | `DONE (N docs)`, `SKIPPED (no doc drift)` |
|
||||
| 4.5 | Integration Test Sync | `DONE (N files)`, `SKIPPED (no suite-e2e drift)`, `SKIPPED (no suite_e2e config block)` |
|
||||
| 5 | CICD Sync | `DONE (N files)`, `SKIPPED (no CI drift)` |
|
||||
| # | Step Name | Extra state tokens (beyond the shared set) |
|
||||
|---|------------------|--------------------------------------------|
|
||||
| 1 | Discover | — |
|
||||
| 2 | Config Review | `IN PROGRESS (awaiting human)` |
|
||||
| 3 | Status | `DONE (no drift)`, `DONE (N drifts)` |
|
||||
| 4 | Document Sync | `DONE (N docs)`, `SKIPPED (no doc drift)` |
|
||||
| 5 | CICD Sync | `DONE (N files)`, `SKIPPED (no CI drift)` |
|
||||
|
||||
All rows accept the shared state tokens (`DONE`, `IN PROGRESS`, `NOT STARTED`, `FAILED (retry N/3)`); rows 2.5, 4, 4.5, and 5 additionally accept `SKIPPED`.
|
||||
All rows accept the shared state tokens (`DONE`, `IN PROGRESS`, `NOT STARTED`, `FAILED (retry N/3)`); rows 4 and 5 additionally accept `SKIPPED`.
|
||||
|
||||
Row rendering format:
|
||||
|
||||
```
|
||||
Step 1 Discover [<state token>]
|
||||
Step 2 Config Review [<state token>]
|
||||
Step 2.5 Glossary & Architecture Vision [<state token>]
|
||||
Step 3 Status [<state token>]
|
||||
Step 4 Document Sync [<state token>]
|
||||
Step 4.5 Integration Test Sync [<state token>]
|
||||
Step 5 CICD Sync [<state token>]
|
||||
Step 1 Discover [<state token>]
|
||||
Step 2 Config Review [<state token>]
|
||||
Step 3 Status [<state token>]
|
||||
Step 4 Document Sync [<state token>]
|
||||
Step 5 CICD Sync [<state token>]
|
||||
```
|
||||
|
||||
## Notes for the meta-repo flow
|
||||
|
||||
- **No session boundary except Step 2 and Step 2.5**: unlike existing-code flow (which has boundaries around decompose), meta-repo flow only pauses at config review and the one-shot glossary/vision capture. Once both are confirmed, syncing is fast enough to complete in one session and Step 2.5 idempotently no-ops on every subsequent invocation.
|
||||
- **No session boundary except Step 2**: unlike existing-code flow (which has boundaries around decompose), meta-repo flow only pauses at config review. Syncing is fast enough to complete in one session.
|
||||
- **Cyclical, not terminal**: no "done forever" state. Each invocation completes a drift cycle; next invocation starts fresh.
|
||||
- **No tracker integration**: this flow does NOT create Jira/ADO tickets. Maintenance is not a feature — if a feature-level ticket spans the meta-repo's concerns, it lives in the per-component workspace.
|
||||
- **Onboarding is opt-in**: never auto-onboarded. User must explicitly request.
|
||||
|
||||
@@ -110,8 +110,7 @@ Before entering a step from this table for the first time in a session, verify t
|
||||
| Flow | Step | Sub-Step | Tracker Action |
|
||||
|------|------|----------|----------------|
|
||||
| greenfield | Plan | Step 6 — Epics | Create epics for each component |
|
||||
| greenfield | Decompose | Implementation decomposition Step 1 + Step 2 — Product tasks | Create ticket per product task, link to epic |
|
||||
| greenfield | Decompose Tests | Step 1t + Step 3 — All test tasks | Create ticket per task, link to epic |
|
||||
| greenfield | Decompose | Step 1 + Step 2 + Step 3 — All tasks | Create ticket per task, link to epic |
|
||||
| existing-code | Decompose Tests | Step 1t + Step 3 — All test tasks | Create ticket per task, link to epic |
|
||||
| existing-code | New Task | Step 7 — Ticket | Create ticket per task, link to epic |
|
||||
|
||||
@@ -139,7 +138,7 @@ One retry ladder covers all failure modes: explicit failure returned by a sub-sk
|
||||
|
||||
Treat the sub-skill as **failed** when ANY of the following is observed:
|
||||
|
||||
- The sub-skill explicitly returns a failed result (including blocked tasks, auto-fix loop exhaustion, prerequisite violations).
|
||||
- The sub-skill explicitly returns a failed result (including blocked subagents, auto-fix loop exhaustion, prerequisite violations).
|
||||
- **Stuck signals**: the same artifact is rewritten 3+ times without meaningful change; the sub-skill re-asks a question that was already answered; no new artifact has been saved despite active execution.
|
||||
|
||||
### Retry ladder
|
||||
@@ -292,7 +291,7 @@ For steps that produce `_docs/` artifacts (problem, research, plan, decompose, d
|
||||
|
||||
## Debug Protocol
|
||||
|
||||
When the implement skill's auto-fix loop fails (code review FAIL after 2 auto-fix attempts) or a task reports a blocker, the user is asked to intervene. This protocol guides the debugging process. (Retry budget and escalation are covered by Failure Handling above; this section is about *how* to diagnose once the user has been looped in.)
|
||||
When the implement skill's auto-fix loop fails (code review FAIL after 2 auto-fix attempts) or an implementer subagent reports a blocker, the user is asked to intervene. This protocol guides the debugging process. (Retry budget and escalation are covered by Failure Handling above; this section is about *how* to diagnose once the user has been looped in.)
|
||||
|
||||
### Structured Debugging Workflow
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ The autodev persists its position to `_docs/_autodev_state.md`. This is a lightw
|
||||
|
||||
## Current Step
|
||||
flow: [greenfield | existing-code | meta-repo]
|
||||
step: [1-17 for greenfield, 1-17 for existing-code, 1-6 for meta-repo, or "done"]
|
||||
step: [1-11 for greenfield, 1-17 for existing-code, 1-6 for meta-repo, or "done"]
|
||||
name: [step name from the active flow's Step Reference Table]
|
||||
status: [not_started / in_progress / completed / skipped / failed]
|
||||
sub_step:
|
||||
|
||||
@@ -209,7 +209,7 @@ Bug, Spec-Gap, Security, Performance, Maintainability, Style, Scope, Architectur
|
||||
|
||||
The `/implement` skill invokes this skill after each batch completes:
|
||||
|
||||
1. Collects changed files from all tasks implemented in the batch
|
||||
1. Collects changed files from all implementer agents in the batch
|
||||
2. Passes task spec paths + changed files to this skill
|
||||
3. If verdict is FAIL — presents findings to user (BLOCKING), user fixes or confirms
|
||||
4. If verdict is PASS or PASS_WITH_WARNINGS — proceeds automatically (findings shown as info)
|
||||
@@ -221,7 +221,7 @@ The `/implement` skill invokes this skill after each batch completes:
|
||||
| Input | Type | Source | Required |
|
||||
|-------|------|--------|----------|
|
||||
| `task_specs` | list of file paths | Task `.md` files from `_docs/02_tasks/todo/` for the current batch | Yes |
|
||||
| `changed_files` | list of file paths | Files modified by the tasks in the batch (from `git diff`) | Yes |
|
||||
| `changed_files` | list of file paths | Files modified by implementer agents (from `git diff` or agent reports) | Yes |
|
||||
| `batch_number` | integer | Current batch number (for report naming) | Yes |
|
||||
| `project_restrictions` | file path | `_docs/00_problem/restrictions.md` | If exists |
|
||||
| `solution_overview` | file path | `_docs/01_solution/solution.md` | If exists |
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
name: decompose
|
||||
description: |
|
||||
Decompose planned components into atomic implementable tasks with bootstrap structure plan.
|
||||
Workflow entrypoints: implementation task decomposition, single component decomposition, and tests-only decomposition.
|
||||
The invoking flow decides which entrypoint to run; this skill executes that selected sequence.
|
||||
4-step workflow: bootstrap structure plan, component task decomposition, blackbox test task decomposition, and cross-task verification.
|
||||
Supports full decomposition (_docs/ structure), single component mode, and tests-only mode.
|
||||
Trigger phrases:
|
||||
- "decompose", "decompose features", "feature decomposition"
|
||||
- "task decomposition", "break down components"
|
||||
@@ -20,7 +20,7 @@ Decompose planned components into atomic, implementable task specs with a bootst
|
||||
|
||||
## Core Principles
|
||||
|
||||
- **Atomic tasks**: each task does one thing; if it exceeds 5 complexity points, split it
|
||||
- **Atomic tasks**: each task does one thing; if it exceeds 8 complexity points, split it
|
||||
- **Behavioral specs, not implementation plans**: describe what the system should do, not how to build it
|
||||
- **Flat structure**: all tasks are tracker-ID-prefixed files in TASKS_DIR — no component subdirectories
|
||||
- **Save immediately**: write artifacts to disk after each task; never accumulate unsaved work
|
||||
@@ -30,15 +30,14 @@ Decompose planned components into atomic, implementable task specs with a bootst
|
||||
|
||||
## Context Resolution
|
||||
|
||||
Resolve the selected entrypoint from the invocation context before any other logic runs. The caller decides whether this is implementation, single component, or tests-only decomposition; this skill only executes the selected sequence.
|
||||
Determine the operating mode based on invocation before any other logic runs.
|
||||
|
||||
**Implementation task decomposition** (default; selected by flows before invoking this skill):
|
||||
**Default** (no explicit input file provided):
|
||||
|
||||
- DOCUMENT_DIR: `_docs/02_document/`
|
||||
- TASKS_DIR: `_docs/02_tasks/`
|
||||
- TASKS_TODO: `_docs/02_tasks/todo/`
|
||||
- Reads from: `_docs/00_problem/`, `_docs/01_solution/`, DOCUMENT_DIR
|
||||
- Produces only implementation tasks. Blackbox/e2e test task files are produced only when the invoking flow selects tests-only decomposition.
|
||||
|
||||
**Single component mode** (provided file is within `_docs/02_document/` and inside a `components/` subdirectory):
|
||||
|
||||
@@ -56,24 +55,24 @@ Resolve the selected entrypoint from the invocation context before any other log
|
||||
- TESTS_DIR: `DOCUMENT_DIR/tests/`
|
||||
- Reads from: `_docs/00_problem/`, `_docs/01_solution/`, TESTS_DIR
|
||||
|
||||
Announce the selected entrypoint and resolved paths to the user before proceeding.
|
||||
Announce the detected mode and resolved paths to the user before proceeding.
|
||||
|
||||
### Step Applicability by Mode
|
||||
|
||||
| Step | File | Implementation | Single | Tests-only |
|
||||
|------|------|:--------------:|:------:|:----------:|
|
||||
| Step | File | Default | Single | Tests-only |
|
||||
|------|------|:-------:|:------:|:----------:|
|
||||
| 1 Bootstrap Structure | `steps/01_bootstrap-structure.md` | ✓ | — | — |
|
||||
| 1t Test Infrastructure | `steps/01t_test-infrastructure.md` | — | — | ✓ |
|
||||
| 1.5 Module Layout | `steps/01-5_module-layout.md` | ✓ | — | — |
|
||||
| 2 Task Decomposition | `steps/02_task-decomposition.md` | ✓ | ✓ | — |
|
||||
| 3 Blackbox Test Tasks | `steps/03_blackbox-test-decomposition.md` | — | — | ✓ |
|
||||
| 3 Blackbox Test Tasks | `steps/03_blackbox-test-decomposition.md` | ✓ | — | ✓ |
|
||||
| 4 Cross-Verification | `steps/04_cross-verification.md` | ✓ | — | ✓ |
|
||||
|
||||
## Input Specification
|
||||
|
||||
### Required Files
|
||||
|
||||
**Implementation task decomposition:**
|
||||
**Default:**
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
@@ -81,11 +80,10 @@ Announce the selected entrypoint and resolved paths to the user before proceedin
|
||||
| `_docs/00_problem/restrictions.md` | Constraints and limitations |
|
||||
| `_docs/00_problem/acceptance_criteria.md` | Measurable acceptance criteria |
|
||||
| `_docs/01_solution/solution.md` | Finalized solution |
|
||||
| `DOCUMENT_DIR/architecture.md` | Architecture from plan/document skill (must contain a `## Architecture Vision` H2 — confirmed user intent) |
|
||||
| `DOCUMENT_DIR/glossary.md` | Project terminology (confirmed by user in plan Phase 2a.0 or document Step 4.5). Use it to keep task names, component references, and AC wording consistent with the user's vocabulary |
|
||||
| `DOCUMENT_DIR/architecture.md` | Architecture from plan skill |
|
||||
| `DOCUMENT_DIR/system-flows.md` | System flows from plan skill |
|
||||
| `DOCUMENT_DIR/components/[##]_[name]/description.md` | Component specs from plan skill |
|
||||
| `DOCUMENT_DIR/tests/` | Optional product acceptance context from test-spec skill; do not create test task files from it in this entrypoint |
|
||||
| `DOCUMENT_DIR/tests/` | Blackbox test specs from plan skill |
|
||||
|
||||
**Single component mode:**
|
||||
|
||||
@@ -112,7 +110,7 @@ Announce the selected entrypoint and resolved paths to the user before proceedin
|
||||
|
||||
### Prerequisite Checks (BLOCKING)
|
||||
|
||||
**Implementation task decomposition:**
|
||||
**Default:**
|
||||
|
||||
1. DOCUMENT_DIR contains `architecture.md` and `components/` — **STOP if missing**
|
||||
2. Create TASKS_DIR and TASKS_TODO if they do not exist
|
||||
@@ -146,8 +144,6 @@ TASKS_DIR/
|
||||
|
||||
**Naming convention**: Each task file is initially saved in `TASKS_TODO/` with a temporary numeric prefix (`[##]_[short_name].md`). After creating the work item ticket, rename the file to use the work item ticket ID as prefix (`[TRACKER-ID]_[short_name].md`). For example: `todo/01_initial_structure.md` → `todo/AZ-42_initial_structure.md`.
|
||||
|
||||
If tracker availability fails, follow `.cursor/rules/tracker.mdc` before continuing. Only when the user explicitly chooses `tracker: local` may the numeric prefix remain; in that mode set `Tracker: pending` and `Epic: pending` in the task header and keep the task eligible for later tracker sync.
|
||||
|
||||
### Save Timing
|
||||
|
||||
| Step | Save immediately after | Filename |
|
||||
@@ -169,11 +165,11 @@ If TASKS_DIR subfolders already contain task files:
|
||||
|
||||
## Progress Tracking
|
||||
|
||||
At the start of execution, create a TodoWrite with all applicable steps for the selected entrypoint (see Step Applicability table). Update status as each step/component completes.
|
||||
At the start of execution, create a TodoWrite with all applicable steps for the detected mode (see Step Applicability table). Update status as each step/component completes.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Bootstrap Structure Plan (implementation mode only)
|
||||
### Step 1: Bootstrap Structure Plan (default mode only)
|
||||
|
||||
Read and follow `steps/01_bootstrap-structure.md`.
|
||||
|
||||
@@ -185,25 +181,25 @@ Read and follow `steps/01t_test-infrastructure.md`.
|
||||
|
||||
---
|
||||
|
||||
### Step 1.5: Module Layout (implementation mode only)
|
||||
### Step 1.5: Module Layout (default mode only)
|
||||
|
||||
Read and follow `steps/01-5_module-layout.md`.
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Task Decomposition (implementation and single component modes)
|
||||
### Step 2: Task Decomposition (default and single component modes)
|
||||
|
||||
Read and follow `steps/02_task-decomposition.md`.
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Blackbox Test Task Decomposition (tests-only mode only)
|
||||
### Step 3: Blackbox Test Task Decomposition (default and tests-only modes)
|
||||
|
||||
Read and follow `steps/03_blackbox-test-decomposition.md`.
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Cross-Task Verification (implementation and tests-only modes)
|
||||
### Step 4: Cross-Task Verification (default and tests-only modes)
|
||||
|
||||
Read and follow `steps/04_cross-verification.md`.
|
||||
|
||||
@@ -211,7 +207,7 @@ Read and follow `steps/04_cross-verification.md`.
|
||||
|
||||
- **Coding during decomposition**: this workflow produces specs, never code
|
||||
- **Over-splitting**: don't create many tasks if the component is simple — 1 task is fine
|
||||
- **Tasks exceeding 5 points**: split them; no task should be too complex for a single implementer
|
||||
- **Tasks exceeding 8 points**: split them; no task should be too complex for a single implementer
|
||||
- **Cross-component tasks**: each task belongs to exactly one component
|
||||
- **Skipping BLOCKING gates**: never proceed past a BLOCKING marker without user confirmation
|
||||
- **Creating git branches**: branch creation is an implementation concern, not a decomposition one
|
||||
@@ -224,7 +220,7 @@ Read and follow `steps/04_cross-verification.md`.
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Ambiguous component boundaries | ASK user |
|
||||
| Task complexity exceeds 5 points after splitting | ASK user |
|
||||
| Task complexity exceeds 8 points after splitting | ASK user |
|
||||
| Missing component specs in DOCUMENT_DIR | ASK user |
|
||||
| Cross-component dependency conflict | ASK user |
|
||||
| Tracker epic not found for a component | ASK user for Epic ID |
|
||||
@@ -236,14 +232,15 @@ Read and follow `steps/04_cross-verification.md`.
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ Task Decomposition (Multi-Mode) │
|
||||
├────────────────────────────────────────────────────────────────┤
|
||||
│ CONTEXT: Invoke the selected entrypoint (implementation / single / tests-only) │
|
||||
│ CONTEXT: Resolve mode (default / single component / tests-only) │
|
||||
│ │
|
||||
│ IMPLEMENTATION TASK DECOMPOSITION: │
|
||||
│ DEFAULT MODE: │
|
||||
│ 1. Bootstrap Structure → steps/01_bootstrap-structure.md │
|
||||
│ [BLOCKING: user confirms structure] │
|
||||
│ 1.5 Module Layout → steps/01-5_module-layout.md │
|
||||
│ [BLOCKING: user confirms layout] │
|
||||
│ 2. Component Tasks → steps/02_task-decomposition.md │
|
||||
│ 3. Blackbox Tests → steps/03_blackbox-test-decomposition.md │
|
||||
│ 4. Cross-Verification → steps/04_cross-verification.md │
|
||||
│ [BLOCKING: user confirms dependencies] │
|
||||
│ │
|
||||
|
||||
@@ -26,7 +26,7 @@ For each component (or the single provided component):
|
||||
4. Do not create tasks for other components — only tasks for the current component
|
||||
5. Each task should be atomic, containing 1 API or a list of semantically connected APIs
|
||||
6. Write each task spec using `templates/task.md`
|
||||
7. Estimate complexity per task (1, 2, 3, 5 points); no task should exceed 5 points — split if it does
|
||||
7. Estimate complexity per task (1, 2, 3, 5, 8 points); no task should exceed 8 points — split if it does
|
||||
8. Note task dependencies (referencing tracker IDs of already-created dependency tasks, e.g., `AZ-42_initial_structure`)
|
||||
9. **Cross-cutting rule**: if a concern spans ≥2 components (logging, config loading, auth/authZ, error envelope, telemetry, feature flags, i18n), create ONE shared task under the cross-cutting epic. Per-component tasks declare it as a dependency and consume it; they MUST NOT re-implement it locally. Duplicate local implementations are an `Architecture` finding (High) in code-review Phase 7 and a `Maintainability` finding in Phase 6.
|
||||
10. **Shared-models / shared-API rule**: classify the task as shared if ANY of the following is true:
|
||||
@@ -43,32 +43,16 @@ For each component (or the single provided component):
|
||||
Consumers read the contract file, not the producer's task spec. This prevents interface drift when the producer's implementation detail leaks into consumers.
|
||||
11. **Immediately after writing each task file**: create a work item ticket, link it to the component's epic, write the work item ticket ID and Epic ID back into the task header, then rename the file from `todo/[##]_[short_name].md` to `todo/[TRACKER-ID]_[short_name].md`.
|
||||
|
||||
## Runtime Completeness Decomposition Gate
|
||||
|
||||
Before Step 2 is considered complete, scan `architecture.md`, `system-flows.md`, component descriptions, and the solution for named internal runtime capabilities and dependencies. Examples include BASALT/OpenVINS/Kimera, FAISS, DINOv2, ONNX/TensorRT, ALIKED/DISK, LightGlue, RANSAC, PostGIS, MAVLink emission, FDR rollover, and any "A-Z" user-visible pipeline.
|
||||
|
||||
For every named internal capability:
|
||||
|
||||
1. Ensure at least one implementation task explicitly owns the production integration or production algorithm.
|
||||
2. Do not treat "define protocol", "create adapter boundary", "add deterministic fallback", "create scaffold", or "prepare native bridge" as implementation of the capability unless the architecture explicitly says the real capability is out of scope.
|
||||
3. If a capability needs external hardware/data to verify, still create the production implementation task. Verification may be hardware-gated later; implementation must not be omitted.
|
||||
4. Add a `## Runtime Completeness` section to any affected task with:
|
||||
- named capability/dependency,
|
||||
- production code that must exist,
|
||||
- allowed external stubs, if any,
|
||||
- unacceptable substitutes such as fake/deterministic/internal stubs.
|
||||
|
||||
## Self-verification (per component)
|
||||
|
||||
- [ ] Every task is atomic (single concern)
|
||||
- [ ] No task exceeds 5 complexity points
|
||||
- [ ] No task exceeds 8 complexity points
|
||||
- [ ] Task dependencies reference correct tracker IDs
|
||||
- [ ] Tasks cover all interfaces defined in the component spec
|
||||
- [ ] No tasks duplicate work from other components
|
||||
- [ ] Every task has a work item ticket linked to the correct epic
|
||||
- [ ] Every shared-models / shared-API task has a contract file at `_docs/02_document/contracts/<component>/<name>.md` and a `## Contract` section linking to it
|
||||
- [ ] Every cross-cutting concern appears exactly once as a shared task, not N per-component copies
|
||||
- [ ] Every named internal runtime capability has a production implementation task, not only an interface/scaffold/fallback task
|
||||
|
||||
## Save action
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Step 3: Blackbox Test Task Decomposition (tests-only mode only)
|
||||
# Step 3: Blackbox Test Task Decomposition (default and tests-only modes)
|
||||
|
||||
**Role**: Professional Quality Assurance Engineer
|
||||
**Goal**: Decompose blackbox test specs into atomic, implementable task specs.
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
## Numbering
|
||||
|
||||
- In default mode: continue sequential numbering from where Step 2 left off.
|
||||
- In tests-only mode: start from 02 (01 is the test infrastructure bootstrap from Step 1t).
|
||||
|
||||
## Steps
|
||||
@@ -13,26 +14,21 @@
|
||||
1. Read all test specs from `DOCUMENT_DIR/tests/` (`blackbox-tests.md`, `performance-tests.md`, `resilience-tests.md`, `security-tests.md`, `resource-limit-tests.md`)
|
||||
2. Group related test scenarios into atomic tasks (e.g., one task per test category or per component under test)
|
||||
3. Each task should reference the specific test scenarios it implements and the environment/test-data specs
|
||||
4. Add a **System Under Test Boundary** section to every e2e/blackbox test task:
|
||||
- The test must drive the product through public runtime boundaries and compare actual outputs to `_docs/00_problem/input_data/expected_results/results_report.md` and any referenced machine-readable expected-result files.
|
||||
- Stubs are allowed only for external systems outside the product boundary: flight controller/SITL, QGC observer, satellite-provider/Suite service, physical Jetson hardware, physical camera, licensed public datasets, and network services.
|
||||
- Stubs, fakes, deterministic fallbacks, monkeypatches, or direct imports are not allowed for internal product modules that the scenario is meant to validate, such as VIO, safety/anchor wrapper, satellite retrieval, anchor verification, tile manager, MAVLink output adapter, or FDR.
|
||||
- If an internal module is not implemented, the test must fail/block as missing product implementation; it must not pass by replacing that module with a test stub.
|
||||
5. Dependencies:
|
||||
4. Dependencies:
|
||||
- In default mode: blackbox test tasks depend on the component implementation tasks they exercise
|
||||
- In tests-only mode: blackbox test tasks depend on the test infrastructure bootstrap task (Step 1t)
|
||||
6. Write each task spec using `templates/task.md`
|
||||
7. Estimate complexity per task (1, 2, 3, 5 points); no task should exceed 5 points — split if it does
|
||||
8. Note task dependencies (referencing tracker IDs of already-created dependency tasks)
|
||||
9. **Immediately after writing each task file**: create a work item ticket under the "Blackbox Tests" epic, write the work item ticket ID and Epic ID back into the task header, then rename the file from `todo/[##]_[short_name].md` to `todo/[TRACKER-ID]_[short_name].md`.
|
||||
5. Write each task spec using `templates/task.md`
|
||||
6. Estimate complexity per task (1, 2, 3, 5, 8 points); no task should exceed 8 points — split if it does
|
||||
7. Note task dependencies (referencing tracker IDs of already-created dependency tasks)
|
||||
8. **Immediately after writing each task file**: create a work item ticket under the "Blackbox Tests" epic, write the work item ticket ID and Epic ID back into the task header, then rename the file from `todo/[##]_[short_name].md` to `todo/[TRACKER-ID]_[short_name].md`.
|
||||
|
||||
## Self-verification
|
||||
|
||||
- [ ] Every scenario from `tests/blackbox-tests.md` is covered by a task
|
||||
- [ ] Every scenario from `tests/performance-tests.md`, `tests/resilience-tests.md`, `tests/security-tests.md`, and `tests/resource-limit-tests.md` is covered by a task
|
||||
- [ ] No task exceeds 5 complexity points
|
||||
- [ ] Dependencies correctly reference the test infrastructure task
|
||||
- [ ] No task exceeds 8 complexity points
|
||||
- [ ] Dependencies correctly reference the dependency tasks (component tasks in default mode, test infrastructure in tests-only mode)
|
||||
- [ ] Every task has a work item ticket linked to the "Blackbox Tests" epic
|
||||
- [ ] Every e2e/blackbox task forbids internal product stubs/fakes and requires comparison against expected-results artifacts
|
||||
|
||||
## Save action
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Step 4: Cross-Task Verification (implementation and tests-only modes)
|
||||
# Step 4: Cross-Task Verification (default and tests-only modes)
|
||||
|
||||
**Role**: Professional software architect and analyst
|
||||
**Goal**: Verify task consistency and produce `_dependencies_table.md`.
|
||||
@@ -8,20 +8,17 @@
|
||||
|
||||
1. Verify task dependencies across all tasks are consistent
|
||||
2. Check no gaps:
|
||||
- In implementation mode: every product interface in `architecture.md` has implementation task coverage
|
||||
- In default mode: every interface in `architecture.md` has tasks covering it
|
||||
- In tests-only mode: every test scenario in `traceability-matrix.md` is covered by a task
|
||||
- In implementation mode: every named internal runtime capability/dependency from architecture, solution, system flows, and component descriptions has a production implementation task, not only an interface/scaffold/fallback task
|
||||
- In tests-only mode: every e2e/blackbox task has a System Under Test Boundary section that forbids stubbing internal product modules and requires comparison to expected-results artifacts
|
||||
3. Check no overlaps: tasks don't duplicate work
|
||||
4. Check no circular dependencies in the task graph
|
||||
5. Produce `_dependencies_table.md` using `templates/dependencies-table.md`
|
||||
|
||||
## Self-verification
|
||||
|
||||
### Implementation mode
|
||||
### Default mode
|
||||
|
||||
- [ ] Every product interface in `architecture.md` is covered by at least one implementation task
|
||||
- [ ] Every named internal runtime capability has a production implementation task
|
||||
- [ ] Every architecture interface is covered by at least one task
|
||||
- [ ] No circular dependencies in the task graph
|
||||
- [ ] Cross-component dependencies are explicitly noted in affected task specs
|
||||
- [ ] `_dependencies_table.md` contains every task with correct dependencies
|
||||
@@ -29,7 +26,6 @@
|
||||
### Tests-only mode
|
||||
|
||||
- [ ] Every test scenario from `traceability-matrix.md` "Covered" entries has a corresponding task
|
||||
- [ ] Every e2e/blackbox task validates actual product behavior and allows stubs only for external systems
|
||||
- [ ] No circular dependencies in the task graph
|
||||
- [ ] Test task dependencies reference the test infrastructure bootstrap
|
||||
- [ ] `_dependencies_table.md` contains every task with correct dependencies
|
||||
|
||||
@@ -28,4 +28,4 @@ Use this template after cross-task verification. Save as `TASKS_DIR/_dependencie
|
||||
- Dependencies column lists tracker IDs (e.g., "AZ-43, AZ-44") or "None"
|
||||
- No circular dependencies allowed
|
||||
- Tasks should be listed in recommended execution order
|
||||
- The `/implement` skill reads this table to compute dependency-aware batches; task execution remains sequential
|
||||
- The `/implement` skill reads this table to compute parallel batches
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Module Layout Template
|
||||
|
||||
The module layout is the **authoritative file-ownership map** used by the `/implement` skill to assign OWNED / READ-ONLY / FORBIDDEN files to each task. It is derived from `_docs/02_document/architecture.md` and the component specs at `_docs/02_document/components/`, and it follows the target language's standard project-layout conventions.
|
||||
The module layout is the **authoritative file-ownership map** used by the `/implement` skill to assign OWNED / READ-ONLY / FORBIDDEN files to implementer subagents. It is derived from `_docs/02_document/architecture.md` and the component specs at `_docs/02_document/components/`, and it follows the target language's standard project-layout conventions.
|
||||
|
||||
Save as `_docs/02_document/module-layout.md`. This file is produced by the decompose skill (Step 1.5 module layout) and consumed by the implement skill (Step 4 file ownership). Task specs remain purely behavioral — they do NOT carry file paths. The layout is the single place where component → filesystem mapping lives.
|
||||
|
||||
@@ -104,4 +104,4 @@ The implement skill's Step 4 (File Ownership) reads this file and, for each task
|
||||
3. Set READ-ONLY = the Public API files of every component listed in `Imports from`, plus `shared/*` Public API files.
|
||||
4. Set FORBIDDEN = every other component's Owns glob.
|
||||
|
||||
Execution inside a batch is already sequential (one task at a time). This mapping is still required because it enforces scope discipline per task — preventing a task from drifting into files that belong to another component.
|
||||
If two tasks in the same batch map to the same component, the implement skill schedules them sequentially (one implementer at a time for that component) to avoid file conflicts on shared internal files.
|
||||
|
||||
@@ -11,7 +11,7 @@ Save as `TASKS_DIR/[##]_[short_name].md` initially, then rename to `TASKS_DIR/[T
|
||||
**Task**: [TRACKER-ID]_[short_name]
|
||||
**Name**: [short human name]
|
||||
**Description**: [one-line description of what this task delivers]
|
||||
**Complexity**: [1|2|3|5] points
|
||||
**Complexity**: [1|2|3|5|8] points
|
||||
**Dependencies**: [AZ-43_shared_models, AZ-44_db_migrations] or "None"
|
||||
**Component**: [component name for context]
|
||||
**Tracker**: [TASK-ID]
|
||||
@@ -102,7 +102,8 @@ Consumers MUST read that file — not this task spec — to discover the interfa
|
||||
- 2 points: Non-trivial, low complexity, minimal coordination
|
||||
- 3 points: Multi-step, moderate complexity, potential alignment needed
|
||||
- 5 points: Difficult, interconnected logic, medium-high risk
|
||||
- 8+ points: Too complex — split into smaller tasks
|
||||
- 8 points: High difficulty, high ambiguity or coordination, multiple components
|
||||
- 13 points: Too complex — split into smaller tasks
|
||||
|
||||
## Output Guidelines
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
- Application components under test
|
||||
- Test runner container (black-box, no internal imports)
|
||||
- Isolated database with seed data
|
||||
- All tests runnable via `docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from e2e-runner`
|
||||
- See the Woodpecker two-workflow contract in [`../templates/ci_cd_pipeline.md`](../templates/ci_cd_pipeline.md) — the test runner entry point defined here becomes the first step of `.woodpecker/01-test.yml`.
|
||||
- All tests runnable via `docker compose -f docker-compose.test.yml up --abort-on-container-exit`
|
||||
7. Define image tagging strategy: `<registry>/<project>/<component>:<git-sha>` for CI, `latest` for local dev only
|
||||
|
||||
## Self-verification
|
||||
|
||||
@@ -85,140 +85,3 @@ Save as `_docs/04_deploy/ci_cd_pipeline.md`.
|
||||
| Deploy success | [Slack] | [team] |
|
||||
| Deploy failure | [Slack/email + PagerDuty] | [on-call] |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference Implementation: Woodpecker CI two-workflow contract
|
||||
|
||||
Use this when the project's CI is **Woodpecker** and the test layout follows the autodev e2e contract from [`../../decompose/templates/test-infrastructure-task.md`](../../decompose/templates/test-infrastructure-task.md) (an `e2e/` folder containing `Dockerfile`, `docker-compose.test.yml`, `conftest.py`, `requirements.txt`, `mocks/`, `fixtures/`, `tests/`).
|
||||
|
||||
The contract is **two workflows in `.woodpecker/`**, scheduled on the same agent label, with the build workflow gated on a successful test run:
|
||||
|
||||
- `.woodpecker/01-test.yml` — runs the e2e contract, publishes `results/report.csv` as an artifact, fails the pipeline on any test failure.
|
||||
- `.woodpecker/02-build-push.yml` — `depends_on: [01-test]`. Builds the image, tags it `${CI_COMMIT_BRANCH}-${TAG_SUFFIX}`, pushes it to the registry. Skipped automatically if test failed.
|
||||
|
||||
The agent label is parameterized via `matrix:` so a single workflow file fans out across architectures: `labels: platform: ${PLATFORM}` routes each matrix entry to the matching agent. Both workflows for a repo must use the same matrix so test and build run on the same machine and share Docker layer cache. New architectures = new matrix entries; never new files.
|
||||
|
||||
### Multi-arch matrix conventions
|
||||
|
||||
| Variable | Meaning | Typical values |
|
||||
|----------|---------|----------------|
|
||||
| `PLATFORM` | Woodpecker agent label — selects which physical machine runs the entry. | `arm64`, `amd64` |
|
||||
| `TAG_SUFFIX` | Image tag suffix appended after the branch name. | `arm`, `amd` |
|
||||
| `DOCKERFILE` *(only when arches need different Dockerfiles)* | Path to the Dockerfile for this entry. | `Dockerfile`, `Dockerfile.jetson` |
|
||||
|
||||
Most repos use the same `Dockerfile` for both arches (multi-arch base images handle the rest), so `DOCKERFILE` can be omitted from the matrix and hardcoded in the build command. Repos with split per-arch Dockerfiles (e.g., `detections` uses `Dockerfile.jetson` on Jetson with TensorRT/CUDA-on-L4T) declare `DOCKERFILE` as a matrix var.
|
||||
|
||||
When only one architecture is currently in use, keep the matrix block with a single entry and the second entry commented out — adding a new arch is then a one-line uncomment, not a structural change.
|
||||
|
||||
### `.woodpecker/01-test.yml`
|
||||
|
||||
```yaml
|
||||
when:
|
||||
event: [push, pull_request, manual]
|
||||
branch: [dev, stage, main]
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- PLATFORM: arm64
|
||||
TAG_SUFFIX: arm
|
||||
# - PLATFORM: amd64
|
||||
# TAG_SUFFIX: amd
|
||||
|
||||
labels:
|
||||
platform: ${PLATFORM}
|
||||
|
||||
steps:
|
||||
- name: e2e
|
||||
image: docker
|
||||
commands:
|
||||
- cd e2e
|
||||
- docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from e2e-runner --build
|
||||
- docker compose -f docker-compose.test.yml down -v
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
- name: report
|
||||
image: docker
|
||||
when:
|
||||
status: [success, failure]
|
||||
commands:
|
||||
- test -f e2e/results/report.csv && cat e2e/results/report.csv || echo "no report"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--abort-on-container-exit` shuts the whole compose down as soon as ANY service exits, so a crashed dependency surfaces immediately instead of hanging the runner.
|
||||
- `--exit-code-from e2e-runner` ensures the pipeline's exit code reflects the test runner's, not the SUT's.
|
||||
- The `report` step runs on `[success, failure]` so the report is always published; without this the CSV is lost on red builds.
|
||||
- `down -v` between runs drops mock state and DB volumes — every test run starts clean.
|
||||
|
||||
### `.woodpecker/02-build-push.yml`
|
||||
|
||||
```yaml
|
||||
when:
|
||||
event: [push, manual]
|
||||
branch: [dev, stage, main]
|
||||
|
||||
depends_on:
|
||||
- 01-test
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- PLATFORM: arm64
|
||||
TAG_SUFFIX: arm
|
||||
# - PLATFORM: amd64
|
||||
# TAG_SUFFIX: amd
|
||||
|
||||
labels:
|
||||
platform: ${PLATFORM}
|
||||
|
||||
steps:
|
||||
- name: build-push
|
||||
image: docker
|
||||
environment:
|
||||
REGISTRY_HOST:
|
||||
from_secret: registry_host
|
||||
REGISTRY_USER:
|
||||
from_secret: registry_user
|
||||
REGISTRY_TOKEN:
|
||||
from_secret: registry_token
|
||||
commands:
|
||||
- echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
|
||||
- export TAG=${CI_COMMIT_BRANCH}-${TAG_SUFFIX}
|
||||
- export BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
- |
|
||||
docker build -f Dockerfile \
|
||||
--build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA \
|
||||
--label org.opencontainers.image.revision=$CI_COMMIT_SHA \
|
||||
--label org.opencontainers.image.created=$BUILD_DATE \
|
||||
--label org.opencontainers.image.source=$CI_REPO_URL \
|
||||
-t $REGISTRY_HOST/azaion/<service>:$TAG .
|
||||
- docker push $REGISTRY_HOST/azaion/<service>:$TAG
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `depends_on: [01-test]` is enforced by Woodpecker — a failed `01-test` (any matrix entry) skips this workflow.
|
||||
- The build workflow does NOT trigger on `pull_request` events: PRs get test signal only; pushes to `dev`/`stage`/`main` produce images. Avoids polluting the registry with PR images.
|
||||
- Replace `<service>` with the actual service name (matches the registry namespace pattern `azaion/<service>`).
|
||||
- For repos with split per-arch Dockerfiles, add `DOCKERFILE: Dockerfile.jetson` (or similar) to the matrix entry and substitute `${DOCKERFILE}` for `Dockerfile` in the `docker build -f` line.
|
||||
|
||||
### Variations by stack
|
||||
|
||||
The contract is language-agnostic because the runner is `docker compose`. The Dockerfile inside `e2e/` selects the test framework:
|
||||
|
||||
| Stack | `e2e/Dockerfile` runs |
|
||||
|-------|----------------------|
|
||||
| Python | `pytest --csv=/results/report.csv -v` |
|
||||
| .NET | `dotnet test --logger:"trx;LogFileName=/results/report.trx"` (convert to CSV in a final step if needed) |
|
||||
| Node/UI | `npm test -- --reporters=default --reporters=jest-junit --outputDirectory=/results` |
|
||||
| Rust | `cargo test --no-fail-fast -- --format json > /results/report.json` |
|
||||
|
||||
When the repo has **only unit tests** (no `e2e/docker-compose.test.yml`), drop the compose orchestration and run the native test command directly inside a stack-appropriate image. Keep the same two-workflow split — `01-test.yml` runs unit tests, `02-build-push.yml` is unchanged.
|
||||
|
||||
### Manual-trigger override (test infrastructure not yet validated)
|
||||
|
||||
If a repo ships a complete `e2e/` layout but the test fixtures are not yet validated end-to-end (e.g., expected-results data is still being authored), gate `01-test.yml` on `event: [manual]` only and add a TODO comment pointing to the unblocking task. The `02-build-push.yml` workflow drops its `depends_on` clause for the manual-only window — an explicit and reversible exception, not a permanent split.
|
||||
|
||||
@@ -31,7 +31,6 @@ _docs/
|
||||
│ ├── components.md
|
||||
│ └── flows/
|
||||
├── 04_verification_log.md # Step 4
|
||||
├── glossary.md # Step 4.5 (confirmed-by-user)
|
||||
├── FINAL_report.md # Step 7
|
||||
└── state.json # Resumability
|
||||
```
|
||||
@@ -50,7 +49,6 @@ Maintained in `DOCUMENT_DIR/state.json` for resumability:
|
||||
"modules_remaining": ["services/auth", "api/endpoints"],
|
||||
"module_batch": 1,
|
||||
"components_written": [],
|
||||
"step_4_5_glossary_vision": "not_started",
|
||||
"last_updated": "2026-03-21T14:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ Covers three related modes that share the same 8-step pipeline:
|
||||
|
||||
## Progress Tracking
|
||||
|
||||
Create a TodoWrite with all steps (0 through 7, including the inline Step 2.5 Module Layout Derivation and Step 4.5 Glossary & Architecture Vision). Update status as each step completes.
|
||||
Create a TodoWrite with all steps (0 through 7). Update status as each step completes.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -251,107 +251,7 @@ Apply corrections inline to the documents that need them.
|
||||
|
||||
**BLOCKING**: Present verification summary to user. Do NOT proceed until user confirms corrections are acceptable or requests additional fixes.
|
||||
|
||||
---
|
||||
|
||||
### Step 4.5: Glossary & Architecture Vision (BLOCKING)
|
||||
|
||||
**Role**: Software architect + business analyst
|
||||
**Goal**: Reconcile the AI's verified understanding of the codebase with the user's intended terminology and architecture vision. Existing-code projects often carry domain language and structural intent that is invisible from code alone (synonyms, deprecated names, modules that are "supposed to" be split, components the user thinks of as one logical unit even though they live in two folders). This step makes that intent explicit before any downstream skill (refactor, decompose, new-task) acts on the docs.
|
||||
|
||||
**When this step runs**:
|
||||
- Always, after Step 4 (Verification Pass) — for Full and Resume modes.
|
||||
- **Skipped** in Focus Area mode (the glossary/vision is system-wide; running it on a partial scan would produce a partial glossary). Resume the user once a full pass exists.
|
||||
|
||||
**Inputs** (already on disk after Step 4):
|
||||
- `DOCUMENT_DIR/architecture.md`, `system-flows.md`, `data_model.md`, `deployment/*`
|
||||
- `DOCUMENT_DIR/components/*/description.md`
|
||||
- `DOCUMENT_DIR/modules/*.md`
|
||||
- `DOCUMENT_DIR/04_verification_log.md` (so the AI knows which doc parts are confirmed vs. flagged)
|
||||
|
||||
**Outputs**:
|
||||
- `DOCUMENT_DIR/glossary.md` (NEW)
|
||||
- `DOCUMENT_DIR/architecture.md` updated in place: a new `## Architecture Vision` section is prepended (or merged into an existing "Overview" / "Vision" heading if already present); existing technical sections are preserved verbatim
|
||||
|
||||
**Procedure**:
|
||||
|
||||
1. **Draft glossary** from verified docs:
|
||||
- Domain entities, processes, roles named in module/component docs
|
||||
- Acronyms / abbreviations
|
||||
- Internal codenames (project, service, model names) that recur in the codebase
|
||||
- Synonym pairs the AI noticed (e.g., the codebase uses "flight" but module comments say "mission")
|
||||
- Stakeholder personas if any docs reference them
|
||||
Each entry: one-line definition + source reference (`source: components/03_flights/description.md`). Skip generic CS/industry terms.
|
||||
|
||||
2. **Draft architecture vision** as the AI currently understands the codebase:
|
||||
- **One paragraph**: what the system is, who runs it, the runtime topology shape (monolith / services / pipeline / library / hybrid), and the dominant pattern (e.g., "submodule-based meta-repo with REST + SSE between UI and backend").
|
||||
- **Components & responsibilities** (one-line each), pulled from `components/*/description.md`.
|
||||
- **Major data flows** (one or two sentences each), pulled from `system-flows.md`.
|
||||
- **Architectural principles / non-negotiables** the AI inferred from the code (e.g., "DB-driven config", "all UI traffic via REST + SSE only", "no per-component shared state"). Mark each with `inferred-from: <source>`.
|
||||
- **Open questions / drift signals**: places where the code disagrees with itself, or where the AI cannot tell intent from implementation (e.g., two components doing similar work — is that legacy duplication or deliberate?).
|
||||
|
||||
3. **Present condensed view** to the user (NOT the full draft files — a synopsis only):
|
||||
|
||||
```
|
||||
══════════════════════════════════════
|
||||
REVIEW: Glossary + Architecture Vision (existing code)
|
||||
══════════════════════════════════════
|
||||
Glossary (N terms drafted from verified docs):
|
||||
- <Term>: <one-line definition>
|
||||
- ...
|
||||
|
||||
Architecture Vision — as inferred from the codebase:
|
||||
<one-paragraph synopsis>
|
||||
|
||||
Components / responsibilities:
|
||||
- <component>: <one-line>
|
||||
- ...
|
||||
|
||||
Principles / non-negotiables (inferred):
|
||||
- <principle> [inferred-from: <source>]
|
||||
- ...
|
||||
|
||||
Open questions / drift signals:
|
||||
- <q1>
|
||||
- <q2>
|
||||
══════════════════════════════════════
|
||||
A) Inferred vision matches my intent — write the files
|
||||
B) Add / correct entries (provide diffs — terms, components,
|
||||
principles, or rename pairs)
|
||||
C) Resolve the open questions / drift signals first
|
||||
══════════════════════════════════════
|
||||
Recommendation: pick C if any drift signals exist;
|
||||
otherwise B if the vision misses
|
||||
project-specific intent; A only when
|
||||
the inferred vision is exactly right.
|
||||
══════════════════════════════════════
|
||||
```
|
||||
|
||||
4. **Iterate**:
|
||||
- On B → integrate the user's diffs/additions, re-present, loop until A.
|
||||
- On C → ask the listed open questions in one batch (M4-style), integrate answers, re-present.
|
||||
- **Do NOT proceed to step 5 until the user picks A.**
|
||||
|
||||
5. **Save**:
|
||||
- Write `DOCUMENT_DIR/glossary.md`, alphabetical, with a top-line `**Status**: confirmed-by-user` and the date.
|
||||
- Update `DOCUMENT_DIR/architecture.md`:
|
||||
- If a `## Architecture Vision` (or `## Vision` / `## Overview`) section already exists at the top, replace its body with the confirmed paragraph + components + principles.
|
||||
- Otherwise, insert `## Architecture Vision` as the first H2 after the title; preserve every existing H2 below.
|
||||
- Do NOT delete or re-order existing technical sections (Tech Stack, Deployment Model, Data Model, NFRs, ADRs).
|
||||
|
||||
6. **Update `state.json`**: mark `step_4_5_glossary_vision: confirmed`. Resume on rerun must skip this step unless the user explicitly invokes `/document --refresh-vision`.
|
||||
|
||||
**Self-verification**:
|
||||
- [ ] Every glossary entry traces to at least one file under `DOCUMENT_DIR/`
|
||||
- [ ] Every component listed in the vision matches a folder under `DOCUMENT_DIR/components/`
|
||||
- [ ] All open questions are answered or explicitly deferred (with the user's acknowledgement)
|
||||
- [ ] `architecture.md` still contains all H2 sections it had before this step
|
||||
- [ ] User picked option A on the latest condensed view
|
||||
|
||||
**BLOCKING**: Do NOT proceed to the session boundary / Step 5 until both files are saved and the user has picked A.
|
||||
|
||||
---
|
||||
|
||||
**Session boundary**: After Step 4.5 is confirmed, suggest a session break before proceeding to the synthesis steps (5–7). These steps produce different artifact types and benefit from fresh context:
|
||||
**Session boundary**: After verification is confirmed, suggest a session break before proceeding to the synthesis steps (5–7). These steps produce different artifact types and benefit from fresh context:
|
||||
|
||||
```
|
||||
══════════════════════════════════════
|
||||
|
||||
@@ -1,59 +1,41 @@
|
||||
---
|
||||
name: implement
|
||||
description: |
|
||||
Implement tasks sequentially with dependency-aware batching and integrated code review.
|
||||
Orchestrate task implementation with dependency-aware batching, parallel subagents, and integrated code review.
|
||||
Reads flat task files and _dependencies_table.md from TASKS_DIR, computes execution batches via topological sort,
|
||||
implements tasks one at a time in dependency order, runs code-review skill after each batch, and loops until done.
|
||||
launches up to 4 implementer subagents in parallel, runs code-review skill after each batch, and loops until done.
|
||||
Use after /decompose has produced task files.
|
||||
Trigger phrases:
|
||||
- "implement", "start implementation", "implement tasks"
|
||||
- "execute tasks"
|
||||
- "run implementers", "execute tasks"
|
||||
category: build
|
||||
tags: [implementation, batching, code-review]
|
||||
tags: [implementation, orchestration, batching, parallel, code-review]
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Implementation Runner
|
||||
# Implementation Orchestrator
|
||||
|
||||
Implement all tasks produced by the `/decompose` skill. This skill reads task specs, computes execution order, writes the code and tests for each task **sequentially** (no subagents, no parallel execution), validates results via the `/code-review` skill, and escalates issues.
|
||||
Orchestrate the implementation of all tasks produced by the `/decompose` skill. This skill is a **pure orchestrator** — it does NOT write implementation code itself. It reads task specs, computes execution order, delegates to `implementer` subagents, validates results via the `/code-review` skill, and escalates issues.
|
||||
|
||||
For each task the main agent receives a task spec, analyzes the codebase, implements the feature, writes tests, and verifies acceptance criteria — then moves on to the next task.
|
||||
The `implementer` agent is the specialist that writes all the code — it receives a task spec, analyzes the codebase, implements the feature, writes tests, and verifies acceptance criteria.
|
||||
|
||||
## Core Principles
|
||||
|
||||
- **Sequential execution**: implement one task at a time. Do NOT spawn subagents and do NOT run tasks in parallel. (See `.cursor/rules/no-subagents.mdc`.)
|
||||
- **Dependency-aware ordering**: tasks run only when all their dependencies are satisfied
|
||||
- **Batching for review, not parallelism**: tasks are grouped into batches so `/code-review` and commits operate on a coherent unit of work — all tasks inside a batch are still implemented one after the other
|
||||
- **Orchestrate, don't implement**: this skill delegates all coding to `implementer` subagents
|
||||
- **Dependency-aware batching**: tasks run only when all their dependencies are satisfied
|
||||
- **Max 4 parallel agents**: never launch more than 4 implementer subagents simultaneously
|
||||
- **File isolation**: no two parallel agents may write to the same file
|
||||
- **Integrated review**: `/code-review` skill runs automatically after each batch
|
||||
- **Completeness before testing**: product implementation is not done until code is checked against task outcomes, included scope, architecture/component promises, named runtime dependencies, and unresolved scaffold/native placeholders — not just task AC tests
|
||||
- **Runtime dependency reality**: production code cannot satisfy a task by exposing only a protocol, fake runner, deterministic fallback, or "native bridge" placeholder when the task/architecture promises a concrete internal capability such as BASALT VIO, FAISS retrieval, LightGlue matching, or a full A-Z localization pipeline. Stubs are allowed only for external systems and tests.
|
||||
- **Auto-start**: batches start immediately — no user confirmation before a batch
|
||||
- **Auto-start**: batches launch immediately — no user confirmation before a batch
|
||||
- **Gate on failure**: user confirmation is required only when code review returns FAIL
|
||||
- **Commit per batch**: after each batch is confirmed, commit. Ask the user whether to push to remote unless the user previously opted into auto-push for this session.
|
||||
|
||||
## Context Resolution
|
||||
|
||||
- TASKS_DIR: `_docs/02_tasks/`
|
||||
- Task files: selected `*.md` files in `TASKS_DIR/todo/` (excluding files starting with `_`)
|
||||
- Task files: all `*.md` files in `TASKS_DIR/todo/` (excluding files starting with `_`)
|
||||
- Dependency table: `TASKS_DIR/_dependencies_table.md`
|
||||
|
||||
### Task Selection Context
|
||||
|
||||
The invoking flow decides which task category this run should execute. The implement skill must honor that selected context instead of consuming every file in `todo/`.
|
||||
|
||||
| Context | Selected task files |
|
||||
|---------|---------------------|
|
||||
| Product implementation | Task specs that are not test-only and not refactoring specs |
|
||||
| Test implementation | `*_test_infrastructure.md` plus task specs whose `Component` or `Epic` identifies `Blackbox Tests` |
|
||||
| Refactoring | Task specs whose filename or task ID includes `_refactor_` |
|
||||
|
||||
If no explicit context is provided, infer it from the active autodev step:
|
||||
- greenfield Step 7 or existing-code Step 10 → Product implementation
|
||||
- greenfield Step 10 or existing-code Step 6 → Test implementation
|
||||
- refactor Phase 4 → Refactoring
|
||||
|
||||
Unselected task files remain in `TASKS_DIR/todo/` for their later flow step.
|
||||
|
||||
### Task Lifecycle Folders
|
||||
|
||||
```
|
||||
@@ -66,8 +48,7 @@ TASKS_DIR/
|
||||
|
||||
## Prerequisite Checks (BLOCKING)
|
||||
|
||||
1. `TASKS_DIR/todo/` exists and contains at least one task file for the selected context — **STOP if missing**
|
||||
- Exception for Product implementation re-entry: if no selected product tasks remain in `todo/`, but the active autodev state is Step 7 or the latest product completeness report is missing/invalid/contains `FAIL`, skip directly to Step 15 (Product Implementation Completeness Gate). This gate may create remediation tasks and return to Step 1. Do not write a final implementation report from this state.
|
||||
1. `TASKS_DIR/todo/` exists and contains at least one task file — **STOP if missing**
|
||||
2. `_dependencies_table.md` exists — **STOP if missing**
|
||||
3. At least one task is not yet completed — **STOP if all done**
|
||||
4. **Working tree is clean** — run `git status --porcelain`; the output must be empty.
|
||||
@@ -75,16 +56,16 @@ TASKS_DIR/
|
||||
- A) Commit or stash stray changes manually, then re-invoke `/implement`
|
||||
- B) Agent commits stray changes as a single `chore: WIP pre-implement` commit and proceeds
|
||||
- C) Abort
|
||||
- Rationale: each batch ends with a commit. Unrelated uncommitted changes would get silently folded into batch commits otherwise.
|
||||
- Rationale: implementer subagents edit files in parallel and commit per batch. Unrelated uncommitted changes get silently folded into batch commits otherwise.
|
||||
- This check is repeated at the start of each batch iteration (see step 6 / step 14 Loop).
|
||||
|
||||
## Algorithm
|
||||
|
||||
### 1. Parse
|
||||
|
||||
- Read selected task `*.md` files from `TASKS_DIR/todo/` (excluding files starting with `_`)
|
||||
- Read all task `*.md` files from `TASKS_DIR/todo/` (excluding files starting with `_`)
|
||||
- Read `_dependencies_table.md` — parse into a dependency graph (DAG)
|
||||
- Validate: no circular dependencies in the selected task graph, all referenced selected-task dependencies exist or are already completed in `TASKS_DIR/done/`
|
||||
- Validate: no circular dependencies, all referenced dependencies exist
|
||||
|
||||
### 2. Detect Progress
|
||||
|
||||
@@ -97,8 +78,8 @@ TASKS_DIR/
|
||||
|
||||
- Topological sort remaining tasks
|
||||
- Select tasks whose dependencies are ALL satisfied (completed)
|
||||
- A batch is simply a coherent group of tasks for review + commit. Within the batch, tasks are implemented sequentially in topological order.
|
||||
- Cap the batch size at a reasonable review scope (default: 4 tasks)
|
||||
- If a ready task depends on any task currently being worked on in this batch, it must wait for the next batch
|
||||
- Cap the batch at 4 parallel agents
|
||||
- If the batch would exceed 20 total complexity points, suggest splitting and let the user decide
|
||||
|
||||
### 4. Assign File Ownership
|
||||
@@ -108,12 +89,11 @@ The authoritative file-ownership map is `_docs/02_document/module-layout.md` (pr
|
||||
For each task in the batch:
|
||||
- Read the task spec's **Component** field.
|
||||
- Look up the component in `_docs/02_document/module-layout.md` → Per-Component Mapping.
|
||||
- Set **OWNED** = the component's `Owns` glob (the files this task is allowed to write).
|
||||
- Set **OWNED** = the component's `Owns` glob (exclusive write for the duration of the batch).
|
||||
- Set **READ-ONLY** = Public API files of every component in the component's `Imports from` list, plus all `shared/*` Public API files.
|
||||
- Set **FORBIDDEN** = every other component's `Owns` glob, and every other component's internal (non-Public API) files.
|
||||
- If the task is a shared / cross-cutting task (lives under `shared/*`), OWNED = that shared directory; READ-ONLY = nothing; FORBIDDEN = every component directory.
|
||||
|
||||
Since execution is sequential, there is no parallel-write conflict to resolve; ownership here is a **scope discipline** check — it stops a task from drifting into unrelated components even when alone.
|
||||
- If two tasks in the same batch map to the same component or overlapping `Owns` globs, schedule them sequentially instead of in parallel.
|
||||
|
||||
If `_docs/02_document/module-layout.md` is missing or the component is not found:
|
||||
- STOP the batch.
|
||||
@@ -122,30 +102,31 @@ If `_docs/02_document/module-layout.md` is missing or the component is not found
|
||||
|
||||
### 5. Update Tracker Status → In Progress
|
||||
|
||||
For each task in the batch, transition its ticket status to **In Progress** via the configured work item tracker (see `protocols.md` for tracker detection) before starting work. If `tracker: local`, skip this step. If a tracker operation fails unexpectedly, follow `.cursor/rules/tracker.mdc`.
|
||||
For each task in the batch, transition its ticket status to **In Progress** via the configured work item tracker (see `protocols.md` for tracker detection) before launching the implementer. If `tracker: local`, skip this step.
|
||||
|
||||
### 6. Implement Tasks Sequentially
|
||||
### 6. Launch Implementer Subagents
|
||||
|
||||
**Per-batch dirty-tree re-check**: before starting the batch, run `git status --porcelain`. On the first batch this is guaranteed clean by the prerequisite check. On subsequent batches, the previous batch ended with a commit so the tree should still be clean. If the tree is dirty at this point, STOP and surface the dirty files to the user using the same A/B/C choice as the prerequisite check. The most likely causes are a failed commit in the previous batch, a user who edited files mid-loop, or a pre-commit hook that re-wrote files and was not captured.
|
||||
**Per-batch dirty-tree re-check**: before launching subagents, run `git status --porcelain`. On the first batch this is guaranteed clean by the prerequisite check. On subsequent batches, the previous batch ended with a commit so the tree should still be clean. If the tree is dirty at this point, STOP and surface the dirty files to the user using the same A/B/C choice as the prerequisite check. The most likely causes are a failed commit in the previous batch, a user who edited files mid-loop, or a pre-commit hook that re-wrote files and was not captured.
|
||||
|
||||
For each task in the batch **in topological order, one at a time**:
|
||||
1. Read the task spec file.
|
||||
2. Respect the file-ownership envelope computed in Step 4 (OWNED / READ-ONLY / FORBIDDEN).
|
||||
3. Implement the feature and write/update tests for every acceptance criterion in the spec. Tests for internal product behavior must exercise the production implementation path. If a test cannot run in the current environment (e.g., TensorRT requires GPU), the test must still exist and skip/block with a clear prerequisite reason, but that skip does not make missing production code complete.
|
||||
4. Run the relevant tests locally before moving on to the next task in the batch. If tests fail, fix in-place — do not defer.
|
||||
5. Capture a short per-task status line (files changed, tests pass/fail, any blockers) for the batch report.
|
||||
For each task in the batch, launch an `implementer` subagent with:
|
||||
- Path to the task spec file
|
||||
- List of files OWNED (exclusive write access)
|
||||
- List of files READ-ONLY
|
||||
- List of files FORBIDDEN
|
||||
- **Explicit instruction**: the implementer must write or update tests that validate each acceptance criterion in the task spec. If a test cannot run in the current environment (e.g., TensorRT requires GPU), the test must still be written and skip with a clear reason.
|
||||
|
||||
Do NOT spawn subagents and do NOT attempt to implement two tasks simultaneously, even if they touch disjoint files. See `.cursor/rules/no-subagents.mdc`.
|
||||
Launch all subagents immediately — no user confirmation.
|
||||
|
||||
### 7. Collect Status
|
||||
### 7. Monitor
|
||||
|
||||
- After all tasks in the batch are finished, aggregate the per-task status lines into a structured batch status.
|
||||
- If any task reported "Blocked", log the blocker with the failing task's ID and continue — the batch report will surface it.
|
||||
- Wait for all subagents to complete
|
||||
- Collect structured status reports from each implementer
|
||||
- If any implementer reports "Blocked", log the blocker and continue with others
|
||||
|
||||
**Stuck detection** — while implementing a task, watch for these signals in your own progress:
|
||||
- The same file has been rewritten 3+ times without tests going green → stop, mark the task Blocked, and move to the next task in the batch (the user will be asked at the end of the batch).
|
||||
- You have tried 3+ distinct approaches without evidence-driven progress → stop, mark Blocked, move on.
|
||||
- Do NOT loop indefinitely on a single task. Record the blocker and proceed.
|
||||
**Stuck detection** — while monitoring, watch for these signals per subagent:
|
||||
- Same file modified 3+ times without test pass rate improving → flag as stuck, stop the subagent, report as Blocked
|
||||
- Subagent has not produced new output for an extended period → flag as potentially hung
|
||||
- If a subagent is flagged as stuck, do NOT let it continue looping — stop it and record the blocker in the batch report
|
||||
|
||||
### 8. AC Test Coverage Verification
|
||||
|
||||
@@ -158,8 +139,8 @@ Before code review, verify that every acceptance criterion in each task spec has
|
||||
- **Not covered**: no test exists for this AC
|
||||
|
||||
If any AC is **Not covered**:
|
||||
- This is a **BLOCKING** failure — the missing test must be written before proceeding
|
||||
- Go back to the offending task, add tests for the specific ACs that lack coverage, then re-run this check
|
||||
- This is a **BLOCKING** failure — the implementer must write the missing test before proceeding
|
||||
- Re-launch the implementer with the specific ACs that need tests
|
||||
- If the test cannot run in the current environment (GPU required, platform-specific, external service), the test must still exist and skip with `pytest.mark.skipif` or `pytest.skip()` explaining the prerequisite
|
||||
- A skipped test counts as **Covered** — the test exists and will run when the environment allows
|
||||
|
||||
@@ -208,14 +189,12 @@ Track `auto_fix_attempts` and `escalated_findings` in the batch report for retro
|
||||
|
||||
### 12. Update Tracker Status → In Testing
|
||||
|
||||
After the batch is committed (and pushed if the user approved pushing), transition the ticket status of each task in the batch to **In Testing** via the configured work item tracker. If `tracker: local`, skip this step. If a tracker operation fails unexpectedly, follow `.cursor/rules/tracker.mdc`.
|
||||
After the batch is committed and pushed, transition the ticket status of each task in the batch to **In Testing** via the configured work item tracker. If `tracker: local`, skip this step.
|
||||
|
||||
### 13. Archive Completed Tasks
|
||||
|
||||
Move each completed task file from `TASKS_DIR/todo/` to `TASKS_DIR/done/`.
|
||||
|
||||
For product implementation, this archive means "batch implementation accepted." The Product Implementation Completeness Gate can still require follow-up remediation tasks before the feature is complete; it does not move original task files back to `todo/`.
|
||||
|
||||
### 14. Loop
|
||||
|
||||
- Go back to step 2 until all tasks in `todo/` are done
|
||||
@@ -237,74 +216,16 @@ For product implementation, this archive means "batch implementation accepted."
|
||||
- **Interaction with Auto-Fix Gate**: Architecture findings (new category from code-review Phase 7) always escalate per the implement auto-fix matrix; they cannot silently auto-fix
|
||||
- **Resumability**: if interrupted, the next invocation checks for the latest `cumulative_review_batches_*.md` and computes the changed-file set from batch reports produced after that review
|
||||
|
||||
### 15. Product Implementation Completeness Gate
|
||||
### 15. Final Test Run
|
||||
|
||||
Run this gate after all **product implementation** tasks are complete and before writing any final product implementation report or allowing autodev to proceed to testability/test decomposition. Skip this gate only when the remaining context is explicitly test implementation or refactoring, as determined by the task files and report filename rules.
|
||||
|
||||
**Goal**: catch the failure mode where narrow tests validate scaffold behavior while the task's actual outcome, included scope, architecture promise, or named integration remains unimplemented.
|
||||
|
||||
Inputs:
|
||||
|
||||
- Completed product task specs from `_docs/02_tasks/done/` for the current cycle
|
||||
- `_docs/02_document/architecture.md`
|
||||
- `_docs/02_document/system-flows.md`
|
||||
- Relevant `_docs/02_document/components/*/description.md` files
|
||||
- Current source code under each completed task's ownership envelope
|
||||
- Batch reports and code-review reports for the current cycle
|
||||
|
||||
For each completed product task:
|
||||
|
||||
1. Read these sections from the task spec: `Description`, `Outcome`, `Scope / Included`, `Acceptance Criteria`, `Non-Functional Requirements`, `Constraints`, and explicit named technologies or integrations.
|
||||
2. Compare those promises against actual source code, not only tests or report prose.
|
||||
3. Search the task's owned component files for unresolved implementation markers: `placeholder`, `stub`, `reserved`, `TODO`, `NotImplemented`, `pass`, `deterministic`, `fake`, `mock`, `scaffold`, `native bridge`, and empty native/readme-only integration directories. Ignore test fixtures/mocks only when they are under test-owned paths and not used as production behavior.
|
||||
4. Verify that each named runtime dependency in the task promise is integrated as production behavior, not merely represented by an interface. Examples: if a task promises FAISS, DINOv2, BASALT, LightGlue, OpenCV, RANSAC, a database, cloud service, or hardware SDK, the production code must either call that dependency or contain an adapter that loads and executes the real dependency package. A deterministic fallback, fake runner, empty `native/` package, or "bridge to be supplied later" is **FAIL** unless the task itself explicitly scoped the dependency out before implementation started.
|
||||
5. Distinguish internal implementation from external prerequisites:
|
||||
- Internal product capabilities (VIO, anchor verification, cache retrieval, safety wrapper, FDR, MAVLink emission) must be implemented in production code before the task can pass.
|
||||
- External systems/hardware/data (Jetson device, physical camera, ArduPilot process, QGC, third-party service credentials, unavailable licensed dataset) may be `BLOCKED` only when production code exists and the missing prerequisite is outside the product boundary.
|
||||
6. Verify tests exercise the real implementation path where local prerequisites exist. Environment-gated tests may skip only with an explicit prerequisite reason; they do not make missing production code complete.
|
||||
7. For any architecture promise that describes an end-to-end user outcome, verify there is an executable production pipeline connecting the relevant components. Isolated component contracts and test-only harness orchestration are not enough.
|
||||
8. Classify each task:
|
||||
- **PASS**: task promises are implemented or explicitly out of scope in the task itself.
|
||||
- **BLOCKED**: production code exists but cannot be fully verified due to external hardware/data/license/runtime prerequisites; the blocker is explicit and tests report blocked/skipped with reason.
|
||||
- **FAIL**: promised production behavior is missing, only scaffolded, or only represented in tests/reports.
|
||||
|
||||
Save the audit to `_docs/03_implementation/implementation_completeness_cycle[N]_report.md` with:
|
||||
|
||||
- Per-task classification
|
||||
- Evidence files/symbols checked
|
||||
- Any unresolved scaffold/native placeholders
|
||||
- Any named promised technologies not integrated
|
||||
- Required remediation task suggestions, each sized to 5 points or less
|
||||
|
||||
Gate:
|
||||
|
||||
- If every product task is `PASS` or `BLOCKED` with explicit prerequisite evidence, continue to Final Test Run.
|
||||
- If any product task is `FAIL`, STOP. Do not write the final product implementation report and do not proceed to any downstream autodev step. Completed original task files remain in `done/`; the missing work is represented by remediation tasks. Present a Choose block:
|
||||
- A) Create remediation tasks now and return to implementation
|
||||
- B) Mark the missing behavior explicitly out of scope in task/docs, then re-run this gate
|
||||
- C) Abort for manual correction
|
||||
- Recommendation must normally be A unless the user deliberately accepts reduced scope.
|
||||
|
||||
Remediation task creation:
|
||||
|
||||
1. For each `FAIL`, create one or more task specs using `.cursor/skills/decompose/templates/task.md`; each remediation task must be sized at 5 points or less.
|
||||
2. Save each task to `_docs/02_tasks/todo/` with a short name prefixed by `remediate_`.
|
||||
3. Set **Component** to the failed task's component and set **Dependencies** to the failed task ID plus any remediation prerequisites.
|
||||
4. Create or defer tracker tickets using the same tracker rules as decompose/new-task: if tracker is available, create tickets immediately; if the user explicitly chose `tracker: local`, keep numeric prefixes with `Tracker: pending` / `Epic: pending`.
|
||||
5. Append the remediation tasks to `_docs/02_tasks/_dependencies_table.md`.
|
||||
6. Return to Step 1 (Parse) in **Product implementation** context. The final product implementation report can be written only after remediation tasks complete and this gate reruns without `FAIL`.
|
||||
|
||||
### 16. Final Test Run
|
||||
|
||||
- After all batches are complete, run the full test suite once unless the invoking flow's immediate next step is `Run Tests`.
|
||||
- If the next flow step is `Run Tests`, record a handoff in the final implementation report and let `.cursor/skills/test-run/SKILL.md` own the full-suite gate to avoid duplicate full runs.
|
||||
- When this step does run, read and execute `.cursor/skills/test-run/SKILL.md` (detect runner, run suite, diagnose failures, present blocking choices).
|
||||
- Test failures are a **blocking gate** — do not proceed until the test-run skill completes with a user decision.
|
||||
- When tests pass, report final summary.
|
||||
- After all batches are complete, run the full test suite once
|
||||
- Read and execute `.cursor/skills/test-run/SKILL.md` (detect runner, run suite, diagnose failures, present blocking choices)
|
||||
- Test failures are a **blocking gate** — do not proceed until the test-run skill completes with a user decision
|
||||
- When tests pass, report final summary
|
||||
|
||||
## Batch Report Persistence
|
||||
|
||||
After each batch completes, save the batch report to `_docs/03_implementation/batch_[NN]_cycle[N]_report.md` for feature implementation (or `batch_[NN]_report.md` for test/refactor runs). Create the directory if it doesn't exist. For product implementation, produce the FINAL implementation report only after the Product Implementation Completeness Gate passes. For test and refactor implementation, produce the FINAL report after all selected tasks complete and the full-suite gate is either run or handed off per Step 16. The filename depends on context:
|
||||
After each batch completes, save the batch report to `_docs/03_implementation/batch_[NN]_cycle[N]_report.md` for feature implementation (or `batch_[NN]_report.md` for test/refactor runs). Create the directory if it doesn't exist. When all tasks are complete, produce a FINAL implementation report with a summary of all batches. The filename depends on context:
|
||||
|
||||
- **Test implementation** (tasks from test decomposition): `_docs/03_implementation/implementation_report_tests.md`
|
||||
- **Feature implementation**: `_docs/03_implementation/implementation_report_{feature_slug}_cycle{N}.md` where `{feature_slug}` is derived from the batch task names (e.g., `implementation_report_core_api_cycle2.md`) and `{N}` is the current `state.cycle` from `_docs/_autodev_state.md`. If `state.cycle` is absent (pre-migration), default to `cycle1`.
|
||||
@@ -343,10 +264,9 @@ After each batch, produce a structured report:
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Same task rewritten 3+ times without green tests | Mark Blocked, continue batch, escalate at batch end |
|
||||
| Implementer fails same approach 3+ times | Stop it, escalate to user |
|
||||
| Task blocked on external dependency (not in task list) | Report and skip |
|
||||
| File ownership violated (task wrote outside OWNED) | ASK user |
|
||||
| Product completeness gate finds missing promised implementation | STOP — create remediation tasks or get explicit user scope reduction |
|
||||
| File ownership conflict unresolvable | ASK user |
|
||||
| Test failure after final test run | Delegate to test-run skill — blocking gate |
|
||||
| All tasks complete | Report final summary, suggest final commit |
|
||||
| `_dependencies_table.md` missing | STOP — run `/decompose` first |
|
||||
@@ -361,8 +281,7 @@ Each batch commit serves as a rollback checkpoint. If recovery is needed:
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Never start a task whose dependencies are not yet completed
|
||||
- Never run tasks in parallel and never spawn subagents — see `.cursor/rules/no-subagents.mdc`
|
||||
- If a task is flagged as stuck, stop working on it and report — do not let it loop indefinitely
|
||||
- Always run the Product Implementation Completeness Gate before final product reports
|
||||
- Always run or hand off the full test suite after all batches complete (step 16)
|
||||
- Never launch tasks whose dependencies are not yet completed
|
||||
- Never allow two parallel agents to write to the same file
|
||||
- If a subagent fails or is flagged as stuck, stop it and report — do not let it loop indefinitely
|
||||
- Always run the full test suite after all batches complete (step 15)
|
||||
|
||||
@@ -3,31 +3,29 @@
|
||||
## Topological Sort with Batch Grouping
|
||||
|
||||
The `/implement` skill uses a topological sort to determine execution order,
|
||||
then groups tasks into batches for code review and commit. Execution within a
|
||||
batch is **sequential** — see `.cursor/rules/no-subagents.mdc`.
|
||||
then groups tasks into batches for parallel execution.
|
||||
|
||||
## Algorithm
|
||||
|
||||
1. Build adjacency list from `_dependencies_table.md`
|
||||
2. Compute in-degree for each task node
|
||||
3. Initialize the ready set with all nodes that have in-degree 0
|
||||
3. Initialize batch 0 with all nodes that have in-degree 0
|
||||
4. For each batch:
|
||||
a. Select up to 4 tasks from the ready set (default batch size cap)
|
||||
b. Implement the selected tasks one at a time in topological order
|
||||
c. When all tasks in the batch complete, remove them from the graph and
|
||||
decrement in-degrees of dependents
|
||||
d. Add newly zero-in-degree nodes to the ready set
|
||||
a. Select up to 4 tasks from the ready set
|
||||
b. Check file ownership — if two tasks would write the same file, defer one to the next batch
|
||||
c. Launch selected tasks as parallel implementer subagents
|
||||
d. When all complete, remove them from the graph and decrement in-degrees of dependents
|
||||
e. Add newly zero-in-degree nodes to the next batch's ready set
|
||||
5. Repeat until the graph is empty
|
||||
|
||||
## Ordering Inside a Batch
|
||||
## File Ownership Conflict Resolution
|
||||
|
||||
Tasks inside a batch are executed in topological order — a task is only
|
||||
started after every task it depends on (inside the batch or in a previous
|
||||
batch) is done. When two tasks have the same topological rank, prefer the
|
||||
lower-numbered (more foundational) task first.
|
||||
When two tasks in the same batch map to overlapping files:
|
||||
- Prefer to run the lower-numbered task first (it's more foundational)
|
||||
- Defer the higher-numbered task to the next batch
|
||||
- If both have equal priority, ask the user
|
||||
|
||||
## Complexity Budget
|
||||
|
||||
Each batch should not exceed 20 total complexity points.
|
||||
If it does, split the batch and let the user choose which tasks to include.
|
||||
The budget exists to keep the per-batch code review scope reviewable.
|
||||
|
||||
@@ -129,8 +129,7 @@ If `_docs/_repo-config.yaml` already exists:
|
||||
- Entries removed (component removed from registry)
|
||||
4. **Ask the user** whether to apply the diff.
|
||||
5. If applied, **preserve `confirmed: true` flags** for entries that still match — don't reset human-approved mappings.
|
||||
6. **Preserve user-owned top-level keys verbatim**: `glossary_doc:` (written by autodev meta-repo Step 2.5) and any `assumptions_log:` entries are NEVER edited or removed by this skill. Carry them through unchanged. If the file referenced by `glossary_doc:` no longer exists on disk, surface as an `unresolved:` question — do not auto-clear the field.
|
||||
7. If user declines, stop — leave config untouched.
|
||||
6. If user declines, stop — leave config untouched.
|
||||
|
||||
### Phase 8: Batch question checkpoint (M4)
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ Propagates component changes into the unified documentation set. Strictly scoped
|
||||
| Root `README.md` **only** if `_repo-config.yaml` lists it as a doc target (e.g., services table) | Install scripts (`ci-*.sh`) → use `monorepo-cicd` |
|
||||
| Docs index (`_docs/README.md` or similar) cross-reference tables | Component-internal docs (`<component>/README.md`, `<component>/docs/*`) |
|
||||
| Cross-cutting docs listed in `docs.cross_cutting` | `_docs/_repo-config.yaml` itself (only `monorepo-discover` and `monorepo-onboard` write it) |
|
||||
| Body of cross-cutting docs **except** the `## Architecture Vision` section (preserved verbatim — owned by autodev meta-repo Step 2.5) | The file at `glossary_doc:` (user-confirmed; only autodev meta-repo Step 2.5 rewrites it). New project terms surfaced during sync are reported back to the user, not silently appended |
|
||||
| `## Architecture Vision` body — read-only, may be referenced for terminology consistency but never edited | — |
|
||||
|
||||
If a component change requires CI/env updates too, tell the user to also run `monorepo-cicd`. This skill does NOT cross domains.
|
||||
|
||||
@@ -168,8 +166,6 @@ Append to `_docs/_repo-config.yaml` under `assumptions_log:`:
|
||||
- Change `confirmed_by_user` or any `confirmed: <bool>` flag
|
||||
- Auto-commit or push
|
||||
- Guess a mapping not in the config
|
||||
- Edit `glossary_doc:` (the file recorded under the config's `glossary_doc:` key)
|
||||
- Edit the `## Architecture Vision` section of any cross-cutting doc; if a sync would conflict with that section, surface the conflict to the user and skip — do not silently rewrite user-confirmed content
|
||||
|
||||
## Edge cases
|
||||
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
---
|
||||
name: monorepo-e2e
|
||||
description: Syncs the suite-level integration e2e harness (`e2e/docker-compose.suite-e2e.yml`, fixtures, Playwright runner) when component contracts drift in ways that affect the cross-service scenario. Reads `_docs/_repo-config.yaml` to know which suite-e2e artifacts are in play. Touches ONLY suite-e2e files — never per-component CI, docs, or component internals. Use when a component changes a port, env var, public API endpoint, DB schema column, or detection model that the suite e2e exercises.
|
||||
---
|
||||
|
||||
# Monorepo Suite-E2E
|
||||
|
||||
Propagates component changes into the suite-level integration e2e harness. Strictly scoped — never edits docs, component internals, per-component CI configs, or the production deploy compose.
|
||||
|
||||
## Scope — explicit
|
||||
|
||||
| In scope | Out of scope |
|
||||
| -------- | ------------ |
|
||||
| `e2e/docker-compose.suite-e2e.yml` (overlay, healthchecks, seed services) | Production `_infra/deploy/<target>/docker-compose.yml` — `monorepo-cicd` owns it |
|
||||
| `e2e/fixtures/init.sql` (seeded rows that the spec depends on) | Component DB migrations — owned by each component |
|
||||
| `e2e/fixtures/expected_detections.json` (detection baseline) | Detection model itself — owned by `detections/` |
|
||||
| `e2e/runner/tests/*.spec.ts` selector / contract-driven edits | New scenarios (user-driven, not drift-driven) |
|
||||
| `e2e/runner/Dockerfile` / `package.json` Playwright version bumps | Net-new e2e infrastructure (use `monorepo-onboard` or initial scaffolding) |
|
||||
| `.woodpecker/suite-e2e.yml` (suite-level pipeline) | Per-component `.woodpecker/01-test.yml` / `02-build-push.yml` — `monorepo-cicd` owns those |
|
||||
| Suite-e2e leftover entries under `_docs/_process_leftovers/` | Per-component leftovers — owned by each component |
|
||||
|
||||
If a component change needs doc updates too, tell the user to also run `monorepo-document`. If it needs production-deploy or per-component CI updates, run `monorepo-cicd`. This skill **only** updates the suite-e2e surface.
|
||||
|
||||
## Preconditions (hard gates)
|
||||
|
||||
1. `_docs/_repo-config.yaml` exists.
|
||||
2. Top-level `confirmed_by_user: true`.
|
||||
3. `suite_e2e.*` section is populated in config (see "Required config block" below). If absent, abort and ask the user to extend the config via `monorepo-discover`.
|
||||
4. Components-in-scope have confirmed contract mappings (port, public API path, DB tables touched), OR user explicitly approves inferred ones.
|
||||
|
||||
## Required config block
|
||||
|
||||
This skill expects `_docs/_repo-config.yaml` to carry:
|
||||
|
||||
```yaml
|
||||
suite_e2e:
|
||||
overlay: e2e/docker-compose.suite-e2e.yml
|
||||
fixtures:
|
||||
init_sql: e2e/fixtures/init.sql
|
||||
baseline_json: e2e/fixtures/expected_detections.json
|
||||
binary_fixtures:
|
||||
- e2e/fixtures/sample.mp4
|
||||
- e2e/fixtures/model.tar.gz
|
||||
runner:
|
||||
dockerfile: e2e/runner/Dockerfile
|
||||
package_json: e2e/runner/package.json
|
||||
spec_dir: e2e/runner/tests
|
||||
pipeline: .woodpecker/suite-e2e.yml
|
||||
scenario:
|
||||
description: "Upload video → detect → overlays → dataset → DB persistence"
|
||||
components_exercised:
|
||||
- ui
|
||||
- annotations
|
||||
- detections
|
||||
- postgres-local
|
||||
api_contracts:
|
||||
- component: ui
|
||||
path: /api/admin/auth/login
|
||||
- component: annotations
|
||||
path: /api/annotations/media/batch
|
||||
- component: annotations
|
||||
path: /api/annotations/media/{id}/annotations
|
||||
db_tables:
|
||||
- media
|
||||
- annotations
|
||||
- detection
|
||||
- detection_classes
|
||||
model_pin:
|
||||
detections_repo_path: <path-to-model-config-or-classes-source>
|
||||
classes_source: annotations/src/Database/DatabaseMigrator.cs
|
||||
```
|
||||
|
||||
If `suite_e2e:` is missing the skill **stops** — it does not invent a default mapping.
|
||||
|
||||
## Mitigations (M1–M7)
|
||||
|
||||
- **M1** Separation: this skill only touches suite-e2e files; no production deploy compose, no per-component CI, no docs, no component internals.
|
||||
- **M3** Factual vs. interpretive: port, env var, API path, DB column — FACTUAL, read from the components' code. Whether a baseline still matches the model — DEFERRED to the user (the skill flags drift, never silently re-records).
|
||||
- **M4** Batch questions at checkpoints.
|
||||
- **M5** Skip over guess: a component change that doesn't map cleanly to one of the in-scope artifacts → skip and report.
|
||||
- **M6** Assumptions footer + append to `_repo-config.yaml` `assumptions_log`.
|
||||
- **M7** Drift detection: verify every path under `suite_e2e.*` exists on disk; stop if not.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Phase 1: Drift check (M7)
|
||||
|
||||
Verify every file listed under `suite_e2e.*` (excluding `binary_fixtures`, which are gitignored) exists on disk. Missing file → stop and ask:
|
||||
- Run `monorepo-discover` to refresh, OR
|
||||
- Skip the missing artifact (recorded in report)
|
||||
|
||||
For `binary_fixtures` paths that are absent (expected — they live in S3/LFS), check whether `expected_detections.json._meta.video_sha256` is still a `TBD-...` placeholder. If yes, surface this as a known leftover (`_docs/_process_leftovers/2026-04-22_suite-e2e-binary-fixtures.md`) and continue.
|
||||
|
||||
### Phase 2: Determine scope
|
||||
|
||||
Same as `monorepo-cicd` Phase 2 — ask the user, or auto-detect. For **auto-detect**, flag commits that touch suite-e2e-relevant concerns:
|
||||
|
||||
| Commit pattern | Suite-e2e impact |
|
||||
| -------------- | ---------------- |
|
||||
| New port exposed by `<component>` | Healthcheck override may change in `e2e/docker-compose.suite-e2e.yml` |
|
||||
| New required env var on `<component>` | `e2e/docker-compose.suite-e2e.yml` `e2e-runner` env block + `init.sql` seed |
|
||||
| Public API path renamed / removed | Spec selector / API call path in `e2e/runner/tests/*.spec.ts` |
|
||||
| DB schema column renamed in a `db_tables` entry | `init.sql` column reference + spec `pg.query` text |
|
||||
| New required DB table referenced by spec | `init.sql` insert block (skip if owned by component migration) |
|
||||
| Detection model rev change in `detections/` | `expected_detections.json` `_meta.model.revision` + flag baseline as stale |
|
||||
| New canonical detection class added | `expected_detections.json._meta` annotation |
|
||||
|
||||
Present the flagged list; confirm.
|
||||
|
||||
### Phase 3: Classify changes per component
|
||||
|
||||
| Change type | Target suite-e2e files |
|
||||
| ----------- | ---------------------- |
|
||||
| Port / env var change | `e2e/docker-compose.suite-e2e.yml` |
|
||||
| API path / contract change | `e2e/runner/tests/*.spec.ts` |
|
||||
| DB schema reference change | `e2e/fixtures/init.sql` and spec SQL queries |
|
||||
| Model / class catalog change | `e2e/fixtures/expected_detections.json` (mark `_meta.fixture_version` bump + leftover entry for binary refresh) |
|
||||
| Playwright dependency drift | `e2e/runner/package.json` + `e2e/runner/Dockerfile` |
|
||||
| Suite scenario steps gone stale | **Stop and ask** — scenario edits are user-driven, not drift-driven |
|
||||
|
||||
### Phase 4: Apply edits
|
||||
|
||||
Edit each in-scope file. After each batch, run `ReadLints` on touched files. Do NOT run the suite e2e itself — that's a downstream pipeline operation, not a sync-skill responsibility.
|
||||
|
||||
For `expected_detections.json`: when the model revision changes, the skill **does not** re-record the baseline — the binary fixture cannot be regenerated from the dev environment. Instead:
|
||||
1. Set `_meta.model.revision` to the new revision.
|
||||
2. Set `_meta.fixture_version` to a new bumped version with a `-stale` suffix (e.g., `0.2.0-stale`).
|
||||
3. Append a new entry to `_docs/_process_leftovers/` describing the required re-record.
|
||||
4. Leave `expected.by_class` untouched — the spec's tolerance check will fail loudly until the binary refresh lands.
|
||||
|
||||
### Phase 5: Update assumptions log
|
||||
|
||||
Append a new `assumptions_log:` entry to `_docs/_repo-config.yaml` recording:
|
||||
- Date, components in scope, which suite-e2e files were touched
|
||||
- Any inferred contract mappings still tagged `confirmed: false`
|
||||
- Any leftover entries created
|
||||
|
||||
### Phase 6: Report
|
||||
|
||||
Render a Choose-format summary of the synced files, surface any `_process_leftovers/` entries created, and end. Do NOT auto-commit.
|
||||
|
||||
## Self-verification
|
||||
|
||||
- [ ] No file outside `e2e/`, `.woodpecker/suite-e2e.yml`, or `_docs/_process_leftovers/` was edited
|
||||
- [ ] `_docs/_repo-config.yaml` `suite_e2e:` block was not silently mutated except for `assumptions_log` append
|
||||
- [ ] `expected_detections.json` was not re-recorded (only metadata bumped + leftover added)
|
||||
- [ ] Every spec edit traces to a flagged commit pattern in Phase 2
|
||||
- [ ] `ReadLints` clean on every touched file
|
||||
|
||||
## Failure handling
|
||||
|
||||
Same retry / escalation protocol as `monorepo-cicd` — see `protocols.md`. The most common failure mode is the binary-fixture leftover (sample.mp4 missing or SHA-mismatched); this skill does not attempt to resolve it, only surfaces it.
|
||||
@@ -59,8 +59,6 @@ Mark each as `complete` / `partial` / `missing` and explain.
|
||||
- Every component in `components:` appears in the registry — flag mismatches
|
||||
- Every `docs.root` file cross-referenced in config exists on disk — flag missing
|
||||
- Every `ci.orchestration_files` and `ci.install_scripts` exists — flag missing
|
||||
- `glossary_doc:` (if recorded in config) points to a file that exists on disk — flag missing
|
||||
- The cross-cutting architecture doc identified by `docs.cross_cutting` contains a `## Architecture Vision` section — flag missing (signals the meta-repo flow's Step 2.5 was skipped or the section was removed)
|
||||
|
||||
### Section 5: Unresolved questions
|
||||
|
||||
@@ -115,8 +113,6 @@ In registry, not in config: [list or "(none)"]
|
||||
In config, not in registry: [list or "(none)"]
|
||||
Config-referenced docs missing: [list or "(none)"]
|
||||
Config-referenced CI files missing: [list or "(none)"]
|
||||
glossary_doc: [path or "not recorded — run /autodev to capture"]
|
||||
Architecture Vision section: [present | missing in <doc>]
|
||||
|
||||
═══════════════════════════════════════════════════
|
||||
Unresolved questions
|
||||
|
||||
@@ -75,7 +75,7 @@ Record the description verbatim for use in subsequent steps.
|
||||
**Role**: Technical analyst
|
||||
**Goal**: Determine whether deep research is needed.
|
||||
|
||||
Read the user's description and the existing codebase documentation from DOCUMENT_DIR (architecture.md including its `## Architecture Vision` section, glossary.md, components/, system-flows.md). Use `glossary.md` to keep the new task's name, acceptance-criteria wording, and component references aligned with the user's confirmed vocabulary; flag the task to the user if the request appears to violate an Architecture Vision principle, do not silently allow it.
|
||||
Read the user's description and the existing codebase documentation from DOCUMENT_DIR (architecture.md, components/, system-flows.md).
|
||||
|
||||
**Consult LESSONS.md**: if `_docs/LESSONS.md` exists, read it and look for entries in categories `estimation`, `architecture`, `dependencies` that might apply to the task under consideration. If a relevant lesson exists (e.g., "estimation: auth-related changes historically take 2x estimate"), bias the classification and recommendation accordingly. Note in the output which lessons (if any) were applied.
|
||||
|
||||
@@ -134,8 +134,7 @@ The `<task_slug>` is a short kebab-case name derived from the feature descriptio
|
||||
**Goal**: Determine where and how to insert the new functionality, and whether existing tests cover the new requirements.
|
||||
|
||||
1. Read the codebase documentation from DOCUMENT_DIR:
|
||||
- `architecture.md` — overall structure (the `## Architecture Vision` H2 is user-confirmed intent and must not be violated by the new task without explicit approval)
|
||||
- `glossary.md` — project terminology; reuse the user's vocabulary in task names, AC, and component references
|
||||
- `architecture.md` — overall structure
|
||||
- `components/` — component specs
|
||||
- `system-flows.md` — data flows (if exists)
|
||||
- `data_model.md` — data model (if exists)
|
||||
@@ -282,7 +281,7 @@ Present using the Choose format for each decision that has meaningful alternativ
|
||||
- Update **Epic** field: `[EPIC-ID]`
|
||||
3. Rename the file from `[##]_[short_name].md` to `[TICKET-ID]_[short_name].md`
|
||||
|
||||
If the work item tracker is not authenticated or unavailable, follow `.cursor/rules/tracker.mdc` before continuing. Only if the user explicitly chooses `tracker: local`:
|
||||
If the work item tracker is not authenticated or unavailable (`tracker: local`):
|
||||
- Keep the numeric prefix
|
||||
- Set **Tracker** to `pending`
|
||||
- Set **Epic** to `pending`
|
||||
@@ -337,7 +336,7 @@ After the user chooses **Done**:
|
||||
| Research skill hits a blocker | Follow research skill's own escalation rules |
|
||||
| Codebase analysis reveals conflicting architectures | **ASK** user which pattern to follow |
|
||||
| Complexity exceeds 5 points | **WARN** user and suggest splitting into multiple tasks |
|
||||
| Work item tracker MCP unavailable | Follow `.cursor/rules/tracker.mdc`; do not continue in local mode unless the user explicitly chooses it |
|
||||
| Work item tracker MCP unavailable | **WARN**, continue with local-only task files |
|
||||
|
||||
## Trigger Conditions
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ Capture any new questions, findings, or insights that arise during test specific
|
||||
|
||||
### Step 2: Solution Analysis
|
||||
|
||||
Read and follow `steps/02_solution-analysis.md`. The step opens with **Phase 2a.0: Glossary & Architecture Vision** (BLOCKING) — drafts `_docs/02_document/glossary.md` and a one-paragraph architecture vision, presents the condensed view to the user, iterates until confirmed, then proceeds into the architecture, data-model, and deployment phases. The confirmed vision becomes the first `## Architecture Vision` H2 of `architecture.md`.
|
||||
Read and follow `steps/02_solution-analysis.md`.
|
||||
|
||||
---
|
||||
|
||||
@@ -107,7 +107,6 @@ Read and follow `steps/07_quality-checklist.md`.
|
||||
- **Coding during planning**: this workflow produces documents, never code
|
||||
- **Multi-responsibility components**: if a component does two things, split it
|
||||
- **Skipping BLOCKING gates**: never proceed past a BLOCKING marker without user confirmation
|
||||
- **Skipping the glossary/vision gate (Phase 2a.0)**: drafting `architecture.md` from raw `solution.md` without confirming terminology and vision means the AI's mental model is not aligned with the user's; every downstream artifact will inherit that drift
|
||||
- **Diagrams without data**: generate diagrams only after the underlying structure is documented
|
||||
- **Copy-pasting problem.md**: the architecture doc should analyze and transform, not repeat the input
|
||||
- **Vague interfaces**: "component A talks to component B" is not enough; define the method, input, output
|
||||
@@ -138,10 +137,8 @@ Read and follow `steps/07_quality-checklist.md`.
|
||||
│ │
|
||||
│ 1. Blackbox Tests → test-spec/SKILL.md │
|
||||
│ [BLOCKING: user confirms test coverage] │
|
||||
│ 2. Solution Analysis → glossary + vision, architecture, │
|
||||
│ data model, deployment │
|
||||
│ [BLOCKING 2a.0: user confirms glossary + vision] │
|
||||
│ [BLOCKING 2a: user confirms architecture] │
|
||||
│ 2. Solution Analysis → architecture, data model, deployment │
|
||||
│ [BLOCKING: user confirms architecture] │
|
||||
│ 3. Component Decomp → component specs + interfaces │
|
||||
│ [BLOCKING: user confirms components] │
|
||||
│ 4. Review & Risk → risk register, iterations │
|
||||
|
||||
@@ -4,105 +4,20 @@
|
||||
**Goal**: Produce `architecture.md`, `system-flows.md`, `data_model.md`, and `deployment/` from the solution draft
|
||||
**Constraints**: No code, no component-level detail yet; focus on system-level view
|
||||
|
||||
### Phase 2a.0: Glossary & Architecture Vision (BLOCKING)
|
||||
|
||||
**Role**: Software architect + business analyst
|
||||
**Goal**: Align the AI's mental model of the project with the user's intent BEFORE drafting `architecture.md`. Capture domain terminology and the user's high-level architecture vision so every downstream artifact (architecture, components, flows, tests, epics) is grounded in confirmed user intent — not in AI inference.
|
||||
|
||||
**Inputs**:
|
||||
- `_docs/00_problem/problem.md`, `acceptance_criteria.md`, `restrictions.md`
|
||||
- `_docs/00_problem/input_data/*`
|
||||
- `_docs/01_solution/solution.md` (and any earlier `solution_draft*.md` siblings)
|
||||
- Any blackbox-test findings produced in Step 1
|
||||
|
||||
**Outputs**:
|
||||
- `_docs/02_document/glossary.md` (NEW)
|
||||
- A confirmed "Architecture Vision" paragraph + bullet list held in working memory and used as the spine of Phase 2a's `architecture.md`
|
||||
|
||||
**Procedure**:
|
||||
|
||||
1. **Draft glossary** — extract project-specific terminology from inputs (NOT generic software terms). Include:
|
||||
- Domain entities, processes, and roles
|
||||
- Acronyms / abbreviations
|
||||
- Internal codenames or product names
|
||||
- Synonym pairs in active use (e.g., "flight" vs. "mission")
|
||||
- Stakeholder personas referenced in problem.md
|
||||
Each entry: one-line definition, plus a parenthetical source (`source: problem.md`, `source: solution.md §3`).
|
||||
Skip terms that have a single well-known industry meaning (REST, JSON, etc.).
|
||||
|
||||
2. **Draft architecture vision** — synthesize from inputs:
|
||||
- **One paragraph**: what the system is, who uses it, the shape of the runtime topology (monolith / services / pipeline / library / hybrid).
|
||||
- **Components & responsibilities** (one-line each). At this stage these are *intent-level*, not the formal decomposition that Step 3 produces.
|
||||
- **Major data flows** (one or two sentences each).
|
||||
- **Architectural principles / non-negotiables** the user has implied (e.g., "DB-driven config", "no per-component state outside Redis", "all UI traffic via REST + SSE only").
|
||||
- **Open architectural questions** the AI cannot resolve from inputs alone.
|
||||
|
||||
3. **Present condensed view** to the user (NOT the full draft files — a synopsis only):
|
||||
|
||||
```
|
||||
══════════════════════════════════════
|
||||
REVIEW: Glossary + Architecture Vision
|
||||
══════════════════════════════════════
|
||||
Glossary (N terms drafted):
|
||||
- <Term>: <one-line definition>
|
||||
- ...
|
||||
Architecture Vision:
|
||||
<one-paragraph synopsis>
|
||||
|
||||
Components / responsibilities:
|
||||
- <component>: <one-line>
|
||||
- ...
|
||||
|
||||
Principles / non-negotiables:
|
||||
- <principle>
|
||||
- ...
|
||||
|
||||
Open questions (AI could not resolve):
|
||||
- <q1>
|
||||
- <q2>
|
||||
══════════════════════════════════════
|
||||
A) Looks correct — write glossary.md, use vision for Phase 2a
|
||||
B) I want to add / correct entries (provide diffs)
|
||||
C) Answer the open questions first, then re-present
|
||||
══════════════════════════════════════
|
||||
Recommendation: pick C if open questions exist, otherwise A
|
||||
══════════════════════════════════════
|
||||
```
|
||||
|
||||
4. **Iterate**:
|
||||
- On B → integrate the user's diffs/additions, re-present the condensed view, loop until A.
|
||||
- On C → ask the listed open questions one round (M4-style batch), integrate answers, re-present.
|
||||
- **Do NOT proceed to step 5 until the user picks A.**
|
||||
|
||||
5. **Save**:
|
||||
- Write `_docs/02_document/glossary.md` with terms in alphabetical order. Include a top-line `**Status**: confirmed-by-user` and the date.
|
||||
- Hold the confirmed vision (paragraph + components + principles) in working memory; Phase 2a will materialize it into `architecture.md` and **must** preserve every confirmed principle and component intent verbatim.
|
||||
|
||||
**Self-verification**:
|
||||
- [ ] Every glossary entry traces to at least one input file (no invented terms)
|
||||
- [ ] Every component listed in the vision is one the inputs reference
|
||||
- [ ] All open questions are either answered or explicitly deferred (with the user's acknowledgement)
|
||||
- [ ] User picked option A on the latest condensed view
|
||||
|
||||
**BLOCKING**: Do NOT proceed to Phase 2a until `glossary.md` is saved and the user has confirmed the architecture vision.
|
||||
|
||||
### Phase 2a: Architecture & Flows
|
||||
|
||||
1. Read all input files thoroughly
|
||||
2. Incorporate findings, questions, and insights discovered during Step 1 (blackbox tests)
|
||||
3. **Apply confirmed vision from Phase 2a.0**: the architecture document must include a top-level `## Architecture Vision` section that contains the user-confirmed paragraph, components, and principles verbatim. The rest of `architecture.md` (tech stack, deployment model, NFRs, ADRs) builds on top of that section, never contradicts it
|
||||
4. Research unknown or questionable topics via internet; ask user about ambiguities
|
||||
5. Document architecture using `templates/architecture.md` as structure
|
||||
6. Document system flows using `templates/system-flows.md` as structure
|
||||
3. Research unknown or questionable topics via internet; ask user about ambiguities
|
||||
4. Document architecture using `templates/architecture.md` as structure
|
||||
5. Document system flows using `templates/system-flows.md` as structure
|
||||
|
||||
**Self-verification**:
|
||||
- [ ] `architecture.md` opens with a `## Architecture Vision` section matching Phase 2a.0
|
||||
- [ ] Architecture covers all capabilities mentioned in solution.md
|
||||
- [ ] System flows cover all main user/system interactions
|
||||
- [ ] No contradictions with problem.md, restrictions.md, or the confirmed vision
|
||||
- [ ] No contradictions with problem.md or restrictions.md
|
||||
- [ ] Technology choices are justified
|
||||
- [ ] Blackbox test findings are reflected in architecture decisions
|
||||
- [ ] Every term used in `architecture.md` that is project-specific appears in `glossary.md`
|
||||
|
||||
**Save action**: Write `architecture.md` and `system-flows.md`
|
||||
|
||||
|
||||
@@ -58,4 +58,4 @@ Do NOT create minimal epics with just a summary and short description. The epic
|
||||
|
||||
8. **Create "Blackbox Tests" epic** — this epic will parent the blackbox test tasks created by the `/decompose` skill. It covers implementing the test scenarios defined in `tests/`.
|
||||
|
||||
**Save action**: Epics created via the configured tracker MCP. Also saved locally in `epics.md` with ticket IDs. If tracker availability fails, follow `.cursor/rules/tracker.mdc`; only if the user explicitly chooses `tracker: local`, save locally only with pending tracker markers.
|
||||
**Save action**: Epics created via the configured tracker MCP. Also saved locally in `epics.md` with ticket IDs. If `tracker: local`, save locally only.
|
||||
|
||||
@@ -133,4 +133,4 @@ Link to architecture.md and relevant component spec.]
|
||||
- `component` — a normal per-component epic
|
||||
- `cross-cutting` — a shared concern that spans ≥2 components
|
||||
- `tests` — the blackbox-tests epic (always exactly one)
|
||||
- Complexity points for child issues follow the project standard: 1, 2, 3, 5. Do not create issues above 5 points — split them.
|
||||
- Complexity points for child issues follow the project standard: 1, 2, 3, 5, 8. Do not create issues above 5 points — split them.
|
||||
|
||||
@@ -181,8 +181,6 @@ Categorized measurable criteria with markdown headers and bullet points:
|
||||
|
||||
Every criterion must have a measurable value. Vague criteria like "should be fast" are not acceptable — push for "less than 400ms end-to-end".
|
||||
|
||||
**AC must be design-independent**: describe testable outcomes only — no libraries, algorithms, params, or design choices. Implementation follows AC, never reverse. (IEEE 830 / Atlassian / GitScrum)
|
||||
|
||||
### input_data/
|
||||
|
||||
At least one file. Options:
|
||||
|
||||
@@ -24,8 +24,6 @@ Phase details live in `phases/` — read the relevant file before executing each
|
||||
- **Save immediately**: write artifacts to disk after each phase
|
||||
- **Delegate execution**: all code changes go through the implement skill via task files
|
||||
- **Ask, don't assume**: when scope or priorities are unclear, STOP and ask the user
|
||||
- **Exact-fit recommendations**: do not recommend a replacement pattern, library, service, architecture, algorithm, or "modern approach" merely because it improves structure or solves a similar class of problem. It must fit confirmed product constraints, acceptance criteria, operating context, integration boundaries, and current code realities. Otherwise reject it, mark it experimental, or ask the user before adding it to the roadmap.
|
||||
- **Per-mode API capability verification on replacements**: when a refactor proposes replacing or adding a library/SDK/framework/service that exposes multiple modes or configurations, pin the exact mode the refactored code will use (inputs, outputs, runtime) and verify *that mode* via mandatory `context7` lookup plus a saved Minimum Viable Example before promoting the recommendation to `Selected`. Capability claims at the category level ("supports A, B, C modes") must be cross-checked against the literal mode enumeration — `A, B → A+B` style conflations are the recurring silent-failure path.
|
||||
|
||||
## Context Resolution
|
||||
|
||||
@@ -59,7 +57,7 @@ Create REFACTOR_DIR and RUN_DIR if missing. If a RUN_DIR with the same name alre
|
||||
|
||||
Both modes produce `RUN_DIR/list-of-changes.md` (template: `templates/list-of-changes.md`). Both modes then convert that file into task files in TASKS_DIR during Phase 2.
|
||||
|
||||
**Guided mode cleanup**: after `RUN_DIR/list-of-changes.md` is created from the input file, delete the original input file only if it lives outside `RUN_DIR`. If the provided file is already the canonical `RUN_DIR/list-of-changes.md`, keep it as the audit record.
|
||||
**Guided mode cleanup**: after `RUN_DIR/list-of-changes.md` is created from the input file, delete the original input file to avoid duplication.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -81,10 +79,10 @@ Both modes produce `RUN_DIR/list-of-changes.md` (template: `templates/list-of-ch
|
||||
- "refactor [specific target]" → skip phase 1 if docs exist
|
||||
- Default → all phases
|
||||
|
||||
**Testability-run specifics** (guided mode invoked by autodev existing-code Step 4 or greenfield Step 8):
|
||||
**Testability-run specifics** (guided mode invoked by autodev existing-code flow Step 4):
|
||||
- Run name is `01-testability-refactoring`.
|
||||
- Phase 3 (Safety Net) is skipped by design — no tests exist yet. Compensating control: the `list-of-changes.md` gate in Phase 1 must be reviewed and approved by the user before Phase 4 runs.
|
||||
- Scope is MINIMAL and surgical; reject change entries that drift into full refactor territory (see the invoking flow's testability step for allowed/disallowed lists). Flagged entries go to `RUN_DIR/deferred_to_refactor.md` for the next optional full-refactor step or backlog consideration.
|
||||
- Scope is MINIMAL and surgical; reject change entries that drift into full refactor territory (see existing-code flow Step 4 for allowed/disallowed lists). Flagged entries go to `RUN_DIR/deferred_to_refactor.md` for Step 8 (optional full refactor) consideration.
|
||||
- After Phase 4 (Execution) completes, write `RUN_DIR/testability_changes_summary.md` as Phase 4.5. Format: one bullet per applied change.
|
||||
```markdown
|
||||
# Testability Changes Summary ({{run_name}})
|
||||
|
||||
@@ -95,7 +95,7 @@ Also copy to project standard locations:
|
||||
|
||||
**Critical step — do not skip.** Before producing the change list, cross-reference documented business flows against actual implementation. This catches issues that static code inspection alone misses.
|
||||
|
||||
1. **Read documented flows**: Load `DOCUMENT_DIR/system-flows.md`, `DOCUMENT_DIR/architecture.md` (paying special attention to its `## Architecture Vision` section — that's the user-confirmed structural intent), `DOCUMENT_DIR/glossary.md`, `DOCUMENT_DIR/module-layout.md`, every file under `DOCUMENT_DIR/contracts/`, and `SOLUTION_DIR/solution.md` (whichever exist). Extract every documented business flow, data path, architectural decision, module ownership boundary, and contract shape. Any refactor change that contradicts a confirmed Architecture Vision principle must either be rejected or surfaced to the user before being added to `list-of-changes.md` — those principles are not refactor targets without explicit user approval.
|
||||
1. **Read documented flows**: Load `DOCUMENT_DIR/system-flows.md`, `DOCUMENT_DIR/architecture.md`, `DOCUMENT_DIR/module-layout.md`, every file under `DOCUMENT_DIR/contracts/`, and `SOLUTION_DIR/solution.md` (whichever exist). Extract every documented business flow, data path, architectural decision, module ownership boundary, and contract shape.
|
||||
|
||||
2. **Trace each flow through code**: For every documented flow (e.g., "video batch processing", "image tiling", "engine initialization"), walk the actual code path line by line. At each decision point ask:
|
||||
- Does the code match the documented/intended behavior?
|
||||
|
||||
@@ -7,29 +7,14 @@
|
||||
## 2a. Deep Research
|
||||
|
||||
1. Analyze current implementation patterns
|
||||
2. Extract the **Project Constraint Matrix** from `problem.md`, `restrictions.md`, `acceptance_criteria.md`, current architecture/docs, and actual code constraints. Include required inputs/outputs, operating context, lifecycle assumptions, integration boundaries, non-functional targets, and hard disqualifiers.
|
||||
3. Research modern approaches for similar systems
|
||||
4. For each alternative pattern/library/service/architecture/algorithm, research intrinsic implementation constraints: required inputs/outputs, runtime assumptions, supported deployment modes, resource needs, operational limits, licensing/security constraints, and known failure reports.
|
||||
|
||||
**API Capability Verification — Per-Mode (MANDATORY, BLOCKING for proposed replacements)**
|
||||
|
||||
When a refactor recommendation replaces (or adds) a library/SDK/framework/service, the same per-mode verification used by `/research` Step 2 applies — selecting a replacement on category fit alone is the same silent-failure path. For every replacement candidate that has multiple modes or configurations:
|
||||
|
||||
1. **Pin the exact mode/configuration** the refactored code will use, in one explicit sentence. Inputs (data shapes, sensor counts, payloads, rates), outputs (per `acceptance_criteria.md` and contract files), runtime (matching the project's deployment).
|
||||
2. **Run `context7` (or equivalent docs lookup)** for the candidate. **Mandatory for every replacement library/SDK/framework candidate**, not optional. Minimum three queries per candidate: mode enumeration, project's exact mode (with input/output shapes), disqualifier probe ("does this mode produce the required output? are there published limitations on this runtime?"). Append URLs to `RUN_DIR/analysis/research_findings.md` references section.
|
||||
3. **Save a Minimum Viable Example (MVE)** for the pinned mode under `RUN_DIR/analysis/mve_evidence.md` with: source, inputs in example, outputs in example, project inputs, project outputs required, match assessment ✅/⚠️/❌. If no official example covers the project's exact configuration, the recommendation cannot be `Selected` based on category fit alone — it must be `Experimental only` (with required-evidence note) or `Rejected`.
|
||||
4. **Treat "the same library in a different mode" as a different recommendation.** If the project's pinned mode is `<X>` but the only documented evidence covers `<Y>`, do not silently soften the description. Open a separate recommendation row, with its own MVE, fit assessment, and disqualifiers.
|
||||
5. **Common silent-failure pattern**: a fact summary paraphrases docs as "supports A, B, C, D modes" when the docs actually mean "supports A; B; C and D as separate orthogonal modes" — no `A+B` combination exists. Cross-check paraphrased capability claims against the literal mode enumeration.
|
||||
|
||||
5. Identify what could be done differently
|
||||
6. Suggest improvements only when they fit the Project Constraint Matrix. A cleaner or more modern approach that violates product constraints must be marked `Rejected` or `Experimental only`, not added as a roadmap recommendation.
|
||||
2. Research modern approaches for similar systems
|
||||
3. Identify what could be done differently
|
||||
4. Suggest improvements based on state-of-the-art practices
|
||||
|
||||
Write `RUN_DIR/analysis/research_findings.md`:
|
||||
- Current state analysis: patterns used, strengths, weaknesses
|
||||
- Alternative approaches per component: current vs alternative, pros/cons, migration effort
|
||||
- Prioritized recommendations: quick wins + strategic improvements
|
||||
- Constraint-fit table: recommendation, **pinned mode/config**, constraints checked, **API capability evidence (MVE link)**, evidence, mismatches/disqualifiers, status (`Selected` / `Rejected` / `Experimental only` / `Needs user decision`)
|
||||
- For every recommendation that replaces or adds a library/SDK/framework, append a **Restrictions × Candidate-Mode sub-matrix** that walks every numbered line of `restrictions.md` and `acceptance_criteria.md` against the candidate's pinned mode, marking each cell ✅ Pass / ❌ Fail / ❓ Verify / N/A with cited evidence. A recommendation cannot be `Selected` while any cell is ❌ or ❓.
|
||||
|
||||
## 2b. Solution Assessment & Hardening Tracks
|
||||
|
||||
@@ -37,7 +22,6 @@ Write `RUN_DIR/analysis/research_findings.md`:
|
||||
2. Identify weak points in codebase, map to specific code areas
|
||||
3. Perform gap analysis: acceptance criteria vs current state
|
||||
4. Prioritize changes by impact and effort
|
||||
5. Reject or escalate any proposed refactor that improves code structure while weakening required behavior, integration contracts, runtime constraints, safety/security posture, or acceptance criteria
|
||||
|
||||
Present optional hardening tracks for user to include in the roadmap:
|
||||
|
||||
@@ -63,9 +47,6 @@ Write `RUN_DIR/analysis/refactoring_roadmap.md`:
|
||||
- Gap analysis: what's missing, what needs improvement
|
||||
- Phased roadmap: Phase 1 (critical fixes), Phase 2 (major improvements), Phase 3 (enhancements)
|
||||
- Selected hardening tracks and their items
|
||||
- Applicability gate: each roadmap item must state constraint fit, mismatches, required evidence, and status (`Selected` / `Rejected` / `Experimental only` / `Needs user decision`)
|
||||
|
||||
**BLOCKING applicability gate**: Before 2c and 2d, every recommendation in the roadmap must be `Selected`. Items marked `Rejected` are excluded. Items marked `Experimental only` or `Needs user decision` require a user decision before task creation.
|
||||
|
||||
## 2c. Create Epic
|
||||
|
||||
@@ -74,7 +55,7 @@ Create a work item tracker epic for this refactoring run:
|
||||
1. Epic name: the RUN_DIR name (e.g., `01-testability-refactoring`)
|
||||
2. Create the epic via configured tracker MCP
|
||||
3. Record the Epic ID — all tasks in 2d will be linked under this epic
|
||||
4. If tracker is unavailable, follow `.cursor/rules/tracker.mdc`; only use `PENDING` placeholders if the user explicitly chooses `tracker: local`
|
||||
4. If tracker unavailable, use `PENDING` placeholder and note for later
|
||||
|
||||
## 2d. Task Decomposition
|
||||
|
||||
@@ -98,12 +79,6 @@ Convert the finalized `RUN_DIR/list-of-changes.md` into implementable task files
|
||||
**Self-verification**:
|
||||
- [ ] All acceptance criteria are addressed in gap analysis
|
||||
- [ ] Recommendations are grounded in actual code, not abstract
|
||||
- [ ] Every recommendation has been checked against the Project Constraint Matrix
|
||||
- [ ] No recommendation violates product restrictions, acceptance criteria, documented architecture decisions, or actual code integration boundaries
|
||||
- [ ] Every replacement library/SDK/framework recommendation has a pinned mode/config, a saved MVE in `mve_evidence.md`, and a Restrictions × Candidate-Mode sub-matrix with no ❌ or ❓ cells
|
||||
- [ ] `context7` (or equivalent) was consulted for every replacement library/SDK/framework recommendation
|
||||
- [ ] Paraphrased capability claims have been cross-checked against the literal mode-enumeration evidence (no `A, B → A+B` style conflation)
|
||||
- [ ] Rejected and experimental approaches are documented but not converted into implementation tasks without user approval
|
||||
- [ ] Roadmap phases are prioritized by impact
|
||||
- [ ] Epic created and all tasks linked to it
|
||||
- [ ] Every entry in list-of-changes.md has a corresponding task file in TASKS_DIR
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- All `[TRACKER-ID]_refactor_*.md` files are present
|
||||
- Each task file has valid header fields (Task, Name, Description, Complexity, Dependencies)
|
||||
2. Verify `TASKS_DIR/_dependencies_table.md` includes the refactoring tasks
|
||||
3. Verify all tests pass (safety net from Phase 3 is green), unless this is a testability run where Phase 3 was intentionally skipped
|
||||
3. Verify all tests pass (safety net from Phase 3 is green)
|
||||
4. If any check fails, go back to the relevant phase to fix
|
||||
|
||||
## 4b. Delegate to Implement Skill
|
||||
@@ -21,9 +21,9 @@ The implement skill will:
|
||||
1. Parse task files and dependency graph from TASKS_DIR
|
||||
2. Detect already-completed tasks (skip non-refactoring tasks from prior workflow steps)
|
||||
3. Compute execution batches for the refactoring tasks
|
||||
4. Implement tasks sequentially in topological order (no subagents, no parallelism)
|
||||
4. Launch implementer subagents (up to 4 in parallel)
|
||||
5. Run code review after each batch
|
||||
6. Commit per batch and push only when the user approved pushing
|
||||
6. Commit and push per batch
|
||||
7. Update work item ticket status
|
||||
|
||||
Do NOT modify, skip, or abbreviate any part of the implement skill's workflow. The refactor skill is delegating execution, not optimizing it.
|
||||
@@ -47,7 +47,7 @@ After the implement skill completes:
|
||||
For each successfully completed refactoring task:
|
||||
|
||||
1. Transition the work item ticket status to **Done** via the configured tracker MCP
|
||||
2. If tracker is unavailable, follow `.cursor/rules/tracker.mdc`; if the user explicitly chose `tracker: local`, note the pending status transitions in `RUN_DIR/execution_log.md`
|
||||
2. If tracker unavailable, note the pending status transitions in `RUN_DIR/execution_log.md`
|
||||
|
||||
For any failed or blocked tasks, leave their status as-is (the implement skill already set them to In Testing or blocked).
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ For each component doc affected:
|
||||
## 7d. Update System-Level Documentation
|
||||
|
||||
If structural changes were made (new modules, removed modules, changed interfaces):
|
||||
1. Update `_docs/02_document/architecture.md` if architecture changed — but **never edit the `## Architecture Vision` section**. That section is user-confirmed (plan Phase 2a.0 / document Step 4.5); if a refactor invalidates a vision principle, surface it to the user and let them update the vision themselves before continuing. Update only the technical sections below the Vision H2.
|
||||
1. Update `_docs/02_document/architecture.md` if architecture changed
|
||||
2. Update `_docs/02_document/system-flows.md` if flow sequences changed
|
||||
3. Update `_docs/02_document/diagrams/components.md` if component relationships changed
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ Save as `RUN_DIR/list-of-changes.md`. Produced during Phase 1 (Discovery).
|
||||
- **Problem**: [what makes this problematic / untestable / coupled]
|
||||
- **Change**: [what to do — behavioral description, not implementation steps]
|
||||
- **Rationale**: [why this change is needed]
|
||||
- **Constraint Fit**: [which product constraints / acceptance criteria / integration boundaries this preserves; or "Rejected — violates ..."]
|
||||
- **Risk**: [low | medium | high]
|
||||
- **Dependencies**: [other change IDs this depends on, or "None"]
|
||||
|
||||
@@ -32,7 +31,6 @@ Save as `RUN_DIR/list-of-changes.md`. Produced during Phase 1 (Discovery).
|
||||
- **Problem**: [description]
|
||||
- **Change**: [description]
|
||||
- **Rationale**: [description]
|
||||
- **Constraint Fit**: [description]
|
||||
- **Risk**: [low | medium | high]
|
||||
- **Dependencies**: [C01, or "None"]
|
||||
```
|
||||
@@ -46,8 +44,6 @@ Save as `RUN_DIR/list-of-changes.md`. Produced during Phase 1 (Discovery).
|
||||
- **File(s)** must reference actual files verified to exist in the codebase
|
||||
- **Problem** describes the current state, not the desired state
|
||||
- **Change** describes what the system should do differently — behavioral, not prescriptive
|
||||
- **Constraint Fit** proves the change preserves confirmed product requirements, restrictions, acceptance criteria, architecture decisions, and integration contracts
|
||||
- Do not include changes whose only benefit is structural cleanliness if they weaken required behavior or violate constraints; record those as rejected in analysis instead
|
||||
- **Dependencies** reference other change IDs within this list; cross-run dependencies use tracker IDs
|
||||
- In guided mode, the input file entries are validated against actual code and enriched with file paths, risk, and dependencies before writing
|
||||
- In automatic mode, entries are derived from Phase 1 component analysis and Phase 2 research findings
|
||||
|
||||
@@ -30,27 +30,6 @@ Transform vague topics raised by users into high-quality, deliverable research r
|
||||
- **Internet-first investigation** — do not rely on training data for factual claims; search the web extensively for every sub-question, rephrase queries when results are thin, and keep searching until you have converging evidence from multiple independent sources
|
||||
- **Multi-perspective analysis** — examine every problem from at least 3 different viewpoints (e.g., end-user, implementer, business decision-maker, contrarian, domain expert, field practitioner); each perspective should generate its own search queries
|
||||
- **Question multiplication** — for each sub-question, generate multiple reformulated search queries (synonyms, related terms, negations, "what can go wrong" variants, practitioner-focused variants) to maximize coverage and uncover blind spots
|
||||
- **Component option breadth** — for every component area, build a broad option landscape before selecting. Search direct candidates, adjacent-domain alternatives, commercial/open-source variants, classical/simple baselines, current SOTA, and "do not use" failure cases. A component may not be narrowed to one candidate until alternatives have been searched and rejected with evidence.
|
||||
- **Component research depth** — for every serious component candidate, go beyond discovery pages. Read official docs, repository/license files, issue discussions, benchmarks, deployment guides, version/platform requirements, security notes, maintenance signals, and real-world failure reports. Extract evidence for inputs/outputs, lifecycle assumptions, runtime/storage/latency fit, integration boundaries, licensing, operational risks, and unsupported scenarios before assigning any selection status.
|
||||
- **Exact-fit component selection** — never select a component, tool, library, service, architecture pattern, or algorithm merely because it solves a similar class of problem. It must be proven compatible with the project's explicit operating context, constraints, required inputs/outputs, non-functional requirements, lifecycle assumptions, and acceptance criteria. If fit is unproven or mismatched, mark it `Rejected`, `Experimental only`, or escalate for user decision before it can shape the solution.
|
||||
- **Per-mode API capability verification** *(applies only to technical-component selection — see Research Output Class below)* — when a candidate library/SDK/framework/service exposes multiple modes or configurations, *the candidate is not a single thing*. Pin the exact mode the project will use (one explicit sentence: inputs, outputs, runtime), and verify *that mode* against the project's required inputs/outputs via official docs (mandatory `context7` lookup) plus a saved Minimum Viable Example. Capability claims at the category level ("supports X, Y, Z modes") must be cross-checked against the literal mode enumeration before being treated as project-applicable. Two modes of one library are two distinct candidates for the purposes of the Component Applicability Gate. Does not apply to non-technical research (concept comparison, market/policy investigation, knowledge organization, etc.).
|
||||
|
||||
## Research Output Class (BLOCKING — set in Step 1)
|
||||
|
||||
Before applying any of the technical-component gates (per-mode API capability verification, Component Applicability Gate, Restrictions × Candidate-Mode sub-matrix, MVE evidence, mandatory `context7` lookup), classify the research output into one of two classes. Record the decision in `00_question_decomposition.md` once, near the top, so every downstream step honors it.
|
||||
|
||||
| Class | What the output recommends or selects | Examples | Technical-component gates apply? |
|
||||
|-------|---------------------------------------|----------|----------------------------------|
|
||||
| **Technical-component selection** | One or more libraries, SDKs, frameworks, services, protocols, data formats, infrastructure patterns, algorithms, or APIs that will be implemented or operated against | "Pick a vector database", "Compare auth-token strategies for our API", "Should we use Kafka or RabbitMQ?", architecture / tech-stack / migration drafts (Mode A, Mode B) | **Yes — all gates active** |
|
||||
| **Non-technical investigation** | Concept comparisons, knowledge organization, root-cause investigation of an event, market/policy/regulatory/social analysis, literature review, decision support without committing to specific tooling | "Why did adoption stall in Q3?", "Compare phenomenology vs constructivism", "Map regulatory landscape for X", "What do practitioners say about onboarding under remote-first orgs?" | **No — skip API/MVE/sub-matrix gates; the rest of the 8-step engine still applies** |
|
||||
|
||||
How to decide:
|
||||
1. Inspect the question and the input files (`problem.md`, `restrictions.md`, `acceptance_criteria.md`, or the standalone input file).
|
||||
2. If the deliverable will name specific software/services/protocols that someone will then build with or operate, it is **Technical-component selection**.
|
||||
3. If the deliverable is a report, comparison, or recommendation that does not commit to specific tooling, it is **Non-technical investigation**.
|
||||
4. **Mixed runs are valid.** Some research questions have a non-technical core but include one technical sub-question (or vice versa). In that case classify per component area within the run, not the run as a whole, and note in `00_question_decomposition.md` which component areas trigger the technical-component gates.
|
||||
|
||||
When the run is purely **Non-technical investigation**, the rest of the research engine — question decomposition, perspective rotation, exhaustive web search, fact extraction, comparison framework, reasoning chain, validation, deliverable formatting — still applies in full. The sections that get skipped are explicitly the technical gates listed in the table above.
|
||||
|
||||
## Context Resolution
|
||||
|
||||
|
||||
@@ -27,26 +27,13 @@
|
||||
- [ ] Iterative deepening completed: follow-up questions from initial findings were searched
|
||||
- [ ] No sub-question relies solely on training data without web verification
|
||||
|
||||
## Component Option Breadth
|
||||
|
||||
- [ ] `00_question_decomposition.md` contains a Component Option Search Plan
|
||||
- [ ] Every component area was searched across simple baseline, established production, open-source, commercial/vendor, current SOTA, adjacent-domain, no-build/defer, and known-bad options where applicable
|
||||
- [ ] Every component area has at least 3 realistic candidates, or a documented explanation of why broad searches found fewer
|
||||
- [ ] Each lead candidate has official/source-of-truth evidence plus independent validation when available
|
||||
- [ ] Each component area includes at least one baseline/fallback option and at least one rejected or experimental option when possible
|
||||
- [ ] Alternative names, synonyms, and neighboring-domain terms were searched before declaring the option landscape complete
|
||||
- [ ] Licensing, runtime, platform, maintenance, and unsupported-scenario searches were performed for every lead, fallback, and rejected candidate
|
||||
|
||||
## Mode A Specific
|
||||
|
||||
- [ ] Phase 1 completed: AC assessment was presented to and confirmed by user
|
||||
- [ ] AC assessment consistent: Solution draft respects the (possibly adjusted) acceptance criteria and restrictions
|
||||
- [ ] Competitor analysis included: Existing solutions were researched
|
||||
- [ ] All components have comparison tables: Each component lists alternatives with tools, advantages, limitations, security, cost
|
||||
- [ ] Component options are broad: component tables include baseline, production, open-source, commercial/vendor, SOTA/research, adjacent-domain, defer/no-build, and disqualified options where applicable
|
||||
- [ ] Tools/libraries verified: Suggested tools actually exist and work as described
|
||||
- [ ] Component fit matrix completed: `06_component_fit_matrix.md` (or `06_component_fit_matrix/` if split) exists and every selected component/tool/pattern is marked `Selected`
|
||||
- [ ] No field-adjacent substitution: no selected candidate is chosen only because it solves a similar class of problem while failing the project's explicit constraints
|
||||
- [ ] Testing strategy covers AC: Tests map to acceptance criteria
|
||||
- [ ] Tech stack documented (if Phase 3 ran): `tech_stack.md` has evaluation tables, risk assessment, and learning requirements
|
||||
- [ ] Security analysis documented (if Phase 4 ran): `security_analysis.md` has threat model and per-component controls
|
||||
@@ -58,9 +45,6 @@
|
||||
- [ ] New draft is self-contained: Written as if from scratch, no "updated" markers
|
||||
- [ ] Performance column included: Mode B comparison tables include performance characteristics
|
||||
- [ ] Previous draft issues addressed: Every finding in the table is resolved in the new draft
|
||||
- [ ] Existing selected components were challenged against a broad alternative landscape before being kept
|
||||
- [ ] Existing component fit audited: every old and new component/tool/pattern was checked against `restrictions.md`, `acceptance_criteria.md`, and the Project Constraint Matrix
|
||||
- [ ] Rejected/experimental candidates are not lead recommendations unless the user explicitly accepted the risk
|
||||
|
||||
## Timeliness Check (High-Sensitivity Domain BLOCKING)
|
||||
|
||||
@@ -80,7 +64,7 @@ When the research topic has Critical or High sensitivity level:
|
||||
## Target Audience Consistency Check (BLOCKING)
|
||||
|
||||
- [ ] Research boundary clearly defined: `00_question_decomposition.md` has clear population/geography/timeframe/level boundaries
|
||||
- [ ] Every source has target audience annotated in `01_source_registry.md` (or category files under `01_source_registry/` if split)
|
||||
- [ ] Every source has target audience annotated in `01_source_registry.md`
|
||||
- [ ] Mismatched sources properly handled (excluded, annotated, or marked reference-only)
|
||||
- [ ] No audience confusion in fact cards: Every fact has target audience consistent with research boundary
|
||||
- [ ] No audience confusion in the report: Policies/research/data cited have consistent target audiences
|
||||
@@ -92,33 +76,3 @@ When the research topic has Critical or High sensitivity level:
|
||||
- [ ] Cited facts have corresponding statements in the original text (no over-interpretation)
|
||||
- [ ] Source publication/update dates annotated; technical docs include version numbers
|
||||
- [ ] Unverifiable information annotated `[limited source]` and not sole support for core conclusions
|
||||
|
||||
## Exact-Fit Validation (BLOCKING)
|
||||
|
||||
- [ ] Project Constraint Matrix extracted from problem context before component selection
|
||||
- [ ] Component fit matrix includes `Component Area`, `Option Family`, and `Pinned Mode/Config` columns
|
||||
- [ ] Every selected component/tool/library/service/pattern/algorithm has evidence for required inputs/outputs and integration boundaries
|
||||
- [ ] Every selected candidate has evidence for the operating context and lifecycle assumptions it must support
|
||||
- [ ] Every selected candidate has evidence for non-functional targets that are binding for the project
|
||||
- [ ] Known unsupported scenarios and failure reports were searched for every selected candidate
|
||||
- [ ] Mismatches are recorded as disqualifiers, not softened into generic limitations
|
||||
- [ ] Any candidate with unproven fit is marked `Experimental only` or escalated for user decision
|
||||
- [ ] Any candidate with documented constraint conflict is marked `Rejected`
|
||||
|
||||
## API Capability Verification (BLOCKING)
|
||||
|
||||
**Applicability**: this checklist applies only when the run is classified as **Technical-component selection** (see SKILL.md → Research Output Class). For non-technical research (concept comparison, market/policy investigation, root-cause analysis, knowledge organization), skip this checklist entirely and note the skip in `05_validation_log.md`. For mixed runs, apply only to technical component areas.
|
||||
|
||||
For every lead candidate that is a library/SDK/framework/service:
|
||||
|
||||
- [ ] The exact mode/configuration the project will use is pinned in one explicit sentence (inputs, outputs, runtime); no vague "supports X" language
|
||||
- [ ] `context7` (or equivalent docs lookup) was run for the candidate, with at least 3 queries: mode enumeration, project's exact mode, disqualifier probe
|
||||
- [ ] All consulted URLs from context7 / official docs are appended to `01_source_registry.md` (or files under `01_source_registry/` if split)
|
||||
- [ ] A Minimum Viable Example (MVE) was saved for the pinned mode in `02_fact_cards.md` / `02_fact_cards/` (or `02_mve_evidence.md`) with: source, inputs in example, outputs in example, project inputs, project outputs required, match assessment ✅/⚠️/❌
|
||||
- [ ] When the MVE inputs or outputs do not exactly match the project's, the mismatch is cited from the official docs (not inferred), and the candidate is `Experimental only` or `Rejected`
|
||||
- [ ] When a library has multiple modes, each project-relevant mode appears as its own candidate row (not a single library row that softens across modes)
|
||||
- [ ] Restrictions × Candidate-Modes sub-matrix in `06_component_fit_matrix.md` (or files under `06_component_fit_matrix/` if split) is filled for every lead candidate, with one row per numbered restriction and per numbered acceptance criterion
|
||||
- [ ] Sub-matrix uses ✅ / ❌ / ❓ / N/A only — no free-form prose substitutes
|
||||
- [ ] No `Selected` candidate has any ❌ or ❓ cell in its sub-matrix
|
||||
- [ ] "Validation gate required" footnotes are explicitly classified as either *API capability* (must be resolved here) or *runtime quality* (may be carried forward)
|
||||
- [ ] Paraphrased capability claims in fact cards have been cross-checked against the literal mode-enumeration evidence (no `mono, inertial → mono-inertial` style conflation)
|
||||
|
||||
@@ -89,7 +89,7 @@ Value Translation:
|
||||
|
||||
## Source Registry Entry Template
|
||||
|
||||
For each source consulted, immediately append to `01_source_registry.md` (or the appropriate category file under `01_source_registry/` if the artifact has been split — see splittable-artifacts convention in `steps/00_project-integration.md`):
|
||||
For each source consulted, immediately append to `01_source_registry.md`:
|
||||
```markdown
|
||||
## Source #[number]
|
||||
- **Title**: [source title]
|
||||
|
||||
@@ -57,49 +57,22 @@ RESEARCH_DIR/
|
||||
├── 03_comparison_framework.md # Step 4 output: selected framework and populated data
|
||||
├── 04_reasoning_chain.md # Step 6 output: fact → conclusion reasoning
|
||||
├── 05_validation_log.md # Step 7 output: use-case validation results
|
||||
├── 06_component_fit_matrix.md # Step 7.5 output: component exact-fit gate
|
||||
└── raw/ # Raw source archive (optional)
|
||||
├── source_1.md
|
||||
└── source_2.md
|
||||
```
|
||||
|
||||
#### Splittable artifacts — Layout convention
|
||||
|
||||
The following three artifacts MAY equivalently be a **folder** of the same base name when the single-file form has grown unwieldy (typically ≳ 1000 lines or ≳ 200 KB):
|
||||
|
||||
- `01_source_registry.md` ↔ `01_source_registry/`
|
||||
- `02_fact_cards.md` ↔ `02_fact_cards/`
|
||||
- `06_component_fit_matrix.md` ↔ `06_component_fit_matrix/`
|
||||
|
||||
When using the folder form:
|
||||
|
||||
- Place a `00_summary.md` index file at the folder root with a short common summary table and the cross-cutting status the single-file form would have carried in its preamble.
|
||||
- Split per-entry content into category files (e.g. one file per sub-question or per component): `SQ1_*.md`, `C1_*.md`, etc. Keep entry numbering global across the folder so cross-references like "Source #42" still resolve to exactly one place.
|
||||
- Cross-references from outside the folder may point at either `01_source_registry/00_summary.md` (for the index) or directly at the relevant category file.
|
||||
|
||||
```
|
||||
RESEARCH_DIR/01_source_registry/ # split form (when single-file is too large)
|
||||
├── 00_summary.md # index + investigation status + compact source table
|
||||
├── SQ1_existing_systems.md # category file
|
||||
├── SQ2_canonical_pipeline.md # category file
|
||||
├── C1_vio.md # per-component file
|
||||
└── ...
|
||||
```
|
||||
|
||||
Throughout the rest of this skill (other steps, references, templates), the singular `XX.md` form is used as a logical name; treat each occurrence as applying equally to the folder form when the artifact has been split.
|
||||
|
||||
### Save Timing & Content
|
||||
|
||||
| Step | Save immediately after completion | Filename |
|
||||
|------|-----------------------------------|----------|
|
||||
| Mode A Phase 1 | AC & restrictions assessment tables | `00_ac_assessment.md` |
|
||||
| Step 0-1 | Question type classification + sub-question list | `00_question_decomposition.md` |
|
||||
| Step 2 | Each consulted source link, tier, summary | `01_source_registry.md` *(splittable, see convention)* |
|
||||
| Step 3 | Each fact card (statement + source + confidence) | `02_fact_cards.md` *(splittable, see convention)* |
|
||||
| Step 2 | Each consulted source link, tier, summary | `01_source_registry.md` |
|
||||
| Step 3 | Each fact card (statement + source + confidence) | `02_fact_cards.md` |
|
||||
| Step 4 | Selected comparison framework + initial population | `03_comparison_framework.md` |
|
||||
| Step 6 | Reasoning process for each dimension | `04_reasoning_chain.md` |
|
||||
| Step 7 | Validation scenarios + results + review checklist | `05_validation_log.md` |
|
||||
| Step 7.5 | Component exact-fit gate and selection status | `06_component_fit_matrix.md` *(splittable, see convention)* |
|
||||
| Step 8 | Complete solution draft | `OUTPUT_DIR/solution_draft##.md` |
|
||||
|
||||
### Save Principles
|
||||
@@ -117,12 +90,11 @@ Throughout the rest of this skill (other steps, references, templates), the sing
|
||||
|------|---------|----------------|
|
||||
| `00_ac_assessment.md` | AC & restrictions assessment (Mode A only) | After Phase 1 completion |
|
||||
| `00_question_decomposition.md` | Question type, sub-question list | After Step 0-1 completion |
|
||||
| `01_source_registry.md` *(splittable)* | All source links and summaries | Continuously updated during Step 2 |
|
||||
| `02_fact_cards.md` *(splittable)* | Extracted facts and sources | Continuously updated during Step 3 |
|
||||
| `01_source_registry.md` | All source links and summaries | Continuously updated during Step 2 |
|
||||
| `02_fact_cards.md` | Extracted facts and sources | Continuously updated during Step 3 |
|
||||
| `03_comparison_framework.md` | Selected framework and populated data | After Step 4 completion |
|
||||
| `04_reasoning_chain.md` | Fact → conclusion reasoning | After Step 6 completion |
|
||||
| `05_validation_log.md` | Use-case validation and review | After Step 7 completion |
|
||||
| `06_component_fit_matrix.md` *(splittable)* | Exact-fit matrix for every proposed component/tool/pattern with status `Selected` / `Rejected` / `Experimental only` / `Needs user decision` | Before Step 8 deliverable formatting |
|
||||
| `OUTPUT_DIR/solution_draft##.md` | Complete solution draft | After Step 8 completion |
|
||||
| `OUTPUT_DIR/tech_stack.md` | Tech stack evaluation and decisions | After Phase 3 (optional) |
|
||||
| `OUTPUT_DIR/security_analysis.md` | Threat model and security controls | After Phase 4 (optional) |
|
||||
|
||||
@@ -6,9 +6,7 @@ Triggered when no `solution_draft*.md` files exist in OUTPUT_DIR, or when the us
|
||||
|
||||
**Role**: Professional software architect
|
||||
|
||||
> **AC must be design-independent**: describe testable outcomes only — no libraries, algorithms, params, or design choices. Implementation follows AC, never reverse. (IEEE 830 / Atlassian / GitScrum)
|
||||
|
||||
A focused preliminary research pass **before** the main solution research. The goal is to validate that the acceptance criteria and restrictions are realistic before designing a solution around them. Any revision proposed in this phase must respect the design-independence rule above — propose AC changes as outcome/budget edits, not as implementation prescriptions.
|
||||
A focused preliminary research pass **before** the main solution research. The goal is to validate that the acceptance criteria and restrictions are realistic before designing a solution around them.
|
||||
|
||||
**Input**: All files from INPUT_DIR (or INPUT_FILE in standalone mode)
|
||||
|
||||
@@ -75,18 +73,16 @@ Full 8-step research methodology. Produces the first solution draft.
|
||||
**Task** (drives the 8-step engine):
|
||||
1. Research existing/competitor solutions for similar problems — search broadly across industries and adjacent domains, not just the obvious competitors
|
||||
2. Research the problem thoroughly — all possible ways to solve it, split into components; search for how different fields approach analogous problems
|
||||
3. Derive a **Project Constraint Matrix** before evaluating component options. Extract exact constraints from `problem.md`, `restrictions.md`, `acceptance_criteria.md`, input data notes, and the Phase 1 AC assessment. Include required inputs/outputs, operating context, runtime envelope, data availability, lifecycle boundaries, non-functional targets, integration boundaries, security constraints, and explicit out-of-scope decisions.
|
||||
4. For each component, research all possible solutions and find the most efficient state-of-the-art approaches — use multiple query variants and perspectives from Step 1
|
||||
5. For each promising approach, search for real-world deployment experience: success stories, failure reports, lessons learned, and practitioner opinions
|
||||
6. Search for contrarian viewpoints — who argues against the common approaches and why? What failure modes exist?
|
||||
7. Verify that suggested tools/libraries actually exist and work as described — check official repos, latest releases, and community health (stars, recent commits, open issues)
|
||||
8. For every candidate component/tool/library/service/pattern/algorithm, prove exact fit against the Project Constraint Matrix. A field-adjacent solution is not selectable unless its documented implementation assumptions match the project's constraints. Mismatches must be recorded as disqualifiers and the candidate marked `Rejected`, `Experimental only`, or `Needs user decision`.
|
||||
9. Include security considerations in each component analysis
|
||||
10. Provide rough cost estimates for proposed solutions
|
||||
3. For each component, research all possible solutions and find the most efficient state-of-the-art approaches — use multiple query variants and perspectives from Step 1
|
||||
4. For each promising approach, search for real-world deployment experience: success stories, failure reports, lessons learned, and practitioner opinions
|
||||
5. Search for contrarian viewpoints — who argues against the common approaches and why? What failure modes exist?
|
||||
6. Verify that suggested tools/libraries actually exist and work as described — check official repos, latest releases, and community health (stars, recent commits, open issues)
|
||||
7. Include security considerations in each component analysis
|
||||
8. Provide rough cost estimates for proposed solutions
|
||||
|
||||
Be concise in formulating. The fewer words, the better, but do not miss any important details.
|
||||
|
||||
**Save action**: Write `RESEARCH_DIR/06_component_fit_matrix.md` (or its split-folder equivalent under `RESEARCH_DIR/06_component_fit_matrix/`, per the splittable-artifacts convention in `00_project-integration.md`) before the final draft, then write `OUTPUT_DIR/solution_draft##.md` using template: `templates/solution_draft_mode_a.md`
|
||||
**Save action**: Write `OUTPUT_DIR/solution_draft##.md` using template: `templates/solution_draft_mode_a.md`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -10,25 +10,18 @@ Full 8-step research methodology applied to assessing and improving an existing
|
||||
|
||||
**Task** (drives the 8-step engine):
|
||||
1. Read the existing solution draft thoroughly
|
||||
2. Derive or refresh the **Project Constraint Matrix** from all files in INPUT_DIR. Include required inputs/outputs, operating context, runtime envelope, data availability, lifecycle boundaries, non-functional targets, integration boundaries, security constraints, and explicit out-of-scope decisions.
|
||||
3. Audit every component/decision in the existing draft against the Project Constraint Matrix before researching alternatives:
|
||||
- If a component's documented implementation assumptions match the project constraints, keep it eligible and record evidence.
|
||||
- If fit is unproven, mark it `Experimental only` until evidence is found.
|
||||
- If constraints conflict, mark it `Rejected` and search for alternatives.
|
||||
- If rejecting it changes product behavior or risk materially, escalate for user decision.
|
||||
4. Research in internet extensively — for each component/decision in the draft, search for:
|
||||
2. Research in internet extensively — for each component/decision in the draft, search for:
|
||||
- Known problems and limitations of the chosen approach
|
||||
- What practitioners say about using it in production
|
||||
- Better alternatives that may have emerged recently
|
||||
- Common failure modes and edge cases
|
||||
- How competitors/similar projects solve the same problem differently
|
||||
5. Search specifically for contrarian views: "why not [chosen approach]", "[chosen approach] criticism", "[chosen approach] failure"
|
||||
6. Identify security weak points and vulnerabilities — search for CVEs, security advisories, and known attack vectors for each technology in the draft
|
||||
7. Identify performance bottlenecks — search for benchmarks, load test results, and scalability reports
|
||||
8. For each identified weak point, search for multiple solution approaches and compare them
|
||||
9. For every revised candidate, prove exact fit against the Project Constraint Matrix. Do not select field-adjacent or "similar problem" options unless their intrinsic implementation constraints match the project.
|
||||
10. Based on findings, form a new solution draft in the same format
|
||||
3. Search specifically for contrarian views: "why not [chosen approach]", "[chosen approach] criticism", "[chosen approach] failure"
|
||||
4. Identify security weak points and vulnerabilities — search for CVEs, security advisories, and known attack vectors for each technology in the draft
|
||||
5. Identify performance bottlenecks — search for benchmarks, load test results, and scalability reports
|
||||
6. For each identified weak point, search for multiple solution approaches and compare them
|
||||
7. Based on findings, form a new solution draft in the same format
|
||||
|
||||
**Save action**: Write `RESEARCH_DIR/06_component_fit_matrix.md` (or its split-folder equivalent under `RESEARCH_DIR/06_component_fit_matrix/`, per the splittable-artifacts convention in `00_project-integration.md`) before the final draft, then write `OUTPUT_DIR/solution_draft##.md` (incremented) using template: `templates/solution_draft_mode_b.md`
|
||||
**Save action**: Write `OUTPUT_DIR/solution_draft##.md` (incremented) using template: `templates/solution_draft_mode_b.md`
|
||||
|
||||
**Optional follow-up**: After Mode B completes, the user can request Phase 3 (Tech Stack Consolidation) or Phase 4 (Security Deep Dive) using the revised draft. These phases work identically to their Mode A descriptions in `steps/01_mode-a-initial-research.md`.
|
||||
|
||||
@@ -40,7 +40,6 @@ Key principle: Critical-sensitivity topics (AI/LLMs, blockchain) require sources
|
||||
- "What existing/competitor solutions address this problem?"
|
||||
- "What are the component parts of this problem?"
|
||||
- "For each component, what are the state-of-the-art solutions?"
|
||||
- "For each component, what are the practical alternatives across simple baseline, established production option, open-source option, commercial option, current SOTA, adjacent-domain option, and no-build/defer option?"
|
||||
- "What are the security considerations per component?"
|
||||
- "What are the cost implications of each approach?"
|
||||
|
||||
@@ -49,7 +48,6 @@ Key principle: Critical-sensitivity topics (AI/LLMs, blockchain) require sources
|
||||
- "What are the security vulnerabilities in the proposed architecture?"
|
||||
- "Where are the performance bottlenecks?"
|
||||
- "What solutions exist for each identified issue?"
|
||||
- "For each component already selected in the draft, what alternatives should be considered before keeping, replacing, or rejecting it?"
|
||||
|
||||
**General sub-question patterns** (use when applicable):
|
||||
- **Sub-question A**: "What is X and how does it work?" (Definition & mechanism)
|
||||
@@ -86,27 +84,6 @@ For **each sub-question**, generate **at least 3-5 search query variants** befor
|
||||
|
||||
Record all planned queries in `00_question_decomposition.md` alongside each sub-question.
|
||||
|
||||
#### Component Option Breadth (MANDATORY)
|
||||
|
||||
Before Step 2, identify the component areas implied by the problem and create a search plan for options in each area. A component area is any replaceable tool, library, model, service, algorithm, data format, protocol, infrastructure pattern, or validation approach that could materially affect the solution.
|
||||
|
||||
For every component area, generate search queries for these option families unless clearly not applicable:
|
||||
- **Simple baseline**: low-complexity classical or manual approach that can serve as a fallback or regression baseline.
|
||||
- **Established production option**: mature library/service/pattern with field usage.
|
||||
- **Open-source candidate**: permissive-license option with inspectable implementation and community history.
|
||||
- **Commercial/vendor option**: paid or vendor-supported option, including SDK/platform constraints.
|
||||
- **Current SOTA / research option**: recent model, paper, or benchmark leader that may be promising but immature.
|
||||
- **Adjacent-domain option**: solution from a neighboring domain with similar constraints.
|
||||
- **No-build / defer option**: whether the component can be avoided, simplified, or moved out of scope.
|
||||
- **Known bad option**: candidate or family that appears attractive but has documented failure modes or disqualifiers.
|
||||
|
||||
For each component area, record:
|
||||
- Candidate names and option families to search.
|
||||
- At least 5 query variants covering alternatives, comparisons, limitations, licensing, runtime/scale, and exact project constraints.
|
||||
- The minimum evidence needed to mark a candidate `Selected`, `Rejected`, `Experimental only`, or `Needs user decision`.
|
||||
|
||||
Add this as a "Component Option Search Plan" section in `00_question_decomposition.md`.
|
||||
|
||||
**Research Subject Boundary Definition (BLOCKING - must be explicit)**:
|
||||
|
||||
When decomposing questions, you must explicitly define the **boundaries of the research subject**:
|
||||
@@ -117,9 +94,6 @@ When decomposing questions, you must explicitly define the **boundaries of the r
|
||||
| **Geography** | Which region is being studied? | Chinese universities vs US universities vs global |
|
||||
| **Timeframe** | Which period is being studied? | Post-2020 vs full historical picture |
|
||||
| **Level** | Which level is being studied? | Undergraduate vs graduate vs vocational |
|
||||
| **Operating context** | What exact environment, lifecycle phase, and runtime conditions must the solution support? | In-flight embedded runtime vs offline post-processing; production web traffic vs admin batch job |
|
||||
| **Required interfaces** | What inputs, outputs, protocols, data shapes, and ownership boundaries are fixed? | One camera vs stereo rig; REST API vs message queue; local file boundary vs service API |
|
||||
| **Non-functional envelope** | What latency, throughput, storage, memory, availability, safety, security, cost, and maintainability targets are binding? | <400 ms p95, 8 GB RAM, 99.9% availability, reversible migrations |
|
||||
|
||||
**Common mistake**: User asks about "university classroom issues" but sources include policies targeting "K-12 students" — mismatched target populations will invalidate the entire research.
|
||||
|
||||
@@ -142,11 +116,9 @@ Record the audit result in `00_question_decomposition.md` as a "Completeness Aud
|
||||
- Summary of relevant problem context from INPUT_DIR
|
||||
- Classified question type and rationale
|
||||
- **Research subject boundary definition** (population, geography, timeframe, level)
|
||||
- **Project Constraint Matrix summary** (operating context, required interfaces, non-functional envelope, lifecycle assumptions, and hard disqualifiers extracted from input files)
|
||||
- List of decomposed sub-questions
|
||||
- **Chosen perspectives** (at least 3 from the Perspective Rotation table) with rationale
|
||||
- **Search query variants** for each sub-question (at least 3-5 per sub-question)
|
||||
- **Component Option Search Plan** (component areas, option families, candidate names, query variants, required evidence)
|
||||
- **Completeness audit** (taxonomy cross-reference + domain discovery results)
|
||||
4. Write TodoWrite to track progress
|
||||
|
||||
@@ -160,7 +132,7 @@ Tier sources by authority, **prioritize primary sources** (L1 > L2 > L3 > L4). C
|
||||
|
||||
**Tool Usage**:
|
||||
- Use `WebSearch` for broad searches; `WebFetch` to read specific pages
|
||||
- Use the `context7` MCP server (`resolve-library-id` then `query-docs` / `get-library-docs`) for up-to-date library/framework documentation. **Mandatory per lead candidate** — see "API Capability Verification" below.
|
||||
- Use the `context7` MCP server (`resolve-library-id` then `get-library-docs`) for up-to-date library/framework documentation
|
||||
- Always cross-verify training data claims against live sources for facts that may have changed (versions, APIs, deprecations, security advisories)
|
||||
- When citing web sources, include the URL and date accessed
|
||||
|
||||
@@ -173,77 +145,17 @@ Do not stop at the first few results. The goal is to build a comprehensive evide
|
||||
- Consult at least **2 different source tiers** per sub-question (e.g., L1 official docs + L4 community discussion)
|
||||
- If initial searches yield fewer than 3 relevant sources for a sub-question, **broaden the search** with alternative terms, related domains, or analogous problems
|
||||
|
||||
**Minimum search effort per component area**:
|
||||
- Search every option family from the "Component Option Search Plan" before choosing a lead candidate.
|
||||
- For each lead, fallback, or rejected candidate, search at least one official/source-of-truth page and at least one independent validation source when available.
|
||||
- Search `"[component] alternatives"`, `"[candidate] vs [alternative]"`, `"[candidate] limitations"`, `"[candidate] license"`, `"[candidate] production"`, and `"[candidate] [binding project constraint]"`.
|
||||
- If fewer than 3 realistic candidates are found for a component area, explicitly document why the landscape is narrow and search adjacent domains before accepting that result.
|
||||
- Include at least one simple baseline and one "do not use" or disqualified candidate per component area when possible; these prevent false confidence in the selected option.
|
||||
|
||||
**Candidate implementation-limit searches (MANDATORY)**:
|
||||
For every component/tool/library/service/pattern/algorithm that may be selected or recommended, search for its intrinsic implementation constraints. Do not rely on product category labels, marketing summaries, or examples from a different operating context. Include query variants for:
|
||||
- Official supported inputs/outputs, protocols, data formats, and deployment modes
|
||||
- Required hardware/runtime/platform/version constraints
|
||||
- Timing, throughput, memory, storage, synchronization, and scaling assumptions
|
||||
- Lifecycle assumptions: offline vs online, batch vs real time, development vs production, single tenant vs multi tenant, local vs networked
|
||||
- Known unsupported scenarios, limitations, issue reports, production failures, and workarounds
|
||||
- Licensing, security, maintenance, and community-health constraints
|
||||
- Exact phrases from the project's restrictions and acceptance criteria combined with the candidate name
|
||||
|
||||
**API Capability Verification — Per-Mode (MANDATORY, BLOCKING for lead candidates)**:
|
||||
|
||||
**Applicability**: this section applies only when the run is classified as **Technical-component selection** in the SKILL's Research Output Class section, and only to lead candidates that are libraries/SDKs/frameworks/services/protocols/data formats with multiple modes or configurations. For non-technical research (concept comparison, market/policy investigation, knowledge organization, root-cause analysis without tooling commitments), skip this entire sub-section and continue with the rest of Step 2 — the broader candidate implementation-limit search above is sufficient. State the skip explicitly once in `02_fact_cards.md` (or in `02_fact_cards/00_summary.md` if split): `API Capability Verification: not applicable — this run is a Non-technical investigation, no library/SDK/service candidates`.
|
||||
|
||||
Most libraries/SDKs/services expose **multiple modes or configurations** (e.g., monocular vs stereo VO, sync vs async API, batch vs streaming inference, write-through vs write-behind cache). Selecting a candidate "because it supports X" without pinning *which mode* the project will use, and *whether that exact mode produces the required outputs from the required inputs*, is the most common silent-failure path in research. A library can support a class of problem in mode A while being unusable for the project's specific configuration in mode B.
|
||||
|
||||
For every lead candidate that is a library/SDK/framework/service with multiple modes or configurations, do the following — in this order, before marking the candidate `Selected`:
|
||||
|
||||
1. **Pin the exact mode/configuration the project will use.**
|
||||
Derived from the Project Constraint Matrix: which inputs are available (sensor count, sensor types, data shapes, rates), which outputs are required (per `acceptance_criteria.md` and contract files), which hardware/runtime is fixed (per `restrictions.md`). Write this as a single sentence: "We will use `<library>` in `<mode/config>` with inputs `<list>` and expect outputs `<list>` on `<runtime>`." Do not progress past this step on a vague mode description.
|
||||
|
||||
2. **Run `context7` (or equivalent docs lookup) for the candidate** — this is **mandatory for every lead library/SDK/framework candidate**, not optional. Minimum three queries per candidate:
|
||||
1. *Mode enumeration*: "What modes/configurations does `<library>` support? List every value of the mode/config enum and what each requires as input."
|
||||
2. *Project's exact mode*: "Show a minimum runnable example of `<library>` in `<the pinned mode>` with `<the project's input shape>`. What does it produce?"
|
||||
3. *Disqualifier probe*: "Does `<library>` `<the pinned mode>` produce `<the required output>`? Are there published limitations of `<the pinned mode>` for `<the project's runtime/hardware>`?"
|
||||
|
||||
For services without context7 coverage, use official docs site + WebFetch on the API reference page + the project's example/tutorial directory in the source repo. Append every consulted URL to `01_source_registry.md` (or the appropriate category file under `01_source_registry/` if split — see splittable-artifacts convention in `00_project-integration.md`).
|
||||
|
||||
3. **Save a Minimum Viable Example (MVE) for the pinned mode.**
|
||||
Append to `02_fact_cards.md` / `02_fact_cards/` (or a sibling `02_mve_evidence.md`) at least one block per lead library candidate with:
|
||||
|
||||
```markdown
|
||||
## MVE — <library> in <pinned mode>
|
||||
- **Source**: <official URL or context7 reference, with date>
|
||||
- **Inputs in the example**: <e.g., 2 calibrated cameras + IMU at 200 Hz>
|
||||
- **Outputs in the example**: <e.g., 6-DoF pose with covariance>
|
||||
- **Project inputs**: <e.g., 1 camera + IMU at 200 Hz>
|
||||
- **Project outputs required**: <e.g., 6-DoF pose with metric translation>
|
||||
- **Match assessment**: ✅ exact match / ⚠️ partial (specify dimension) / ❌ mismatch (specify dimension)
|
||||
- **If ⚠️ or ❌**: cite the official-docs sentence that establishes the mismatch.
|
||||
```
|
||||
|
||||
If no official example covers the project's exact configuration → the candidate cannot be marked `Selected` based on category fit alone. Status must be `Experimental only` (with required-evidence note) or `Rejected` (when the docs explicitly disqualify the configuration).
|
||||
|
||||
4. **Bind every numbered Restriction and Acceptance Criterion to the candidate's pinned mode.**
|
||||
For each numbered line in `restrictions.md` and `acceptance_criteria.md`, decide one of: `Pass` (the pinned mode satisfies it with cited evidence), `Fail` (the pinned mode contradicts it with cited evidence), `Verify` (no evidence either way; deeper investigation required), `N/A` (the line is irrelevant to this component area). Record this in `02_fact_cards.md` (or the candidate's per-component file under `02_fact_cards/` if split) under the candidate's MVE block. The structural matrix in Step 7.5 reads from these bindings.
|
||||
|
||||
5. **Treat "the same library in a different mode" as a different candidate.**
|
||||
If the project's pinned mode is `Monocular` but the only documented evidence covers `Stereo`, do not silently soften "rotation only" into "rotation + translation". Open a separate candidate row for the Monocular mode, with its own MVE, fit assessment, and disqualifiers. Two modes of one library are two distinct candidates for the purposes of this gate.
|
||||
|
||||
**Common silent-failure pattern this guards against**: a fact card paraphrases the docs as "supports A, B, C, D modes" when the docs actually mean "supports A; B; C and D as separate orthogonal modes". A category-level "Selected" decision then carries through every downstream artifact, masking that the project's required A+B combination does not exist as a single mode.
|
||||
|
||||
**Search broadening strategies** (use when results are thin):
|
||||
- Try adjacent fields: if researching "drone indoor navigation", also search "robot indoor navigation", "warehouse AGV navigation"
|
||||
- Try different communities: academic papers, industry whitepapers, military/defense publications, hobbyist forums
|
||||
- Try different geographies: search in English + search for European/Asian approaches if relevant
|
||||
- Try historical evolution: "history of X", "evolution of X approaches", "X state of the art 2024 2025"
|
||||
- Try failure analysis: "X project failure", "X post-mortem", "X recall", "X incident report"
|
||||
- Try disqualifier probes: "X unsupported", "X limitations", "X requirements", "X with [project constraint]", "X without [required input]", "X real-time [target]", "X production failure"
|
||||
|
||||
**Search saturation rule**: Continue searching until new queries stop producing substantially new information. If the last 3 searches only repeat previously found facts, the sub-question is saturated.
|
||||
|
||||
**Save action**:
|
||||
For each source consulted, **immediately** append to `01_source_registry.md` (or the appropriate category file under `01_source_registry/` if split) using the entry template from `references/source-tiering.md`.
|
||||
For each source consulted, **immediately** append to `01_source_registry.md` using the entry template from `references/source-tiering.md`.
|
||||
|
||||
---
|
||||
|
||||
@@ -273,7 +185,7 @@ Transform sources into **verifiable fact cards**:
|
||||
- ❓ Low: Inference or from unofficial sources
|
||||
|
||||
**Save action**:
|
||||
For each extracted fact, **immediately** append to `02_fact_cards.md` (or the appropriate category file under `02_fact_cards/` if split):
|
||||
For each extracted fact, **immediately** append to `02_fact_cards.md`:
|
||||
```markdown
|
||||
## Fact #[number]
|
||||
- **Statement**: [specific fact description]
|
||||
@@ -282,7 +194,6 @@ For each extracted fact, **immediately** append to `02_fact_cards.md` (or the ap
|
||||
- **Target Audience**: [which group this fact applies to, inherited from source or further refined]
|
||||
- **Confidence**: ✅/⚠️/❓
|
||||
- **Related Dimension**: [corresponding comparison dimension]
|
||||
- **Fit Impact**: [supports selection / disqualifies / makes experimental / needs user decision]
|
||||
```
|
||||
|
||||
**Target audience in fact statements**:
|
||||
@@ -318,7 +229,7 @@ After initial fact extraction, review what you have found and identify **knowled
|
||||
- Failure cases and edge conditions
|
||||
- Recent developments that may change the picture
|
||||
|
||||
4. **Update artifacts**: Append new sources to `01_source_registry.md`, new facts to `02_fact_cards.md` (use the appropriate category files under `01_source_registry/` and `02_fact_cards/` if split)
|
||||
4. **Update artifacts**: Append new sources to `01_source_registry.md`, new facts to `02_fact_cards.md`
|
||||
|
||||
**Exit criteria**: Proceed to Step 4 when:
|
||||
- Every sub-question has at least 3 facts with at least one from L1/L2
|
||||
|
||||
@@ -24,18 +24,6 @@ Write to `03_comparison_framework.md`:
|
||||
| ... | | | |
|
||||
```
|
||||
|
||||
**Required exact-fit dimensions for component/tool decisions**:
|
||||
When the output selects or recommends a component, tool, library, service, architecture pattern, or algorithm, the framework MUST include these dimensions unless explicitly not applicable:
|
||||
- Option family (`Simple baseline`, `Established production`, `Open-source`, `Commercial/vendor`, `Current SOTA`, `Adjacent-domain`, `No-build/defer`, `Known bad`)
|
||||
- Required inputs/outputs and ownership boundaries
|
||||
- Operating context and lifecycle fit
|
||||
- Non-functional envelope fit
|
||||
- Implementation assumptions and hard disqualifiers
|
||||
- Evidence quality and source tier
|
||||
- Selection status (`Selected`, `Rejected`, `Experimental only`, `Needs user decision`)
|
||||
|
||||
For each component area, include multiple candidates in the initial population. Do not present only the preferred option unless the investigation found no realistic alternatives; if so, state the searches that proved the narrow landscape.
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Reference Point Baseline Alignment
|
||||
@@ -109,8 +97,6 @@ Validate conclusions against a typical scenario:
|
||||
- [ ] Are there any important dimensions missed?
|
||||
- [ ] Is there any over-extrapolation?
|
||||
- [ ] Are conclusions actionable/verifiable?
|
||||
- [ ] Does every selected component/tool/pattern match the Project Constraint Matrix?
|
||||
- [ ] Are mismatches marked as disqualifiers instead of hidden as generic "limitations"?
|
||||
|
||||
**Save action**:
|
||||
Write to `05_validation_log.md`:
|
||||
@@ -142,66 +128,6 @@ If using Y: [expected behavior]
|
||||
|
||||
---
|
||||
|
||||
### Step 7.5: Component Applicability Gate (BLOCKING)
|
||||
|
||||
**Applicability**: this gate applies only when the run is classified as **Technical-component selection** in the SKILL's Research Output Class section. For non-technical research (concept comparison, market/policy investigation, root-cause analysis without tooling, knowledge organization), skip this entire step and proceed to Step 8 — there are no components to gate. State the skip once in `05_validation_log.md`: `Step 7.5 (Component Applicability Gate): not applicable — Non-technical investigation`. For mixed runs (some component areas technical, some not), apply this gate only to the technical component areas; the non-technical ones do not produce 7.5 rows.
|
||||
|
||||
Before finalizing the solution draft, build an exact-fit matrix for every component/tool/library/service/pattern/algorithm that is selected, recommended, rejected, or treated as a fallback. Free-form prose in a "Project Constraints Checked" column is **not sufficient** — mismatches hide inside rationale text. The matrix must be structured per restriction and per acceptance criterion.
|
||||
|
||||
#### 7.5.1 Top-level Component Fit Matrix
|
||||
|
||||
```markdown
|
||||
# Component Fit Matrix
|
||||
|
||||
| Component Area | Candidate | Pinned Mode/Config | Option Family | Intended Role | API Capability Evidence | Mismatches / Disqualifiers | Status | Decision Rationale |
|
||||
|----------------|-----------|--------------------|---------------|---------------|-------------------------|----------------------------|--------|--------------------|
|
||||
| [area] | [name] | [exact mode/config the project will use, copied verbatim from the MVE block in Step 2] | [family] | [role] | MVE: [link to MVE block in `02_fact_cards.md` / `02_fact_cards/` or `02_mve_evidence.md`]; docs: [Source #] | [none / list] | Selected / Rejected / Experimental only / Needs user decision | [why] |
|
||||
```
|
||||
|
||||
The new **Pinned Mode/Config** column is mandatory. A row without a pinned mode is incomplete. The new **API Capability Evidence** column links to the Minimum Viable Example saved during Step 2's API Capability Verification — without an MVE link the candidate cannot be `Selected`.
|
||||
|
||||
#### 7.5.2 Restrictions × Candidate-Modes Sub-Matrix (MANDATORY)
|
||||
|
||||
For each lead candidate row in the top-level matrix, append a structured cross-check that walks every numbered line of `restrictions.md` and `acceptance_criteria.md` against the candidate's **pinned mode/config**.
|
||||
|
||||
```markdown
|
||||
## Sub-Matrix — <Candidate Name> in <Pinned Mode>
|
||||
|
||||
| Restriction / AC | Candidate-mode behavior | Result | Evidence |
|
||||
|------------------|-------------------------|--------|----------|
|
||||
| R1: <verbatim line from restrictions.md> | <how the pinned mode behaves under this restriction> | ✅ Pass / ❌ Fail / ❓ Verify / N/A | [Fact # / Source # / MVE link] |
|
||||
| R2: ... | ... | ... | ... |
|
||||
| ... | ... | ... | ... |
|
||||
| AC-1.1: <verbatim line from acceptance_criteria.md> | <how the pinned mode satisfies (or contradicts) this AC's measurable target> | ✅ / ❌ / ❓ / N/A | [Fact # / Source # / MVE link] |
|
||||
| AC-1.2: ... | ... | ... | ... |
|
||||
| ... | ... | ... | ... |
|
||||
```
|
||||
|
||||
Cell semantics:
|
||||
- ✅ **Pass** — the candidate's pinned mode satisfies this line, with cited official-doc or MVE evidence.
|
||||
- ❌ **Fail** — the candidate's pinned mode contradicts this line, with cited evidence. Even one ❌ disqualifies the candidate from `Selected` status.
|
||||
- ❓ **Verify** — no evidence yet either way; further investigation required (loops back to Step 2 / Step 3.5). A row left ❓ at the end of analysis blocks the candidate.
|
||||
- **N/A** — the line is irrelevant to this component area (state why in one phrase).
|
||||
|
||||
A candidate row may not be marked `Selected` while any cell is ❌ or ❓.
|
||||
|
||||
#### 7.5.3 Decision Rules
|
||||
|
||||
- `Selected` is allowed only when (a) the top-level row has an MVE link, (b) the sub-matrix has zero ❌, (c) the sub-matrix has zero ❓, and (d) the candidate's documented implementation assumptions match the project's explicit constraints and acceptance criteria.
|
||||
- `Experimental only` is required when a candidate might work but lacks proof for the exact operating context (e.g., MVE exists for a similar configuration but not the exact one).
|
||||
- `Rejected` is required when documented assumptions conflict with project constraints (any sub-matrix row is ❌ with cited evidence).
|
||||
- `Needs user decision` is required when a mismatch changes scope, cost, safety, product behavior, or acceptance criteria — and the user has not yet been consulted.
|
||||
- Each component area must include at least one selected or fallback-safe option, plus the most credible rejected/experimental alternatives discovered during web research.
|
||||
- A component area with only one candidate is incomplete unless `00_question_decomposition.md` documents the broader searches and why they yielded no realistic alternatives.
|
||||
- A candidate may not appear as the lead solution in Step 8 unless this gate marks it `Selected`.
|
||||
- "Validation gate required" footnotes are not equivalent to `Selected`. If the validation gate concerns API capability (does the mode produce the required output?), that is a Step-2 / Step-7.5 question and must be resolved here, not deferred to runtime. Only validation gates concerning *runtime quality* (e.g., "does this VO converge on this terrain class?") may be carried forward as `Selected with runtime gate`.
|
||||
|
||||
**Save action**: Write `06_component_fit_matrix.md` (or, when split, the equivalent files under `06_component_fit_matrix/` — typically `00_summary.md` for the top-level matrix plus per-component sub-matrix files) containing both 7.5.1 (top-level) and 7.5.2 (per-candidate sub-matrices).
|
||||
|
||||
**BLOCKING**: If any lead candidate has ❌, ❓, `Experimental only`, `Rejected`, or `Needs user decision` status, do not silently proceed. Ask the user or choose a different selected candidate.
|
||||
|
||||
---
|
||||
|
||||
### Step 8: Deliverable Formatting
|
||||
|
||||
Make the output **readable, traceable, and actionable**.
|
||||
@@ -213,8 +139,8 @@ Integrate all intermediate artifacts. Write to `OUTPUT_DIR/solution_draft##.md`
|
||||
|
||||
Sources to integrate:
|
||||
- Extract background from `00_question_decomposition.md`
|
||||
- Reference key facts from `02_fact_cards.md` (or files under `02_fact_cards/` if split)
|
||||
- Reference key facts from `02_fact_cards.md`
|
||||
- Organize conclusions from `04_reasoning_chain.md`
|
||||
- Generate references from `01_source_registry.md` (or files under `01_source_registry/` if split)
|
||||
- Generate references from `01_source_registry.md`
|
||||
- Supplement with use cases from `05_validation_log.md`
|
||||
- For Mode A: include AC assessment from `00_ac_assessment.md`
|
||||
|
||||
@@ -10,21 +10,12 @@
|
||||
|
||||
[Architecture solution that meets restrictions and acceptance criteria.]
|
||||
|
||||
> **Applicability** — the table columns `Pinned Mode/Config` and `API Capability Evidence` apply only to technical-component runs (per SKILL.md → Research Output Class). For non-technical research outputs (concept comparison, market/policy report, investigation answer), this Architecture section may be replaced with a comparison/analysis section that does not use these columns; or the columns may be marked `N/A` per row when the row describes a non-technical "component" (a process, a policy, an organizational construct). For mixed runs, fill the columns only on rows that describe libraries/SDKs/frameworks/services/protocols/data formats/algorithms.
|
||||
|
||||
### Component: [Component Name]
|
||||
|
||||
| Solution | Tools | Pinned Mode/Config | Advantages | Limitations | Requirements | Security | Cost | API Capability Evidence | Fit |
|
||||
|----------|-------|--------------------|-----------|-------------|-------------|----------|------|-------------------------|-----|
|
||||
| [Option 1] | [lib/platform] | [exact mode/config used: inputs, outputs, runtime] | [pros] | [cons] | [intrinsic requirements] | [security] | [cost] | MVE: [link to MVE block]; docs: [Source #] | [Selected / Rejected / Experimental only / Needs user decision — cite exact-fit evidence and disqualifiers] |
|
||||
| [Option 2] | [lib/platform] | [exact mode/config used] | [pros] | [cons] | [intrinsic requirements] | [security] | [cost] | MVE: [link]; docs: [Source #] | [Selected / Rejected / Experimental only / Needs user decision] |
|
||||
|
||||
**Exact-fit evidence**:
|
||||
- Project constraints checked: [inputs/outputs, operating context, lifecycle, NFRs, acceptance criteria]
|
||||
- Evidence: [Fact # / Source #]
|
||||
- Disqualifiers: [none or list]
|
||||
- Restrictions × Candidate-Modes sub-matrix: see `06_component_fit_matrix.md` (or `06_component_fit_matrix/` if split) § <Candidate Name>
|
||||
- API capability gates: ✅ MVE saved / ⚠️ partial — see disqualifiers / ❌ no MVE — candidate is Experimental only or Rejected
|
||||
| Solution | Tools | Advantages | Limitations | Requirements | Security | Cost | Fit |
|
||||
|----------|-------|-----------|-------------|-------------|----------|------|-----|
|
||||
| [Option 1] | [lib/platform] | [pros] | [cons] | [reqs] | [security] | [cost] | [fit assessment] |
|
||||
| [Option 2] | [lib/platform] | [pros] | [cons] | [reqs] | [security] | [cost] | [fit assessment] |
|
||||
|
||||
[Repeat per component]
|
||||
|
||||
|
||||
@@ -13,21 +13,12 @@
|
||||
|
||||
[Architecture solution that meets restrictions and acceptance criteria.]
|
||||
|
||||
> **Applicability** — the table columns `Pinned Mode/Config` and `API Capability Evidence` apply only to technical-component runs (per SKILL.md → Research Output Class). For non-technical assessment outputs (e.g., reassessing a policy approach, comparing organizational designs), this Architecture section may be replaced with the assessment content that does not use these columns; or the columns may be marked `N/A` per row for non-technical "components". For mixed runs, fill the columns only on rows that describe libraries/SDKs/frameworks/services/protocols/data formats/algorithms.
|
||||
|
||||
### Component: [Component Name]
|
||||
|
||||
| Solution | Tools | Pinned Mode/Config | Advantages | Limitations | Requirements | Security | Performance | API Capability Evidence | Fit |
|
||||
|----------|-------|--------------------|-----------|-------------|-------------|----------|------------|-------------------------|-----|
|
||||
| [Option 1] | [lib/platform] | [exact mode/config used: inputs, outputs, runtime] | [pros] | [cons] | [intrinsic requirements] | [security] | [perf] | MVE: [link to MVE block]; docs: [Source #] | [Selected / Rejected / Experimental only / Needs user decision — cite exact-fit evidence and disqualifiers] |
|
||||
| [Option 2] | [lib/platform] | [exact mode/config used] | [pros] | [cons] | [intrinsic requirements] | [security] | [perf] | MVE: [link]; docs: [Source #] | [Selected / Rejected / Experimental only / Needs user decision] |
|
||||
|
||||
**Exact-fit evidence**:
|
||||
- Project constraints checked: [inputs/outputs, operating context, lifecycle, NFRs, acceptance criteria]
|
||||
- Evidence: [Fact # / Source #]
|
||||
- Disqualifiers: [none or list]
|
||||
- Restrictions × Candidate-Modes sub-matrix: see `06_component_fit_matrix.md` (or `06_component_fit_matrix/` if split) § <Candidate Name>
|
||||
- API capability gates: ✅ MVE saved / ⚠️ partial — see disqualifiers / ❌ no MVE — candidate is Experimental only or Rejected
|
||||
| Solution | Tools | Advantages | Limitations | Requirements | Security | Performance | Fit |
|
||||
|----------|-------|-----------|-------------|-------------|----------|------------|-----|
|
||||
| [Option 1] | [lib/platform] | [pros] | [cons] | [reqs] | [security] | [perf] | [fit assessment] |
|
||||
| [Option 2] | [lib/platform] | [pros] | [cons] | [reqs] | [security] | [perf] | [fit assessment] |
|
||||
|
||||
[Repeat per component]
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ test-run has two modes. The caller passes the mode explicitly; if missing, defau
|
||||
| Mode | Scope | Typical caller | Input artifacts |
|
||||
|------|-------|---------------|-----------------|
|
||||
| `functional` (default) | Unit / integration / blackbox tests — correctness | autodev Steps that verify after Implement Tests or Implement | `scripts/run-tests.sh`, `_docs/02_document/tests/environment.md`, `_docs/02_document/tests/blackbox-tests.md` |
|
||||
| `perf` | Performance / load / stress / soak tests — latency, throughput, error-rate thresholds | autodev greenfield Step 15, existing-code Step 15 (pre-deploy) | `scripts/run-performance-tests.sh`, `_docs/02_document/tests/performance-tests.md`, AC thresholds in `_docs/00_problem/acceptance_criteria.md` |
|
||||
| `perf` | Performance / load / stress / soak tests — latency, throughput, error-rate thresholds | autodev greenfield Step 9, existing-code Step 15 (pre-deploy) | `scripts/run-performance-tests.sh`, `_docs/02_document/tests/performance-tests.md`, AC thresholds in `_docs/00_problem/acceptance_criteria.md` |
|
||||
|
||||
Direct user invocation (`/test-run`) defaults to `functional`. If the user says "perf tests", "load test", "performance", or passes a performance scenarios file, run `perf` mode.
|
||||
|
||||
@@ -32,17 +32,6 @@ After selecting a mode, read its corresponding workflow below; do not mix them.
|
||||
|
||||
## Functional Mode
|
||||
|
||||
### 0. System-Under-Test Reality Gate
|
||||
|
||||
Before accepting any functional, blackbox, or e2e result as a pass, verify what the tests actually exercised.
|
||||
|
||||
1. If `_docs/00_problem/input_data/expected_results/results_report.md` exists, at least one e2e/blackbox run must compare actual product outputs against that mapping or the machine-readable files it references.
|
||||
2. Stubs are allowed only for external systems outside the product boundary: flight controller/SITL, QGC observer, satellite-provider/Suite service, physical Jetson hardware, physical camera, unavailable licensed datasets, and network services.
|
||||
3. Stubs, fakes, deterministic fallbacks, monkeypatches, or direct replacement of internal product modules are not allowed for the behavior under test. Internal examples include VIO, safety/anchor wrapper, satellite retrieval, anchor verification, tile manager, MAVLink output adapter, FDR, and the A-Z localization pipeline.
|
||||
4. If tests pass only because an internal module is fake/scaffolded, classify the run as **failed** with category `missing product implementation`.
|
||||
5. If a scenario is blocked because external hardware/data is absent, verify the production code path exists before accepting the block as legitimate. Missing internal production code is not an environment block.
|
||||
6. If the test runner writes CSV/Markdown reports, inspect them. A zero exit code is not enough; blocked/internal-stubbed scenarios still require classification.
|
||||
|
||||
### 1. Detect Test Runner
|
||||
|
||||
Check in order — first match wins:
|
||||
@@ -105,7 +94,7 @@ Categorize skips as: **explicit skip (dead code)**, **runtime skip (unreachable)
|
||||
|
||||
### 5. Handle Outcome
|
||||
|
||||
**All tests pass, zero skipped, and the System-Under-Test Reality Gate passes** → return success to the autodev for auto-chain.
|
||||
**All tests pass, zero skipped** → return success to the autodev for auto-chain.
|
||||
|
||||
**Any test fails or errors** → this is a **blocking gate**. Never silently ignore failures. **Always investigate the root cause before deciding on an action.** Read the failing test code, read the error output, check service logs if applicable, and determine whether the bug is in the test or in the production code.
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ Examples:
|
||||
|
||||
File: `expected_results/image_01_detections.json`
|
||||
|
||||
```json
|
||||
```json
|
||||
{
|
||||
"input": "image_01.jpg",
|
||||
"expected": {
|
||||
@@ -119,7 +119,7 @@ File: `expected_results/image_01_detections.json`
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# =============================================================================
|
||||
# Azaion Admin API — environment variable template
|
||||
# Copy to `.env` (git-ignored) and fill in real values for your environment.
|
||||
# Production secrets MUST come from the secret manager, not from a checked-in
|
||||
# file. See _docs/04_deploy/reports/deploy_status_report.md for the full table.
|
||||
# =============================================================================
|
||||
|
||||
# ---------- ASP.NET Core runtime --------------------------------------------
|
||||
ASPNETCORE_ENVIRONMENT=Development # Development | Staging | Production
|
||||
ASPNETCORE_URLS=http://+:8080 # Kestrel bind address inside the container
|
||||
|
||||
# ---------- Database (PostgreSQL on port 4312 in prod, 5432 in test) --------
|
||||
# Two roles: reader (read-only) and admin (read/write). See env/db/01_permissions.sql.
|
||||
ASPNETCORE_ConnectionStrings__AzaionDb=Host=localhost;Port=4312;Database=azaion;Username=azaion_reader;Password=CHANGE_ME
|
||||
ASPNETCORE_ConnectionStrings__AzaionDbAdmin=Host=localhost;Port=4312;Database=azaion;Username=azaion_admin;Password=CHANGE_ME
|
||||
|
||||
# ---------- JWT (HMAC-SHA256, 4 h TTL) --------------------------------------
|
||||
ASPNETCORE_JwtConfig__Secret=CHANGE_ME_TO_A_RANDOM_STRING_AT_LEAST_32_BYTES
|
||||
ASPNETCORE_JwtConfig__Issuer=AzaionApi
|
||||
ASPNETCORE_JwtConfig__Audience=Annotators/OrangePi/Admins
|
||||
ASPNETCORE_JwtConfig__TokenLifetimeHours=4
|
||||
|
||||
# ---------- Resource storage (filesystem) -----------------------------------
|
||||
ASPNETCORE_ResourcesConfig__ResourcesFolder=Content
|
||||
ASPNETCORE_ResourcesConfig__SuiteInstallerFolder=suite
|
||||
ASPNETCORE_ResourcesConfig__SuiteStageInstallerFolder=suite-stage
|
||||
|
||||
# ---------- Container build / image label ------------------------------------
|
||||
# Injected at build time as --build-arg CI_COMMIT_SHA=… by Woodpecker.
|
||||
# Local builds may leave it unset (Dockerfile defaults to "unknown").
|
||||
# CI_COMMIT_SHA=
|
||||
|
||||
# ---------- Deploy targets (consumed by scripts/, not by the API process) ---
|
||||
DEPLOY_HOST=admin.azaion.com # SSH target for scripts/deploy.sh
|
||||
DEPLOY_SSH_USER=root # SSH user on DEPLOY_HOST
|
||||
DEPLOY_CONTAINER_NAME=azaion.api # Docker container name on the host
|
||||
DEPLOY_HOST_PORT=4000 # Port published on DEPLOY_HOST (mapped to 8080 in container)
|
||||
DEPLOY_HOST_CONTENT_DIR=/root/api/content # Bind-mount for resource files
|
||||
DEPLOY_HOST_LOGS_DIR=/root/api/logs # Bind-mount for Serilog rolling files
|
||||
|
||||
# ---------- Container registry ----------------------------------------------
|
||||
REGISTRY_HOST=docker.azaion.com # Private registry; CI may use localhost:5000
|
||||
REGISTRY_IMAGE=azaion/admin # Image path inside REGISTRY_HOST
|
||||
REGISTRY_TAG=dev-arm # main→arm, stage→stage-arm, dev→dev-arm
|
||||
REGISTRY_USER= # CI / scripts only — leave empty in dev .env
|
||||
REGISTRY_TOKEN= # CI / scripts only — leave empty in dev .env
|
||||
@@ -1,54 +0,0 @@
|
||||
when:
|
||||
event: [push, pull_request, manual]
|
||||
branch: [dev, stage, main]
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- PLATFORM: arm64
|
||||
TAG_SUFFIX: arm
|
||||
# - PLATFORM: amd64
|
||||
# TAG_SUFFIX: amd
|
||||
|
||||
labels:
|
||||
platform: ${PLATFORM}
|
||||
|
||||
steps:
|
||||
- name: lint-format
|
||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
||||
commands:
|
||||
- dotnet format Azaion.AdminApi.sln --verify-no-changes --verbosity diagnostic
|
||||
|
||||
- name: unit-tests
|
||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
||||
commands:
|
||||
- dotnet restore Azaion.AdminApi.sln
|
||||
- dotnet test Azaion.AdminApi.sln --no-restore --configuration Release --logger "console;verbosity=normal" --logger "trx;LogFileName=test-results.trx" --results-directory /app/test-results
|
||||
|
||||
- name: deps-audit
|
||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
||||
commands:
|
||||
# Security audit recommendation 13: fail the build on any High or Critical
|
||||
# vulnerable dependency. The grep returns non-zero when no match is found,
|
||||
# which we want to treat as success — hence the explicit inversion.
|
||||
- dotnet restore Azaion.AdminApi.sln
|
||||
- dotnet list Azaion.AdminApi.sln package --vulnerable --include-transitive 2>&1 | tee deps-audit.log
|
||||
- if grep -E "^\s+>\s+\S+\s+\S+\s+\S+\s+(High|Critical)\s*$" deps-audit.log; then echo "Vulnerable High/Critical dependency found"; exit 1; fi
|
||||
|
||||
- name: e2e-tests
|
||||
image: docker
|
||||
commands:
|
||||
# Mirrors scripts/run-tests.sh: drop volumes from any prior run so the DB
|
||||
# init scripts re-run on a clean data dir, then run compose to completion.
|
||||
- docker compose -f docker-compose.test.yml down -v --remove-orphans
|
||||
- docker compose -f docker-compose.test.yml up --build --abort-on-container-exit --exit-code-from e2e-consumer
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
- name: e2e-cleanup
|
||||
image: docker
|
||||
when:
|
||||
status: [success, failure]
|
||||
commands:
|
||||
- docker compose -f docker-compose.test.yml down -v --remove-orphans
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -1,53 +0,0 @@
|
||||
when:
|
||||
event: [push, manual]
|
||||
branch: [dev, stage, main]
|
||||
|
||||
depends_on:
|
||||
- 01-test
|
||||
|
||||
# Multi-arch matrix. Adding amd64 = uncommenting the second entry once an
|
||||
# amd64 agent is online.
|
||||
matrix:
|
||||
include:
|
||||
- PLATFORM: arm64
|
||||
TAG_SUFFIX: arm
|
||||
# - PLATFORM: amd64
|
||||
# TAG_SUFFIX: amd
|
||||
|
||||
labels:
|
||||
platform: ${PLATFORM}
|
||||
|
||||
steps:
|
||||
- name: build-push
|
||||
image: docker
|
||||
environment:
|
||||
REGISTRY_HOST:
|
||||
from_secret: registry_host
|
||||
REGISTRY_USER:
|
||||
from_secret: registry_user
|
||||
REGISTRY_TOKEN:
|
||||
from_secret: registry_token
|
||||
commands:
|
||||
- echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
|
||||
- export BRANCH_TAG=${CI_COMMIT_BRANCH}-${TAG_SUFFIX}
|
||||
# 12-char SHA prefix is human-readable while still globally-unique inside
|
||||
# the repo. Pair with TAG_SUFFIX so multi-arch entries don't collide.
|
||||
- export SHA_TAG=$(echo "$CI_COMMIT_SHA" | cut -c1-12)-${TAG_SUFFIX}
|
||||
- export BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
- export IMAGE=$REGISTRY_HOST/azaion/admin
|
||||
- |
|
||||
docker build -f Dockerfile \
|
||||
--build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA \
|
||||
--build-arg BUILD_DATE=$BUILD_DATE \
|
||||
--label org.opencontainers.image.revision=$CI_COMMIT_SHA \
|
||||
--label org.opencontainers.image.created=$BUILD_DATE \
|
||||
--label org.opencontainers.image.source=$CI_REPO_URL \
|
||||
-t $IMAGE:$BRANCH_TAG \
|
||||
-t $IMAGE:$SHA_TAG .
|
||||
# Mutable branch tag for "give me whatever's latest on dev" pulls.
|
||||
- docker push $IMAGE:$BRANCH_TAG
|
||||
# Immutable SHA tag — the deploy scripts pin to this and rollback uses it.
|
||||
- docker push $IMAGE:$SHA_TAG
|
||||
- echo "Pushed $IMAGE:$BRANCH_TAG and $IMAGE:$SHA_TAG"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -0,0 +1,31 @@
|
||||
when:
|
||||
event: [push, manual]
|
||||
branch: [dev, stage, main]
|
||||
|
||||
labels:
|
||||
platform: arm64
|
||||
|
||||
steps:
|
||||
- name: build-push
|
||||
image: docker
|
||||
environment:
|
||||
REGISTRY_HOST:
|
||||
from_secret: registry_host
|
||||
REGISTRY_USER:
|
||||
from_secret: registry_user
|
||||
REGISTRY_TOKEN:
|
||||
from_secret: registry_token
|
||||
commands:
|
||||
- echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
|
||||
- export TAG=${CI_COMMIT_BRANCH}-arm
|
||||
- export BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
- |
|
||||
docker build -f Dockerfile \
|
||||
--build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA \
|
||||
--label org.opencontainers.image.revision=$CI_COMMIT_SHA \
|
||||
--label org.opencontainers.image.created=$BUILD_DATE \
|
||||
--label org.opencontainers.image.source=$CI_REPO_URL \
|
||||
-t $REGISTRY_HOST/azaion/admin:$TAG .
|
||||
- docker push $REGISTRY_HOST/azaion/admin:$TAG
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docker", "Docker", "{49FBE4
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Dockerfile = Dockerfile
|
||||
.dockerignore = .dockerignore
|
||||
deploy.cmd = deploy.cmd
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
|
||||
+23
-88
@@ -6,7 +6,6 @@ using Azaion.Common.Entities;
|
||||
using Azaion.Common.Requests;
|
||||
using Azaion.Services;
|
||||
using FluentValidation;
|
||||
using LinqToDB.Data;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -34,17 +33,6 @@ if (jwtConfig == null || string.IsNullOrEmpty(jwtConfig.Secret))
|
||||
throw new Exception("Missing configuration section: JwtConfig");
|
||||
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtConfig.Secret));
|
||||
|
||||
// Fail-fast for DB connection strings — surfaces a missing env var at startup
|
||||
// instead of on the first request to a DB-backed endpoint.
|
||||
var connectionStrings = builder.Configuration.GetSection(nameof(ConnectionStrings)).Get<ConnectionStrings>();
|
||||
if (connectionStrings == null
|
||||
|| string.IsNullOrEmpty(connectionStrings.AzaionDb)
|
||||
|| string.IsNullOrEmpty(connectionStrings.AzaionDbAdmin))
|
||||
throw new Exception("Missing configuration section: ConnectionStrings (AzaionDb and AzaionDbAdmin are required)");
|
||||
|
||||
// Graceful shutdown: 30 s for in-flight requests; pair with `docker stop -t 40`.
|
||||
builder.Services.Configure<HostOptions>(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
|
||||
|
||||
builder.Services.AddSerilog();
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(o =>
|
||||
@@ -66,9 +54,13 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
var apiAdminPolicy = new AuthorizationPolicyBuilder()
|
||||
.RequireRole(RoleEnum.ApiAdmin.ToString()).Build();
|
||||
|
||||
var apiUploaderPolicy = new AuthorizationPolicyBuilder()
|
||||
.RequireRole(RoleEnum.ResourceUploader.ToString(), RoleEnum.ApiAdmin.ToString()).Build();
|
||||
|
||||
builder.Services.AddAuthorization(o =>
|
||||
{
|
||||
o.AddPolicy(nameof(apiAdminPolicy), apiAdminPolicy);
|
||||
o.AddPolicy(nameof(apiUploaderPolicy), apiUploaderPolicy);
|
||||
});
|
||||
|
||||
#endregion Policies
|
||||
@@ -105,7 +97,6 @@ builder.Services.Configure<ConnectionStrings>(builder.Configuration.GetSection(n
|
||||
builder.Services.AddScoped<IUserService, UserService>();
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
builder.Services.AddScoped<IResourcesService, ResourcesService>();
|
||||
builder.Services.AddScoped<IDetectionClassService, DetectionClassService>();
|
||||
builder.Services.AddSingleton<IDbFactory, DbFactory>();
|
||||
|
||||
builder.Services.AddLazyCache();
|
||||
@@ -141,39 +132,6 @@ app.UseAuthorization();
|
||||
|
||||
app.UseRewriter(new RewriteOptions().AddRedirect("^$", "/swagger"));
|
||||
|
||||
#region Health endpoints
|
||||
// Anonymous; expected to be exposed only on the management interface (not via the
|
||||
// public Nginx vhost). Surface contract documented in
|
||||
// _docs/04_deploy/deployment_procedures.md §2 and observability.md §7.
|
||||
|
||||
app.MapGet("/health/live", (HttpContext http) =>
|
||||
{
|
||||
http.Response.Headers.CacheControl = "no-store";
|
||||
return Results.Ok(new { status = "live" });
|
||||
}).AllowAnonymous().ExcludeFromDescription();
|
||||
|
||||
app.MapGet("/health/ready", async (IDbFactory dbFactory, HttpContext http, CancellationToken ct) =>
|
||||
{
|
||||
http.Response.Headers.CacheControl = "no-store";
|
||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
timeoutCts.CancelAfter(TimeSpan.FromSeconds(2));
|
||||
try
|
||||
{
|
||||
await dbFactory.Run(db => db.ExecuteAsync<int>("SELECT 1"));
|
||||
await dbFactory.RunAdmin(db => db.ExecuteAsync<int>("SELECT 1"));
|
||||
return Results.Ok(new { status = "ready" });
|
||||
}
|
||||
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !ct.IsCancellationRequested)
|
||||
{
|
||||
return Results.Json(new { status = "not-ready", reason = "db-timeout" }, statusCode: 503);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Json(new { status = "not-ready", reason = ex.GetType().Name }, statusCode: 503);
|
||||
}
|
||||
}).AllowAnonymous().ExcludeFromDescription();
|
||||
#endregion Health endpoints
|
||||
|
||||
app.MapPost("/login",
|
||||
async (LoginRequest request, IUserService userService, IAuthService authService, CancellationToken cancellationToken) =>
|
||||
{
|
||||
@@ -195,12 +153,6 @@ app.MapPost("/users",
|
||||
.RequireAuthorization(apiAdminPolicy)
|
||||
.WithSummary("Creates a new user");
|
||||
|
||||
app.MapPost("/devices",
|
||||
async (IUserService userService, CancellationToken cancellationToken)
|
||||
=> await userService.RegisterDevice(cancellationToken))
|
||||
.RequireAuthorization(apiAdminPolicy)
|
||||
.WithSummary("Creates a new device (server-assigned serial, email and password)");
|
||||
|
||||
app.MapGet("/users/current",
|
||||
async (IAuthService authService) => await authService.GetCurrentUser())
|
||||
.RequireAuthorization()
|
||||
@@ -212,6 +164,12 @@ app.MapGet("/users",
|
||||
.RequireAuthorization(apiAdminPolicy)
|
||||
.WithSummary("List users by criteria");
|
||||
|
||||
app.MapPut("/users/hardware/set",
|
||||
async ([FromBody]SetHWRequest request, IUserService userService, ICache cache, CancellationToken ct) =>
|
||||
await userService.UpdateHardware(request.Email, request.Hardware, ct: ct))
|
||||
.RequireAuthorization(apiAdminPolicy)
|
||||
.WithSummary("Sets user's hardware");
|
||||
|
||||
app.MapPut("/users/queue-offsets/set",
|
||||
async ([FromBody]SetUserQueueOffsetsRequest request, IUserService userService, CancellationToken ct)
|
||||
=> await userService.UpdateQueueOffsets(request.Email, request.Offsets, ct))
|
||||
@@ -263,18 +221,20 @@ app.MapPost("/resources/clear/{dataFolder?}",
|
||||
|
||||
app.MapPost("/resources/get/{dataFolder?}", //Need to have POST method for secure password
|
||||
async ([FromBody]GetResourceRequest request, [FromRoute]string? dataFolder, IAuthService authService,
|
||||
IResourcesService resourcesService, CancellationToken ct) =>
|
||||
IUserService userService, IResourcesService resourcesService, CancellationToken ct) =>
|
||||
{
|
||||
var user = await authService.GetCurrentUser();
|
||||
if (user == null)
|
||||
throw new UnauthorizedAccessException();
|
||||
|
||||
var key = Security.GetApiEncryptionKey(user.Email, request.Password);
|
||||
var hwHash = await userService.CheckHardwareHash(user, request.Hardware);
|
||||
|
||||
var key = Security.GetApiEncryptionKey(user.Email, request.Password, hwHash);
|
||||
var stream = await resourcesService.GetEncryptedResource(dataFolder, request.FileName, key, ct);
|
||||
|
||||
return Results.File(stream, "application/octet-stream", request.FileName);
|
||||
}).RequireAuthorization()
|
||||
.WithSummary("Gets encrypted by user's Password resource. POST method for secure password");
|
||||
.WithSummary("Gets encrypted by users Password and HardwareHash resources. POST method for secure password");
|
||||
|
||||
app.MapGet("/resources/get-installer",
|
||||
async (IAuthService authService, IResourcesService resourcesService, CancellationToken ct) =>
|
||||
@@ -303,40 +263,15 @@ app.MapGet("/resources/get-installer/stage",
|
||||
.WithSummary("Gets latest installer");
|
||||
|
||||
|
||||
app.MapPost("/classes",
|
||||
async (CreateDetectionClassRequest request, IValidator<CreateDetectionClassRequest> validator,
|
||||
IDetectionClassService detectionClassService, CancellationToken ct) =>
|
||||
app.MapPost("/resources/check",
|
||||
async (CheckResourceRequest request, IAuthService authService, IUserService userService) =>
|
||||
{
|
||||
var validation = await validator.ValidateAsync(request, ct);
|
||||
if (!validation.IsValid)
|
||||
return Results.ValidationProblem(validation.ToDictionary());
|
||||
var created = await detectionClassService.Create(request, ct);
|
||||
return Results.Ok(created);
|
||||
})
|
||||
.RequireAuthorization(apiAdminPolicy)
|
||||
.WithSummary("Creates a new detection class");
|
||||
|
||||
app.MapPatch("/classes/{id:int}",
|
||||
async (int id, UpdateDetectionClassRequest request, IValidator<UpdateDetectionClassRequest> validator,
|
||||
IDetectionClassService detectionClassService, CancellationToken ct) =>
|
||||
{
|
||||
var validation = await validator.ValidateAsync(request, ct);
|
||||
if (!validation.IsValid)
|
||||
return Results.ValidationProblem(validation.ToDictionary());
|
||||
var updated = await detectionClassService.Update(id, request, ct);
|
||||
return updated == null ? Results.NotFound() : Results.Ok(updated);
|
||||
})
|
||||
.RequireAuthorization(apiAdminPolicy)
|
||||
.WithSummary("Updates an existing detection class (partial-merge accepted)");
|
||||
|
||||
app.MapDelete("/classes/{id:int}",
|
||||
async (int id, IDetectionClassService detectionClassService, CancellationToken ct) =>
|
||||
{
|
||||
var ok = await detectionClassService.Delete(id, ct);
|
||||
return ok ? Results.NoContent() : Results.NotFound();
|
||||
})
|
||||
.RequireAuthorization(apiAdminPolicy)
|
||||
.WithSummary("Deletes a detection class");
|
||||
var user = await authService.GetCurrentUser();
|
||||
if (user == null)
|
||||
throw new UnauthorizedAccessException();
|
||||
await userService.CheckHardwareHash(user, request.Hardware);
|
||||
return true;
|
||||
});
|
||||
|
||||
app.UseExceptionHandler(_ => {});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="11.10.0" />
|
||||
<PackageReference Include="linq2db" Version="5.4.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Npgsql" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -39,6 +39,12 @@ public enum ExceptionEnum
|
||||
[Description("User account is disabled.")]
|
||||
UserDisabled = 38,
|
||||
|
||||
[Description("Hardware mismatch! You are not authorized to access this resource from this hardware.")]
|
||||
HardwareIdMismatch = 40,
|
||||
|
||||
[Description("Hardware should be not empty.")]
|
||||
BadHardware = 45,
|
||||
|
||||
[Description("Wrong resource file name.")]
|
||||
WrongResourceName = 50,
|
||||
|
||||
|
||||
@@ -6,6 +6,5 @@ namespace Azaion.Common.Database;
|
||||
|
||||
public class AzaionDb(DataOptions dataOptions) : DataConnection(dataOptions)
|
||||
{
|
||||
public ITable<User> Users => this.GetTable<User>();
|
||||
public ITable<DetectionClass> DetectionClasses => this.GetTable<DetectionClass>();
|
||||
public ITable<User> Users => this.GetTable<User>();
|
||||
}
|
||||
@@ -36,11 +36,6 @@ public static class AzaionDbSchemaHolder
|
||||
p => string.IsNullOrEmpty(p) ? new UserConfig() : JsonConvert.DeserializeObject<UserConfig>(p))
|
||||
.IsNullable();
|
||||
|
||||
builder.Entity<DetectionClass>()
|
||||
.HasTableName("detection_classes")
|
||||
.Property(x => x.Id)
|
||||
.IsPrimaryKey()
|
||||
.IsIdentity();
|
||||
|
||||
builder.Build();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ public interface IDbFactory
|
||||
Task<T> Run<T>(Func<AzaionDb, Task<T>> func);
|
||||
Task Run(Func<AzaionDb, Task> func);
|
||||
Task RunAdmin(Func<AzaionDb, Task> func);
|
||||
Task<T> RunAdmin<T>(Func<AzaionDb, Task<T>> func);
|
||||
}
|
||||
|
||||
public class DbFactory : IDbFactory
|
||||
@@ -55,10 +54,4 @@ public class DbFactory : IDbFactory
|
||||
await using var db = new AzaionDb(_dataOptionsAdmin);
|
||||
await func(db);
|
||||
}
|
||||
|
||||
public async Task<T> RunAdmin<T>(Func<AzaionDb, Task<T>> func)
|
||||
{
|
||||
await using var db = new AzaionDb(_dataOptionsAdmin);
|
||||
return await func(db);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Azaion.Common.Entities;
|
||||
|
||||
public class DetectionClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
public string ShortName { get; set; } = null!;
|
||||
public string Color { get; set; } = null!;
|
||||
public double MaxSizeM { get; set; }
|
||||
public string? PhotoMode { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Azaion.Common.Requests;
|
||||
|
||||
public class CreateDetectionClassRequest
|
||||
{
|
||||
public string Name { get; set; } = null!;
|
||||
public string ShortName { get; set; } = null!;
|
||||
public string Color { get; set; } = null!;
|
||||
public double MaxSizeM { get; set; }
|
||||
public string? PhotoMode { get; set; }
|
||||
}
|
||||
|
||||
public class CreateDetectionClassValidator : AbstractValidator<CreateDetectionClassRequest>
|
||||
{
|
||||
public CreateDetectionClassValidator()
|
||||
{
|
||||
RuleFor(r => r.Name).NotEmpty().MaximumLength(120);
|
||||
RuleFor(r => r.ShortName).NotEmpty().MaximumLength(20);
|
||||
RuleFor(r => r.Color).NotEmpty().MaximumLength(20);
|
||||
RuleFor(r => r.MaxSizeM).GreaterThan(0);
|
||||
RuleFor(r => r.PhotoMode!).MaximumLength(20).When(r => r.PhotoMode != null);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,15 @@ using FluentValidation;
|
||||
|
||||
namespace Azaion.Common.Requests;
|
||||
|
||||
public class CheckResourceRequest
|
||||
{
|
||||
public string Hardware { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class GetResourceRequest
|
||||
{
|
||||
public string Password { get; set; } = null!;
|
||||
public string Hardware { get; set; } = null!;
|
||||
public string FileName { get; set; } = null!;
|
||||
}
|
||||
|
||||
@@ -17,9 +23,13 @@ public class GetResourceRequestValidator : AbstractValidator<GetResourceRequest>
|
||||
.WithErrorCode(nameof(ExceptionEnum.PasswordLengthIncorrect))
|
||||
.WithMessage(_ => BusinessException.GetMessage(ExceptionEnum.PasswordLengthIncorrect));
|
||||
|
||||
RuleFor(r => r.Hardware)
|
||||
.NotEmpty()
|
||||
.WithMessage(_ => BusinessException.GetMessage(ExceptionEnum.BadHardware));
|
||||
|
||||
RuleFor(r => r.FileName)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(nameof(ExceptionEnum.WrongResourceName))
|
||||
.WithMessage(_ => BusinessException.GetMessage(ExceptionEnum.WrongResourceName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Azaion.Common.Requests;
|
||||
|
||||
public class RegisterDeviceResponse
|
||||
{
|
||||
public string Serial { get; set; } = null!;
|
||||
public string Email { get; set; } = null!;
|
||||
public string Password { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Azaion.Common.Requests;
|
||||
|
||||
public class SetHWRequest
|
||||
{
|
||||
public string Email { get; set; } = null!;
|
||||
public string? Hardware { get; set; }
|
||||
}
|
||||
|
||||
public class SetHWRequestValidator : AbstractValidator<SetHWRequest>
|
||||
{
|
||||
public SetHWRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.Email).NotEmpty()
|
||||
.WithErrorCode(ExceptionEnum.EmailLengthIncorrect.ToString())
|
||||
.WithMessage(_ => BusinessException.GetMessage(ExceptionEnum.EmailLengthIncorrect));
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Azaion.Common.Requests;
|
||||
|
||||
public class UpdateDetectionClassRequest
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? ShortName { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public double? MaxSizeM { get; set; }
|
||||
public string? PhotoMode { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateDetectionClassValidator : AbstractValidator<UpdateDetectionClassRequest>
|
||||
{
|
||||
public UpdateDetectionClassValidator()
|
||||
{
|
||||
RuleFor(r => r.Name!).NotEmpty().MaximumLength(120).When(r => r.Name != null);
|
||||
RuleFor(r => r.ShortName!).NotEmpty().MaximumLength(20).When(r => r.ShortName != null);
|
||||
RuleFor(r => r.Color!).NotEmpty().MaximumLength(20).When(r => r.Color != null);
|
||||
RuleFor(r => r.MaxSizeM!.Value).GreaterThan(0).When(r => r.MaxSizeM != null);
|
||||
RuleFor(r => r.PhotoMode!).MaximumLength(20).When(r => r.PhotoMode != null);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
using Azaion.Common.Database;
|
||||
using Azaion.Common.Entities;
|
||||
using Azaion.Common.Requests;
|
||||
using LinqToDB;
|
||||
|
||||
namespace Azaion.Services;
|
||||
|
||||
public interface IDetectionClassService
|
||||
{
|
||||
Task<DetectionClass> Create(CreateDetectionClassRequest request, CancellationToken ct = default);
|
||||
Task<DetectionClass?> Update(int id, UpdateDetectionClassRequest request, CancellationToken ct = default);
|
||||
Task<bool> Delete(int id, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public class DetectionClassService(IDbFactory dbFactory) : IDetectionClassService
|
||||
{
|
||||
public async Task<DetectionClass> Create(CreateDetectionClassRequest request, CancellationToken ct = default) =>
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
{
|
||||
var entity = new DetectionClass
|
||||
{
|
||||
Name = request.Name,
|
||||
ShortName = request.ShortName,
|
||||
Color = request.Color,
|
||||
MaxSizeM = request.MaxSizeM,
|
||||
PhotoMode = request.PhotoMode,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
var newId = await db.InsertWithInt32IdentityAsync(entity, token: ct);
|
||||
entity.Id = newId;
|
||||
return entity;
|
||||
});
|
||||
|
||||
public async Task<DetectionClass?> Update(int id, UpdateDetectionClassRequest request, CancellationToken ct = default) =>
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
{
|
||||
var existing = await db.DetectionClasses.FirstOrDefaultAsync(x => x.Id == id, token: ct);
|
||||
if (existing == null)
|
||||
return null;
|
||||
|
||||
if (request.Name != null) existing.Name = request.Name;
|
||||
if (request.ShortName != null) existing.ShortName = request.ShortName;
|
||||
if (request.Color != null) existing.Color = request.Color;
|
||||
if (request.MaxSizeM.HasValue) existing.MaxSizeM = request.MaxSizeM.Value;
|
||||
if (request.PhotoMode != null) existing.PhotoMode = request.PhotoMode;
|
||||
|
||||
await db.UpdateAsync(existing, token: ct);
|
||||
return existing;
|
||||
});
|
||||
|
||||
public async Task<bool> Delete(int id, CancellationToken ct = default) =>
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
{
|
||||
var deleted = await db.DetectionClasses.DeleteAsync(x => x.Id == id, token: ct);
|
||||
return deleted > 0;
|
||||
});
|
||||
}
|
||||
@@ -11,8 +11,11 @@ public static class Security
|
||||
public static string ToHash(this string str) =>
|
||||
Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str)));
|
||||
|
||||
public static string GetApiEncryptionKey(string email, string password) =>
|
||||
$"{email}-{password}-#%@AzaionKey@%#---".ToHash();
|
||||
public static string GetHWHash(string hardware) =>
|
||||
$"Azaion_{hardware}_%$$$)0_".ToHash();
|
||||
|
||||
public static string GetApiEncryptionKey(string email, string password, string? hardwareHash) =>
|
||||
$"{email}-{password}-{hardwareHash}-#%@AzaionKey@%#---".ToHash();
|
||||
|
||||
public static async Task EncryptTo(this Stream inputStream, Stream toStream, string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
using System.Security.Cryptography;
|
||||
using Azaion.Common;
|
||||
using Azaion.Common;
|
||||
using Azaion.Common.Database;
|
||||
using Azaion.Common.Entities;
|
||||
using Azaion.Common.Extensions;
|
||||
using Azaion.Common.Requests;
|
||||
using LinqToDB;
|
||||
using Npgsql;
|
||||
|
||||
namespace Azaion.Services;
|
||||
|
||||
public interface IUserService
|
||||
{
|
||||
Task RegisterUser(RegisterUserRequest request, CancellationToken ct = default);
|
||||
Task<RegisterDeviceResponse> RegisterDevice(CancellationToken ct = default);
|
||||
Task<User> ValidateUser(LoginRequest request, CancellationToken ct = default);
|
||||
Task<User?> GetByEmail(string? email, CancellationToken ct = default);
|
||||
Task UpdateHardware(string email, string? hardware = null, CancellationToken ct = default);
|
||||
Task UpdateQueueOffsets(string email, UserQueueOffsets queueOffsets, CancellationToken ct = default);
|
||||
Task<IEnumerable<User>> GetUsers(string? searchEmail, RoleEnum? searchRole, CancellationToken ct = default);
|
||||
Task<string> CheckHardwareHash(User user, string hardware, CancellationToken ct = default);
|
||||
Task ChangeRole(string email, RoleEnum newRole, CancellationToken ct = default);
|
||||
Task SetEnableStatus(string email, bool isEnabled, CancellationToken ct = default);
|
||||
Task RemoveUser(string email, CancellationToken ct = default);
|
||||
@@ -24,76 +23,25 @@ public interface IUserService
|
||||
|
||||
public class UserService(IDbFactory dbFactory, ICache cache) : IUserService
|
||||
{
|
||||
private const string DeviceEmailPrefix = "azj-";
|
||||
private const string DeviceEmailDomain = "@azaion.com";
|
||||
private const int SerialNumberStart = 4; // index of NNNN inside "azj-NNNN..." (length of DeviceEmailPrefix)
|
||||
private const int SerialNumberLength = 4;
|
||||
private const int DevicePasswordBytes = 16; // hex-encoded → 32 chars
|
||||
|
||||
public async Task RegisterUser(RegisterUserRequest request, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
{
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
var existingUser = await db.Users.FirstOrDefaultAsync(u => u.Email == request.Email, token: ct);
|
||||
if (existingUser != null)
|
||||
throw new BusinessException(ExceptionEnum.EmailExists);
|
||||
|
||||
await db.InsertAsync(new User
|
||||
{
|
||||
await db.InsertAsync(new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Email = request.Email,
|
||||
PasswordHash = request.Password.ToHash(),
|
||||
Role = request.Role,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsEnabled = true
|
||||
}, token: ct);
|
||||
});
|
||||
}
|
||||
catch (PostgresException ex) when (ex.SqlState == PostgresErrorCodes.UniqueViolation)
|
||||
{
|
||||
throw new BusinessException(ExceptionEnum.EmailExists);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RegisterDeviceResponse> RegisterDevice(CancellationToken ct = default)
|
||||
{
|
||||
var (serial, email) = await NextDeviceIdentity(ct);
|
||||
var password = Convert.ToHexString(RandomNumberGenerator.GetBytes(DevicePasswordBytes)).ToLowerInvariant();
|
||||
|
||||
await RegisterUser(new RegisterUserRequest
|
||||
{
|
||||
Email = email,
|
||||
Password = password,
|
||||
Role = RoleEnum.CompanionPC
|
||||
}, ct);
|
||||
|
||||
return new RegisterDeviceResponse
|
||||
{
|
||||
Serial = serial,
|
||||
Email = email,
|
||||
Password = password
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(string Serial, string Email)> NextDeviceIdentity(CancellationToken ct) =>
|
||||
await dbFactory.Run(async db =>
|
||||
{
|
||||
var lastEmail = await db.Users
|
||||
.Where(u => u.Role == RoleEnum.CompanionPC)
|
||||
.OrderByDescending(u => u.CreatedAt)
|
||||
.Select(u => u.Email)
|
||||
.FirstOrDefaultAsync(token: ct);
|
||||
|
||||
var nextNumber = 0;
|
||||
if (!string.IsNullOrEmpty(lastEmail) && lastEmail.Length >= SerialNumberStart + SerialNumberLength)
|
||||
{
|
||||
var serialPart = lastEmail.Substring(SerialNumberStart, SerialNumberLength);
|
||||
if (int.TryParse(serialPart, out var current))
|
||||
nextNumber = current + 1;
|
||||
}
|
||||
|
||||
var serial = $"{DeviceEmailPrefix}{nextNumber.ToString($"D{SerialNumberLength}")}";
|
||||
var email = $"{serial}{DeviceEmailDomain}";
|
||||
return (serial, email);
|
||||
Id = Guid.NewGuid(),
|
||||
Email = request.Email,
|
||||
PasswordHash = request.Password.ToHash(),
|
||||
Role = request.Role,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsEnabled = true
|
||||
}, token: ct);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<User?> GetByEmail(string? email, CancellationToken ct = default)
|
||||
{
|
||||
@@ -122,6 +70,16 @@ public class UserService(IDbFactory dbFactory, ICache cache) : IUserService
|
||||
});
|
||||
|
||||
|
||||
public async Task UpdateHardware(string email, string? hardware = null, CancellationToken ct = default)
|
||||
{
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
{
|
||||
await db.Users.UpdateAsync(x => x.Email == email,
|
||||
u => new User { Hardware = hardware }, token: ct);
|
||||
});
|
||||
cache.Invalidate(User.GetCacheKey(email));
|
||||
}
|
||||
|
||||
public async Task UpdateQueueOffsets(string email, UserQueueOffsets queueOffsets, CancellationToken ct = default)
|
||||
{
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
@@ -148,6 +106,35 @@ public class UserService(IDbFactory dbFactory, ICache cache) : IUserService
|
||||
u => u.Role == searchRole)
|
||||
.ToListAsync(token: ct));
|
||||
|
||||
public async Task<string> CheckHardwareHash(User user, string hardware, CancellationToken ct = default)
|
||||
{
|
||||
var requestHWHash = Security.GetHWHash(hardware);
|
||||
|
||||
//For the new users Hardware would be empty, fill it with actual hardware on the very first request
|
||||
if (string.IsNullOrEmpty(user.Hardware))
|
||||
{
|
||||
await UpdateHardware(user.Email, hardware, ct);
|
||||
cache.Invalidate(User.GetCacheKey(user.Email));
|
||||
await UpdateLastLoginDate(user, ct);
|
||||
return requestHWHash;
|
||||
}
|
||||
|
||||
var userHWHash = Security.GetHWHash(user.Hardware);
|
||||
if (userHWHash != requestHWHash)
|
||||
throw new BusinessException(ExceptionEnum.HardwareIdMismatch);
|
||||
await UpdateLastLoginDate(user, ct);
|
||||
return userHWHash;
|
||||
}
|
||||
|
||||
private async Task UpdateLastLoginDate(User user, CancellationToken ct = default)
|
||||
{
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
await db.Users.UpdateAsync(x => x.Email == user.Email, u => new User
|
||||
{
|
||||
LastLogin = DateTime.UtcNow
|
||||
}, ct));
|
||||
}
|
||||
|
||||
public async Task ChangeRole(string email, RoleEnum newRole, CancellationToken ct = default)
|
||||
{
|
||||
await dbFactory.RunAdmin(async db =>
|
||||
|
||||
@@ -29,8 +29,9 @@ public class SecurityTest
|
||||
" sakdhvb kasjdhbv kjasdhv kjhas";
|
||||
var email = "user@azaion.com";
|
||||
var password = "testpw";
|
||||
var hardwareId = "test_hardware_id";
|
||||
|
||||
var key = Security.GetApiEncryptionKey(email, password);
|
||||
var key = Security.GetApiEncryptionKey(email, password, hardwareId);
|
||||
|
||||
var encryptedStream = new MemoryStream();
|
||||
await StringToStream(testString).EncryptTo(encryptedStream, key);
|
||||
@@ -48,8 +49,9 @@ public class SecurityTest
|
||||
{
|
||||
var username = "user@azaion.com";
|
||||
var password = "testpw";
|
||||
var hardwareId = "test_hardware_id";
|
||||
|
||||
var key = Security.GetApiEncryptionKey(username, password);
|
||||
var key = Security.GetApiEncryptionKey(username, password, hardwareId);
|
||||
|
||||
var largeFilePath = "large.txt";
|
||||
var largeFileDecryptedPath = "large_decrypted.txt";
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Azaion.Common.Configs;
|
||||
using Azaion.Common.Database;
|
||||
using Azaion.Services;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Azaion.Test;
|
||||
|
||||
public class UserServiceTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task CheckHardwareHashTest()
|
||||
{
|
||||
var dbFactory = new DbFactory(new OptionsWrapper<ConnectionStrings>(new ConnectionStrings
|
||||
{
|
||||
AzaionDb = "Host=188.245.120.247;Port=4312;Database=azaion;Username=azaion_reader;Password=A@1n_zxre@d!only@$Az",
|
||||
AzaionDbAdmin = "Host=188.245.120.247;Port=4312;Database=azaion;Username=azaion_admin;Password=Az@1on_Oddmin$$@r"
|
||||
}));
|
||||
var userService = new UserService(dbFactory, new MemoryCache());
|
||||
var user = await userService.GetByEmail("spielberg@azaion.com");
|
||||
if (user == null)
|
||||
throw new Exception("User not found");
|
||||
|
||||
var res = await userService.CheckHardwareHash(user,
|
||||
"CPU: AMD Ryzen 9 3900XT 12-Core Processor. GPU: Microsoft Remote Display Adapter. Memory: 67037080. DriveSerial: PHMB746301G6480DGN _00000001.");
|
||||
}
|
||||
}
|
||||
+1
-19
@@ -1,14 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
# curl is needed by the HEALTHCHECK below. CA certs and ICU are already in the
|
||||
# aspnet:10.0 image. Trim the apt cache to keep the layer small.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# Non-root user (security audit F-6 / AZ-518). The aspnet:10.0 image ships an
|
||||
# `app` user; we only need to create + chown the dirs that get bind-mounted
|
||||
# from the host so the runtime can write to them.
|
||||
RUN mkdir -p /app/Content /app/logs \
|
||||
&& chown -R app:app /app
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -29,15 +19,7 @@ RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \
|
||||
# Build runtime
|
||||
FROM base AS final
|
||||
ARG CI_COMMIT_SHA=unknown
|
||||
ARG BUILD_DATE=unknown
|
||||
ENV AZAION_REVISION=$CI_COMMIT_SHA
|
||||
LABEL org.opencontainers.image.title="azaion.admin-api" \
|
||||
org.opencontainers.image.revision="$CI_COMMIT_SHA" \
|
||||
org.opencontainers.image.created="$BUILD_DATE" \
|
||||
org.opencontainers.image.source="https://git.azaion.com/azaion/admin"
|
||||
WORKDIR /app
|
||||
COPY --from=publish --chown=app:app /app/publish .
|
||||
USER app
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD curl --fail --silent --show-error http://localhost:8080/health/live || exit 1
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "Azaion.AdminApi.dll"]
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
## 1. System Context
|
||||
|
||||
**Problem being solved**: Azaion Suite requires a centralized admin API to manage users, assign roles, and securely distribute encrypted software resources (DLLs, AI models, installers) to authorized devices and SaaS users.
|
||||
**Problem being solved**: Azaion Suite requires a centralized admin API to manage users, assign roles, bind hardware to user accounts, and securely distribute encrypted software resources (DLLs, AI models, installers) to authorized devices.
|
||||
|
||||
**System boundaries**:
|
||||
- **Inside**: User management, authentication (JWT), role-based authorization, file-based resource storage with per-user AES encryption.
|
||||
- **Outside**: Client applications (admin web panel at admin.azaion.com, fTPM-secured Jetson edge devices), PostgreSQL database, server filesystem for resource storage.
|
||||
|
||||
> **Note (AZ-197, 2026-05-13)**: hardware-fingerprint binding (`User.Hardware`, `CheckHardwareHash`, `PUT /users/hardware/set`, `POST /resources/check`, `HardwareIdMismatch`/`BadHardware` error codes) was removed. Edge devices now ship as fTPM-secured Jetsons; server/desktop access is SaaS-only. The `User.Hardware` DB column remains as a nullable tombstone (no migration in AZ-197).
|
||||
- **Inside**: User management, authentication (JWT), role-based authorization, file-based resource storage with per-user AES encryption, hardware fingerprint validation.
|
||||
- **Outside**: Client applications (Azaion Suite desktop app, admin web panel at admin.azaion.com), PostgreSQL database, server filesystem for resource storage.
|
||||
|
||||
**External systems**:
|
||||
|
||||
@@ -16,7 +14,7 @@
|
||||
|--------|-----------------|-----------|---------|
|
||||
| PostgreSQL | Database (linq2db) | Both | User data persistence |
|
||||
| Server filesystem | File I/O | Both | Resource file storage and retrieval |
|
||||
| Azaion Suite client | REST API | Inbound | Resource download, login |
|
||||
| Azaion Suite client | REST API | Inbound | Resource download, hardware check, login |
|
||||
| Admin web panel (admin.azaion.com) | REST API | Inbound | User management, resource upload |
|
||||
|
||||
## 2. Technology Stack
|
||||
@@ -62,13 +60,10 @@
|
||||
|
||||
| Entity | Description | Owned By Component |
|
||||
|--------|-------------|--------------------|
|
||||
| User | System user with email (UNIQUE-indexed via `users_email_uidx`), password hash, role, config (legacy `Hardware` column tombstoned per AZ-197). Subset of users have `Role = CompanionPC` and are auto-provisioned via `POST /devices` (AZ-196), which delegates the insert to `UserService.RegisterUser` (post-security-audit consolidation, finding F-3). | 01 Data Layer |
|
||||
| User | System user with email, password hash, hardware binding, role, config | 01 Data Layer |
|
||||
| UserConfig | JSON-serialized per-user configuration (queue offsets) | 01 Data Layer |
|
||||
| RoleEnum | Authorization role hierarchy (None → ApiAdmin); `ResourceUploader` retained as data only after the OTA endpoints were retired | 01 Data Layer |
|
||||
| DetectionClass *(AZ-513, cycle 1)* | Operator-managed detection-class catalogue (Name, ShortName, Color, MaxSizeM, PhotoMode?) backing the UI Detection Classes table | 01 Data Layer |
|
||||
| ExceptionEnum | Business error code catalog (HW-related codes 40/45 removed by AZ-197) | Common Helpers |
|
||||
|
||||
> **Removed in cycle 1 / post-cycle-1**: the `Resource` entity, the `resources` table, and the OTA delivery flow (AZ-183 — F10) were reverted after the security audit (finding F-1). The data model no longer carries an OTA-artifact entity.
|
||||
| RoleEnum | Authorization role hierarchy (None → ApiAdmin) | 01 Data Layer |
|
||||
| ExceptionEnum | Business error code catalog | Common Helpers |
|
||||
|
||||
**Key relationships**:
|
||||
- User → RoleEnum: each user has exactly one role
|
||||
@@ -77,7 +72,7 @@
|
||||
**Data flow summary**:
|
||||
- Client → API → UserService → PostgreSQL: user CRUD operations
|
||||
- Client → API → ResourcesService → Filesystem: resource upload/download
|
||||
- Client → API → Security → ResourcesService: encrypted resource retrieval (key derived from user email + password; hardware-hash component removed in AZ-197)
|
||||
- Client → API → Security → ResourcesService: encrypted resource retrieval (key derived from user credentials + hardware)
|
||||
|
||||
## 5. Integration Points
|
||||
|
||||
@@ -115,12 +110,11 @@ No explicit availability, latency, throughput, or recovery targets found in the
|
||||
|
||||
**Authorization**: Role-based (RBAC) via ASP.NET Core authorization policies:
|
||||
- `apiAdminPolicy` — requires `ApiAdmin` role
|
||||
- `apiUploaderPolicy` — requires `ResourceUploader` or `ApiAdmin` (defined but never applied to any endpoint)
|
||||
- General `[Authorize]` — any authenticated user
|
||||
|
||||
> The `apiUploaderPolicy` was added by AZ-183 and removed in the post-cycle-1 revert along with the OTA endpoints it guarded. `RoleEnum.ResourceUploader` remains as data only.
|
||||
|
||||
**Data protection**:
|
||||
- At rest: Resources encrypted with AES-256-CBC using per-user derived key (email + password). The hardware-hash component was removed in AZ-197 (sealed-Jetson + SaaS architecture).
|
||||
- At rest: Resources encrypted with AES-256-CBC using per-user derived key (email + password + hardware hash)
|
||||
- In transit: HTTPS (assumed, not enforced in code)
|
||||
- Secrets management: Environment variables (`ASPNETCORE_*` prefix)
|
||||
|
||||
@@ -146,26 +140,19 @@ No explicit availability, latency, throughput, or recovery targets found in the
|
||||
|
||||
### ADR-003: Per-User Resource Encryption
|
||||
|
||||
**Context**: Resources (DLLs, AI models) must be delivered only to authorized users.
|
||||
**Context**: Resources (DLLs, AI models) must be delivered only to authorized hardware.
|
||||
|
||||
**Decision**: Resources are encrypted at download time using AES-256-CBC with a key derived from the user's email and password. The client must know both to decrypt.
|
||||
**Decision**: Resources are encrypted at download time using AES-256-CBC with a key derived from the user's email, password, and hardware hash. The client must know all three to decrypt.
|
||||
|
||||
**Consequences**: Strong per-user binding. However, encryption happens in memory (MemoryStream), which limits practical file sizes. Key derivation is deterministic — same inputs always produce the same key.
|
||||
|
||||
> **Update (AZ-197, 2026-05-13)**: the hardware-hash component of the derivation was removed. The new key formula is `SHA384(email + "-" + password + "-#%@AzaionKey@%#---")`. See ADR-004 for context on why the hardware binding was retired.
|
||||
### ADR-004: Hardware Fingerprint Binding
|
||||
|
||||
### ADR-004: Hardware Fingerprint Binding — RETIRED (AZ-197)
|
||||
**Context**: Resources should only be usable on a specific physical machine.
|
||||
|
||||
**Original context**: Resources should only be usable on a specific physical machine.
|
||||
**Decision**: On first resource access, the user's hardware fingerprint string is stored. Subsequent accesses compare the hash of the provided hardware against the stored value.
|
||||
|
||||
**Original decision**: On first resource access, the user's hardware fingerprint string was stored. Subsequent accesses compared the hash of the provided hardware against the stored value.
|
||||
|
||||
**Retirement decision (2026-05-13, AZ-197)**: The threat model that motivated this binding (credential reuse across machines via desktop installers) no longer applies:
|
||||
|
||||
- **Edge devices** ship as **fTPM-secured Jetsons** (secure boot, fTPM-protected key storage, no user filesystem access, no installer redistribution). Hardware identity is anchored in the fTPM, not in a SHA-384 of CPU/GPU/Memory/DriveSerial strings.
|
||||
- **Server / desktop access** is **SaaS-only** (browser → admin API). There is no installer to copy and no hardware fingerprint to take.
|
||||
|
||||
The binding's only remaining effect was a real production failure mode (`HardwareIdMismatch`, error code 40) on legitimate hardware events. AZ-197 removed `CheckHardwareHash`, `UpdateHardware`, `Security.GetHWHash`, the `PUT /users/hardware/set` and `POST /resources/check` endpoints, and the `Hardware` field from `GetResourceRequest`. The `User.Hardware` DB column is a nullable tombstone (no migration in AZ-197; separate ticket if/when the column is dropped).
|
||||
**Consequences**: Ties resources to a single device. Hardware changes require admin intervention to reset. The raw hardware string is stored in the DB; only the hash is compared.
|
||||
|
||||
### ADR-005: linq2db over Entity Framework
|
||||
|
||||
|
||||
@@ -29,14 +29,12 @@
|
||||
|
||||
### Entities
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — `DetectionClass` (AZ-513) entity was added. `Resource` (AZ-183) was added then removed in the same cycle (post-cycle-1 revert; security audit F-1 + the OTA delivery model itself was deemed obsolete). The `User.Hardware` column is left in place as a tombstone (nullable, unused) per AZ-197. A UNIQUE INDEX `users_email_uidx` was added on `users.email` (security audit F-3, `env/db/06_users_email_unique.sql`).
|
||||
|
||||
```
|
||||
User:
|
||||
Id: Guid (PK)
|
||||
Email: string (required)
|
||||
PasswordHash: string (required)
|
||||
Hardware: string? (optional — TOMBSTONED by AZ-197; nullable, unused; no application code reads or writes)
|
||||
Hardware: string? (optional)
|
||||
Role: RoleEnum (required)
|
||||
CreatedAt: DateTime (required)
|
||||
LastLogin: DateTime? (optional)
|
||||
@@ -51,19 +49,7 @@ UserQueueOffsets:
|
||||
AnnotationsConfirmOffset: ulong
|
||||
AnnotationsCommandsOffset: ulong
|
||||
|
||||
DetectionClass (AZ-513):
|
||||
Id: int (PK, DB-assigned identity)
|
||||
Name, ShortName, Color: string
|
||||
MaxSizeM: double
|
||||
PhotoMode: string?
|
||||
CreatedAt: DateTime
|
||||
|
||||
// Resource entity — REMOVED post-cycle-1 (AZ-183 reverted). The `resources`
|
||||
// table no longer exists; see env/db/ for the current migration set.
|
||||
|
||||
RoleEnum: None=0, Operator=10, Validator=20, CompanionPC=30, Admin=40, ResourceUploader=50, ApiAdmin=1000
|
||||
// ResourceUploader is now data-only — no endpoint policy references it
|
||||
// after AZ-183 was reverted.
|
||||
```
|
||||
|
||||
### Configuration POCOs
|
||||
@@ -83,7 +69,6 @@ ResourcesConfig:
|
||||
ResourcesFolder: string
|
||||
SuiteInstallerFolder: string
|
||||
SuiteStageInstallerFolder: string
|
||||
# EncryptionMasterKey was added by AZ-183 and removed in the post-cycle-1 revert.
|
||||
```
|
||||
|
||||
## 3. External API Specification
|
||||
@@ -96,26 +81,23 @@ N/A — internal component.
|
||||
|
||||
| Query | Frequency | Hot Path | Index Needed |
|
||||
|-------|-----------|----------|--------------|
|
||||
| `SELECT * FROM users WHERE email = ?` | High | Yes | Yes — UNIQUE INDEX `users_email_uidx` on `email` (security audit F-3, `env/db/06_users_email_unique.sql`) |
|
||||
| `SELECT * FROM users WHERE email = ?` | High | Yes | Yes (email) |
|
||||
| `SELECT * FROM users` with optional filters | Medium | No | No |
|
||||
| `UPDATE users SET ... WHERE email = ?` | Medium | No | No |
|
||||
| `INSERT INTO users` | Low | No | No (UNIQUE INDEX above also enforces single-row-per-email atomically) |
|
||||
| `INSERT INTO users` | Low | No | No |
|
||||
| `DELETE FROM users WHERE email = ?` | Low | No | No |
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
| Data | Cache Type | TTL | Invalidation |
|
||||
|------|-----------|-----|-------------|
|
||||
| User by email | In-memory (LazyCache) | 4 hours | On `UpdateQueueOffsets` (post-AZ-197 — hardware paths gone) |
|
||||
|
||||
> The `Resources.Latest.{arch}.{stage}` cache key (added by AZ-183) was removed in the post-cycle-1 revert.
|
||||
| User by email | In-memory (LazyCache) | 4 hours | On hardware update, queue offset update, hardware check |
|
||||
|
||||
### Storage Estimates
|
||||
|
||||
| Table | Est. Row Count (1yr) | Row Size | Total Size | Growth Rate |
|
||||
|-------|---------------------|----------|------------|-------------|
|
||||
| `users` | 100–1000 web users + 2000–10000 CompanionPC device users (AZ-196 grows this) | ~500 bytes | ~5 MB | Medium (device fleet) |
|
||||
| `detection_classes` (AZ-513) | 10–200 | ~250 bytes | ~50 KB | Low |
|
||||
| `users` | 100–1000 | ~500 bytes | ~500 KB | Low |
|
||||
|
||||
### Data Management
|
||||
|
||||
@@ -134,7 +116,7 @@ N/A — internal component.
|
||||
| linq2db | 5.4.1 | ORM for PostgreSQL access |
|
||||
| Npgsql | 10.0.1 | PostgreSQL ADO.NET provider |
|
||||
| LazyCache | 2.4.0 | In-memory cache with async support |
|
||||
| Newtonsoft.Json | 13.0.4 | JSON serialization for UserConfig (bumped from 13.0.1 by security audit D-1, GHSA-5crp-9r3c-p9vr) |
|
||||
| Newtonsoft.Json | 13.0.1 | JSON serialization for UserConfig |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `DbFactory.LoadOptions` throws `ArgumentException` on empty connection strings (fail-fast at startup).
|
||||
@@ -185,8 +167,7 @@ N/A — internal component.
|
||||
- `Common/Configs/ResourcesConfig`
|
||||
- `Common/Entities/User`
|
||||
- `Common/Entities/RoleEnum`
|
||||
- `Common/Entities/DetectionClass` *(added cycle 1, AZ-513)*
|
||||
- `Common/Database/AzaionDb` (now also holds the `DetectionClasses` table; the `Resources` ITable added by AZ-183 was removed in the post-cycle-1 revert)
|
||||
- `Common/Database/AzaionDb`
|
||||
- `Common/Database/AzaionDbSchemaHolder`
|
||||
- `Common/Database/DbFactory`
|
||||
- `Services/Cache`
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# User Management
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — hardware-binding methods (`UpdateHardware`, `CheckHardwareHash`) and `SetHWRequest` were removed by AZ-197; the `ValidateUser` error set now includes `UserDisabled`; `RegisterDevice` was added by AZ-196 to back the new `POST /devices` endpoint. Post-cycle-1 (security audit F-3): `RegisterDevice` now reuses `RegisterUser` for the row insert; the duplicate-row race was closed by adding a UNIQUE INDEX on `users.email` (`env/db/06_users_email_unique.sql`) and translating `Npgsql.PostgresException(SqlState=23505)` to `BusinessException(EmailExists)` inside `RegisterUser`.
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Full user lifecycle management — web-user registration, credential validation, role changes, account enable/disable, deletion, plus auto-provisioning of CompanionPC device users.
|
||||
**Purpose**: Full user lifecycle management — registration, credential validation, hardware binding, role changes, account enable/disable, and deletion.
|
||||
|
||||
**Architectural Pattern**: Service layer — stateless business logic operating on the Data Layer through `IDbFactory`.
|
||||
|
||||
**Upstream dependencies**: Data Layer (IDbFactory, ICache, User entity), Security & Cryptography (hashing), `System.Security.Cryptography.RandomNumberGenerator` (device password entropy).
|
||||
**Upstream dependencies**: Data Layer (IDbFactory, ICache, User entity), Security & Cryptography (hashing).
|
||||
|
||||
**Downstream consumers**: Admin API (endpoint handlers), Authentication (GetByEmail).
|
||||
|
||||
@@ -18,19 +16,18 @@
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `RegisterUser` | `RegisterUserRequest, CancellationToken` | void | Yes | `BusinessException(EmailExists)` — translated from `PostgresException(23505)` after the F-3 hardening |
|
||||
| `RegisterDevice` | `CancellationToken` | `RegisterDeviceResponse` | Yes | `BusinessException(EmailExists)` (propagated from `RegisterUser`) — added by AZ-196, refactored post-audit to call `RegisterUser` end-to-end |
|
||||
| `ValidateUser` | `LoginRequest, CancellationToken` | `User` | Yes | `BusinessException(NoEmailFound, WrongPassword, UserDisabled)` |
|
||||
| `RegisterUser` | `RegisterUserRequest, CancellationToken` | void | Yes | `BusinessException(EmailExists)` |
|
||||
| `ValidateUser` | `LoginRequest, CancellationToken` | `User` | Yes | `BusinessException(NoEmailFound, WrongPassword)` |
|
||||
| `GetByEmail` | `string? email, CancellationToken` | `User?` | Yes | `ArgumentNullException` |
|
||||
| `UpdateHardware` | `string email, string? hardware, CancellationToken` | void | Yes | None |
|
||||
| `UpdateQueueOffsets` | `string email, UserQueueOffsets, CancellationToken` | void | Yes | None |
|
||||
| `GetUsers` | `string? searchEmail, RoleEnum? searchRole, CancellationToken` | `IEnumerable<User>` | Yes | None |
|
||||
| `CheckHardwareHash` | `User, string hardware, CancellationToken` | `string` (hash) | Yes | `BusinessException(HardwareIdMismatch)` |
|
||||
| `ChangeRole` | `string email, RoleEnum, CancellationToken` | void | Yes | None |
|
||||
| `SetEnableStatus` | `string email, bool, CancellationToken` | void | Yes | None |
|
||||
| `RemoveUser` | `string email, CancellationToken` | void | Yes | None |
|
||||
|
||||
**Removed by AZ-197**: `UpdateHardware`, `CheckHardwareHash`, and the private `UpdateLastLoginDate` helper.
|
||||
|
||||
**Input / Output DTOs**:
|
||||
**Input DTOs**:
|
||||
```
|
||||
RegisterUserRequest:
|
||||
Email: string (required) — validated: min 8 chars, valid email format
|
||||
@@ -41,10 +38,9 @@ LoginRequest:
|
||||
Email: string (required)
|
||||
Password: string (required)
|
||||
|
||||
RegisterDeviceResponse (AZ-196):
|
||||
Serial: string ("azj-NNNN", zero-padded)
|
||||
Email: string ("azj-NNNN@azaion.com")
|
||||
Password: string (32-char hex, plaintext, exposed exactly once)
|
||||
SetHWRequest:
|
||||
Email: string (required, validated: not empty)
|
||||
Hardware: string? (optional — null clears hardware)
|
||||
|
||||
SetUserQueueOffsetsRequest:
|
||||
Email: string (required)
|
||||
@@ -71,7 +67,7 @@ N/A — exposed through Admin API component.
|
||||
|
||||
| Data | Cache Type | TTL | Invalidation |
|
||||
|------|-----------|-----|-------------|
|
||||
| User by email | In-memory (via ICache) | 4 hours | After `UpdateQueueOffsets` (only — `UpdateHardware` / `CheckHardwareHash` invalidations are gone with AZ-197) |
|
||||
| User by email | In-memory (via ICache) | 4 hours | After UpdateHardware, UpdateQueueOffsets, CheckHardwareHash (first login) |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
@@ -93,21 +89,20 @@ N/A — exposed through Admin API component.
|
||||
|
||||
| Helper | Purpose | Used By |
|
||||
|--------|---------|---------|
|
||||
| `Security.ToHash` | Password hashing (SHA-384) | RegisterUser, RegisterDevice, ValidateUser |
|
||||
| `RandomNumberGenerator.GetBytes(16)` + `Convert.ToHexString` | 32-char hex device password | RegisterDevice |
|
||||
| `Security.ToHash` | Password hashing (SHA-384) | RegisterUser, ValidateUser |
|
||||
| `Security.GetHWHash` | Hardware fingerprint hashing | CheckHardwareHash |
|
||||
| `QueryableExtensions.WhereIf` | Conditional LINQ filters | GetUsers |
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- No pagination on `GetUsers` — returns all matching users.
|
||||
- `CheckHardwareHash` auto-stores hardware on first access (no explicit admin approval step).
|
||||
- `RemoveUser` is a hard delete, not soft delete.
|
||||
- `RegisterDevice` returns the plaintext password to the caller exactly once; if the provisioning script loses it, the device must be re-registered.
|
||||
- The `User.Hardware` column is left in place but unused (AZ-197 chose to leave the column nullable rather than ship a migration).
|
||||
|
||||
**Potential race conditions**:
|
||||
- Concurrent `RegisterUser` calls with the same email: both could pass the existence check before insert. Mitigated by database unique constraint on email (if one exists).
|
||||
- Concurrent `RegisterDevice` calls: both could read the same "most recent CompanionPC" row and try to claim the same `azj-NNNN` serial. Mitigated by the `users.email` unique constraint — the loser will fail the insert. (Out of cycle-1 scope: a sequence-based serial allocator would eliminate the retry.)
|
||||
- `CheckHardwareHash` first-login path: concurrent requests could trigger multiple hardware updates.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- `GetUsers` loads full user objects including `UserConfig` JSON; for large user bases, projection would be more efficient.
|
||||
@@ -128,7 +123,5 @@ No explicit logging in UserService.
|
||||
- `Services/UserService`
|
||||
- `Common/Requests/LoginRequest`
|
||||
- `Common/Requests/RegisterUserRequest`
|
||||
- `Common/Requests/RegisterDeviceResponse` *(added cycle 1, AZ-196)*
|
||||
- `Common/Requests/SetHWRequest`
|
||||
- `Common/Requests/SetUserQueueOffsetsRequest`
|
||||
|
||||
**Removed cycle 1 (AZ-197)**: `Common/Requests/SetHWRequest`
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
# Authentication & Security
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — AZ-197 simplified `GetApiEncryptionKey` to `(email, password)` and removed `GetHWHash` outright. The hardware-binding threat model that motivated those primitives is no longer in scope (fTPM-anchored Jetsons + browser SaaS).
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: JWT token creation/validation and cryptographic utilities (password hashing, AES file encryption/decryption).
|
||||
**Purpose**: JWT token creation/validation and cryptographic utilities (password hashing, hardware fingerprint hashing, AES file encryption/decryption).
|
||||
|
||||
**Architectural Pattern**: Service + static utility — `AuthService` is a DI-managed service for JWT operations; `Security` is a static class for cryptographic primitives.
|
||||
|
||||
**Upstream dependencies**: Data Layer (JwtConfig, IUserService for GetByEmail), ASP.NET Core (IHttpContextAccessor).
|
||||
|
||||
**Downstream consumers**: Admin API (token creation on login, current user resolution), User Management (password hashing for both web users and provisioned devices), Resource Management (encryption key derivation, stream encryption).
|
||||
**Downstream consumers**: Admin API (token creation on login, current user resolution), User Management (password hashing, hardware hashing), Resource Management (encryption key derivation, stream encryption).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
@@ -26,12 +24,11 @@
|
||||
| Method | Input | Output | Description |
|
||||
|--------|-------|--------|-------------|
|
||||
| `ToHash` | `string` | `string` (Base64) | SHA-384 hash |
|
||||
| `GetApiEncryptionKey` | `string email, string password` | `string` (Base64) | Derives the per-user AES encryption key string. **Signature simplified by AZ-197** (`hardwareHash` parameter removed). |
|
||||
| `GetHWHash` | `string hardware` | `string` (Base64) | Salted hardware hash |
|
||||
| `GetApiEncryptionKey` | `string email, string password, string? hwHash` | `string` (Base64) | Derives AES encryption key |
|
||||
| `EncryptTo` | `Stream input, Stream output, string key, CancellationToken` | void | AES-256-CBC encrypt stream |
|
||||
| `DecryptTo` | `Stream encrypted, Stream output, string key, CancellationToken` | void | AES-256-CBC decrypt stream |
|
||||
|
||||
**Removed by AZ-197**: `GetHWHash(string hardware)` — no remaining callers in the post-cycle-1 codebase.
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
N/A — exposed through Admin API.
|
||||
@@ -65,13 +62,11 @@ None — `Security` itself is a utility consumed by other components.
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- Password hashing uses SHA-384 without per-user salt or key stretching. Not resistant to rainbow table attacks. (Unchanged by cycle 1.)
|
||||
- The encryption-key salt is a hardcoded constant. (`Security.GetApiEncryptionKey` body — see `services_security.md`.)
|
||||
- Password hashing uses SHA-384 without per-user salt or key stretching. Not resistant to rainbow table attacks.
|
||||
- Hardware and encryption key salts are hardcoded constants.
|
||||
- `GetCurrentUserEmail` assumes `ClaimTypes.Name` is always present; accessing a missing key would throw `KeyNotFoundException`.
|
||||
- AES encryption prepends IV as first 16 bytes — consumers must know this format.
|
||||
|
||||
**Removed in cycle 1**: hardware fingerprint hashing was a known weakness (static salt, no rotation); deleting it via AZ-197 also removed that attack surface.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- Large file encryption loads encrypted output into `MemoryStream` before sending — high memory usage for large files.
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# Resource Management
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — AZ-197 removed the `Hardware` field from `GetResourceRequest` and removed `CheckResourceRequest` and `POST /resources/check` entirely. AZ-183 introduced an OTA update path (`POST /get-update`, `POST /resources/publish`, `IResourceUpdateService`, `Resource` entity, `resources` table, `ResourcesConfig.EncryptionMasterKey`) but it was reverted later the same day after the security audit (finding F-1) — the OTA delivery model itself was deemed obsolete. The component is now back to filesystem-backed storage only.
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: filesystem-backed storage — upload, list, download (per-user AES-encrypted), folder clearing, installer distribution. Owned by `IResourcesService`.
|
||||
**Purpose**: Server-side file storage management — upload, list, download (with per-user AES encryption), folder clearing, and installer distribution.
|
||||
|
||||
**Architectural Pattern**: a single service over the local filesystem. No DB access, no cache.
|
||||
**Architectural Pattern**: Service layer — filesystem operations with encryption applied at the service boundary.
|
||||
|
||||
**Upstream dependencies**: Data Layer (`ResourcesConfig`), Authentication & Security (encryption via `Security.EncryptTo`).
|
||||
**Upstream dependencies**: Data Layer (ResourcesConfig), Authentication & Security (encryption via Security.EncryptTo).
|
||||
|
||||
**Downstream consumers**: Admin API (resource endpoints).
|
||||
|
||||
@@ -24,15 +22,15 @@
|
||||
| `ListResources` | `string? dataFolder, string? search, CancellationToken` | `IEnumerable<string>` | Yes | `DirectoryNotFoundException` |
|
||||
| `ClearFolder` | `string? dataFolder` | void | No | None |
|
||||
|
||||
**Input DTO**:
|
||||
**Input DTOs**:
|
||||
```
|
||||
GetResourceRequest (post-AZ-197):
|
||||
GetResourceRequest:
|
||||
Password: string (required, min 8 chars)
|
||||
Hardware: string (required, not empty)
|
||||
FileName: string (required, not empty)
|
||||
// Hardware field removed by AZ-197.
|
||||
|
||||
// CheckResourceRequest — REMOVED by AZ-197.
|
||||
// GetUpdateRequest, PublishResourceRequest — added by AZ-183, removed in the post-cycle-1 revert.
|
||||
CheckResourceRequest:
|
||||
Hardware: string (required)
|
||||
```
|
||||
|
||||
## 3. External API Specification
|
||||
@@ -41,21 +39,17 @@ N/A — exposed through Admin API.
|
||||
|
||||
## 4. Data Access Patterns
|
||||
|
||||
`ResourcesService` is filesystem-only — no DB access, no cache.
|
||||
|
||||
| Source | Service | Pattern |
|
||||
|--------|---------|---------|
|
||||
| Filesystem (`ResourcesConfig.ResourcesFolder`) | `ResourcesService` | Direct read/write/delete |
|
||||
No database access. All operations are filesystem-based.
|
||||
|
||||
### Storage Estimates
|
||||
|
||||
- **Filesystem**: AI models, DLLs, installers — potentially hundreds of MB per file.
|
||||
Resources are stored as flat files in configured directories. Size depends on uploaded content (AI models, DLLs, installers — potentially hundreds of MB per file).
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: stateless — reads/writes directly to filesystem.
|
||||
**State Management**: Stateless — reads/writes directly to filesystem.
|
||||
|
||||
**Key Dependencies**: none beyond BCL (System.IO).
|
||||
**Key Dependencies**: None beyond BCL (System.IO).
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `SaveResource` throws `BusinessException(NoFileProvided)` for null uploads.
|
||||
@@ -67,13 +61,13 @@ N/A — exposed through Admin API.
|
||||
|
||||
| Helper | Purpose | Used By |
|
||||
|--------|---------|---------|
|
||||
| `Security.EncryptTo` | AES stream encryption | `GetEncryptedResource` |
|
||||
| `Security.GetApiEncryptionKey(email, password)` | Per-user key derivation (post-AZ-197 — no hardware component) | Admin API (before calling `GetEncryptedResource`) |
|
||||
| `Security.EncryptTo` | AES stream encryption | GetEncryptedResource |
|
||||
| `Security.GetApiEncryptionKey` | Key derivation | Admin API (before calling GetEncryptedResource) |
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations** (security-audit findings):
|
||||
- **F-2 (High)** — no path traversal protection: `dataFolder` parameter is concatenated directly with `ResourcesFolder`. A malicious `dataFolder` like `../../etc` could access arbitrary filesystem paths. Filed as separate ticket.
|
||||
**Known limitations**:
|
||||
- No path traversal protection: `dataFolder` parameter is concatenated directly with `ResourcesFolder`. A malicious `dataFolder` like `../../etc` could access arbitrary filesystem paths.
|
||||
- `SaveResource` deletes existing file before writing — no versioning or backup.
|
||||
- `GetEncryptedResource` loads the entire encrypted file into a `MemoryStream` — memory-intensive for large files.
|
||||
- `ListResources` wraps a synchronous `DirectoryInfo.GetFiles` in `Task.FromResult` — not truly async.
|
||||
@@ -96,11 +90,11 @@ N/A — exposed through Admin API.
|
||||
|-----------|------|---------|
|
||||
| INFO | Successful file save | `Resource {data.FileName} Saved Successfully` |
|
||||
|
||||
**Log format**: string interpolation via Serilog (security audit F-12 hygiene item: convert to structured form).
|
||||
**Log format**: String interpolation via Serilog.
|
||||
|
||||
**Log storage**: console + rolling file (via Serilog configured in Program.cs).
|
||||
**Log storage**: Console + rolling file (via Serilog configured in Program.cs).
|
||||
|
||||
## Modules Covered
|
||||
- `Services/ResourcesService`
|
||||
- `Common/Requests/GetResourceRequest` (post-AZ-197 — no `CheckResourceRequest`, no `Hardware` field)
|
||||
- `Common/Configs/ResourcesConfig` (the `EncryptionMasterKey` field added by AZ-183 was removed in the post-cycle-1 revert)
|
||||
- `Common/Requests/GetResourceRequest` (includes CheckResourceRequest)
|
||||
- `Common/Configs/ResourcesConfig`
|
||||
|
||||
@@ -22,8 +22,6 @@ Converts `BusinessException` to HTTP 409 JSON response: `{ ErrorCode: int, Messa
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — endpoints below reflect the post-cycle-1 surface (AZ-513 Detection Classes CRUD, AZ-196 device auto-provisioning, AZ-197 hardware-binding removal). AZ-183 (OTA) shipped in cycle 1 but was reverted later the same day after the security audit (finding F-1) — the OTA delivery model itself was deemed obsolete. For per-endpoint cycle origins see `modules/admin_api_program.md`.
|
||||
|
||||
### Authentication
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
@@ -33,41 +31,29 @@ Converts `BusinessException` to HTTP 409 JSON response: `{ ErrorCode: int, Messa
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/users` | POST | ApiAdmin | Creates a new user |
|
||||
| `/devices` | POST | ApiAdmin | **AZ-196**: provisions a CompanionPC device user (returns serial + email + plaintext password once) |
|
||||
| `/users/current` | GET | Authenticated | Returns current user |
|
||||
| `/users` | GET | ApiAdmin | Lists users (optional email/role filters) |
|
||||
| `/users/hardware/set` | PUT | ApiAdmin | Sets user hardware |
|
||||
| `/users/queue-offsets/set` | PUT | Authenticated | Updates queue offsets |
|
||||
| `/users/{email}/set-role/{role}` | PUT | ApiAdmin | Changes user role |
|
||||
| `/users/{email}/enable` | PUT | ApiAdmin | Enables user |
|
||||
| `/users/{email}/disable` | PUT | ApiAdmin | Disables user |
|
||||
| `/users/{email}` | DELETE | ApiAdmin | Removes user |
|
||||
|
||||
**Removed by AZ-197**: `PUT /users/hardware/set` (Hardware-binding feature deleted)
|
||||
|
||||
### Resource Management
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/resources/{dataFolder?}` | POST | Authenticated | Uploads a file (up to 200 MB) |
|
||||
| `/resources/list/{dataFolder?}` | GET | Authenticated | Lists files |
|
||||
| `/resources/clear/{dataFolder?}` | POST | ApiAdmin | Clears folder |
|
||||
| `/resources/get/{dataFolder?}` | POST | Authenticated | Downloads encrypted resource (key derived from `email + password` only — no Hardware) |
|
||||
| `/resources/get/{dataFolder?}` | POST | Authenticated | Downloads encrypted resource |
|
||||
| `/resources/get-installer` | GET | Authenticated | Downloads production installer |
|
||||
| `/resources/get-installer/stage` | GET | Authenticated | Downloads staging installer |
|
||||
|
||||
**Removed by AZ-197**: `POST /resources/check` (was the hardware-binding side-effect probe).
|
||||
**Removed in post-cycle-1 revert**: `POST /get-update` and `POST /resources/publish` (AZ-183 reverted — security audit F-1; OTA delivery model itself obsolete).
|
||||
|
||||
### Detection Classes
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/classes` | POST | ApiAdmin | **AZ-513**: creates a detection class |
|
||||
| `/classes/{id:int}` | PATCH | ApiAdmin | **AZ-513**: partial-merge update of a detection class |
|
||||
| `/classes/{id:int}` | DELETE | ApiAdmin | **AZ-513**: deletes a detection class |
|
||||
| `/resources/check` | POST | Authenticated | Validates hardware |
|
||||
|
||||
### Authorization Policies
|
||||
- **apiAdminPolicy**: requires `ApiAdmin` role (used on most admin endpoints)
|
||||
|
||||
> The `apiUploaderPolicy` was added by AZ-183 and removed in the post-cycle-1 revert along with the OTA endpoints it guarded. `RoleEnum.ResourceUploader` remains as data only.
|
||||
- **apiUploaderPolicy**: requires `ResourceUploader` or `ApiAdmin` role (**defined but never applied to any endpoint — dead code**)
|
||||
|
||||
### CORS
|
||||
- Allowed origins: `https://admin.azaion.com`, `http://admin.azaion.com`
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# Flow: Hardware Check — OBSOLETE
|
||||
|
||||
> **Removed in AZ-197 (2026-05-13).**
|
||||
>
|
||||
> The `POST /resources/check` endpoint, the `UserService.CheckHardwareHash` method, the `HardwareIdMismatch` (40) and `BadHardware` (45) error codes, and the hardware-hash component of `Security.GetApiEncryptionKey` no longer exist. Resource downloads no longer require a hardware fingerprint.
|
||||
>
|
||||
> See `_docs/03_implementation/batch_06_report.md` and the AZ-197 task spec for context. Devices ship as fTPM-secured Jetsons or via SaaS; per-machine credential binding is no longer the relevant threat model.
|
||||
>
|
||||
> This file is retained as a tombstone so historical references resolve. Do not link to it from new docs.
|
||||
# Flow: Hardware Check
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([POST /resources/check — REMOVED]) --> Removed[Endpoint deleted in AZ-197]
|
||||
Start([POST /resources/check]) --> GetUser[AuthService.GetCurrentUser]
|
||||
GetUser --> CheckNull{User null?}
|
||||
CheckNull -->|Yes| Unauth[401 Unauthorized]
|
||||
CheckNull -->|No| CheckHW[UserService.CheckHardwareHash]
|
||||
CheckHW --> HasHW{User has stored hardware?}
|
||||
HasHW -->|No - first time| StoreHW[Store hardware string in DB]
|
||||
StoreHW --> UpdateLogin[Update last_login]
|
||||
UpdateLogin --> ReturnHash([Return hwHash])
|
||||
HasHW -->|Yes| CompareHash{Hashes match?}
|
||||
CompareHash -->|Yes| UpdateLogin2[Update last_login]
|
||||
UpdateLogin2 --> ReturnHash2([Return hwHash])
|
||||
CompareHash -->|No| Mismatch([409: HardwareIdMismatch])
|
||||
```
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# Module Layout
|
||||
|
||||
**Language**: csharp
|
||||
**Layout Convention**: solution-flat (legacy — pre-`src/` convention)
|
||||
**Root**: `./` (csproj folders sit at workspace root)
|
||||
**Last Updated**: 2026-05-13
|
||||
|
||||
## Layout Rules
|
||||
|
||||
1. This admin/ workspace is one **deployable** (the `Azaion.AdminApi` HTTP service) split across four production csproj projects + one e2e test csproj: `Azaion.AdminApi`, `Azaion.Services`, `Azaion.Common`, `Azaion.Test`, `e2e/Azaion.E2E`.
|
||||
2. Existing task specs (`_docs/02_tasks/*/AZ-*.md`) all use `Component: Admin API` as a single coarse identifier covering this entire workspace. The Per-Component Mapping below honors that convention rather than rewriting every task spec.
|
||||
3. The conceptual sub-components documented in `_docs/02_document/components/01_data_layer..05_admin_api/` are **read-time** documentation aids, not write-time ownership boundaries. They are listed under "Conceptual Sub-Components" below for reference only.
|
||||
4. Public API surface = the namespaces / interfaces exposed across csproj boundaries (`I*Service` interfaces in `Azaion.Services`, request DTOs in `Azaion.Common/Requests/`, entities in `Azaion.Common/Entities/`).
|
||||
5. Tests live in `Azaion.Test/` (in-process unit/integration) and `e2e/Azaion.E2E/` (HTTP black-box). Production code never imports from either.
|
||||
|
||||
## Per-Component Mapping
|
||||
|
||||
### Component: Admin API
|
||||
|
||||
- **Epic**: AZ-181 (and any other admin-API epic, e.g. AZ-509 for the Detection Classes feature)
|
||||
- **Directory**: workspace root (multi-csproj, see below)
|
||||
- **Owns (exclusive write during implementation)**:
|
||||
- `Azaion.AdminApi/**`
|
||||
- `Azaion.Services/**`
|
||||
- `Azaion.Common/**`
|
||||
- `Azaion.Test/**`
|
||||
- `e2e/Azaion.E2E/**` (xUnit/HttpClient-based black-box tests)
|
||||
- `e2e/db-init/**` (test-DB seed/init scripts consumed by the e2e harness)
|
||||
- `docker.test/**` (test fixture / schema-init helpers used by `Azaion.Test`)
|
||||
- `docker-compose.test.yml`
|
||||
- **Public API** (visible to other csprojs within the workspace):
|
||||
- `Azaion.Services/I*Service.cs` interfaces (UserService, AuthService, ResourcesService, …)
|
||||
- `Azaion.Services/Security.cs`, `Azaion.Services/Cache.cs` (used by `Azaion.AdminApi/Program.cs`)
|
||||
- `Azaion.Common/Requests/*` request DTOs
|
||||
- `Azaion.Common/Entities/*` linq2db entities
|
||||
- `Azaion.Common/Database/*` `IDbFactory` + connection helpers
|
||||
- `Azaion.Common/Configs/*` strongly-typed config records
|
||||
- `Azaion.Common/Extensions/*` extension methods
|
||||
- `Azaion.Common/BusinessException.cs`
|
||||
- `Azaion.AdminApi/Program.cs` (composition root + minimal-API endpoints)
|
||||
- `Azaion.AdminApi/BusinessExceptionHandler.cs`
|
||||
- **Internal (do NOT import across csproj boundaries)**:
|
||||
- private/internal members within each csproj (default C# visibility rules apply)
|
||||
- `Azaion.AdminApi/appsettings*.json` (loaded by the host, not imported)
|
||||
- `e2e/Azaion.E2E/Helpers/*` (test-only helpers, never imported by production)
|
||||
- **Imports from**: (none — this is the only deployable in the workspace; the Loader is architecturally retired per `suite/_docs/_repo-config.yaml` `unresolved:loader-retirement-arch-doc`)
|
||||
- **Consumed by**: HTTP clients (UI workspace, edge services on secured Jetson, SaaS browser sessions) — out of process
|
||||
|
||||
## Conceptual Sub-Components (documentation only — NOT ownership boundaries)
|
||||
|
||||
These come from `_docs/02_document/components/` and exist for reading the codebase, not for assigning task ownership. A single task may legitimately touch multiple sub-components within the `Admin API` umbrella.
|
||||
|
||||
| # | Sub-component | Primary file locations |
|
||||
|---|----------------------|------------------------|
|
||||
| 1 | Data Layer | `Azaion.Common/Database/`, `Azaion.Common/Configs/`, `Azaion.Common/Entities/` (incl. `DetectionClass.cs` added cycle 1; `Resource.cs` added then removed in same cycle — see post-cycle-1 revert) |
|
||||
| 2 | User Management | `Azaion.Services/UserService.cs` (incl. `RegisterDevice` added cycle 1 / AZ-196 — calls `RegisterUser` end-to-end after security-audit consolidation, finding F-3), `Azaion.Common/Requests/Register{User,DeviceResponse}.cs`, `LoginRequest.cs`, `SetUserQueueOffsetsRequest.cs` |
|
||||
| 3 | Auth & Security | `Azaion.Services/AuthService.cs`, `Azaion.Services/Security.cs` (post-AZ-197 — `GetHWHash` removed; signature simplified), `Azaion.Services/Cache.cs` |
|
||||
| 4 | Resource Management | `Azaion.Services/ResourcesService.cs`, `Azaion.Common/Requests/GetResourceRequest.cs` (`SetHWRequest.cs` removed by AZ-197; `ResourceUpdateService.cs` + `GetUpdateRequest.cs` + `PublishResourceRequest.cs` removed when AZ-183 was reverted) |
|
||||
| 4b | Detection Classes | `Azaion.Services/DetectionClassService.cs` + `Azaion.Common/Requests/{Create,Update}DetectionClassRequest.cs` (added cycle 1 / AZ-513) |
|
||||
| 5 | Admin API (HTTP) | `Azaion.AdminApi/Program.cs`, `Azaion.AdminApi/BusinessExceptionHandler.cs`, `Azaion.AdminApi/appsettings*.json` |
|
||||
|
||||
## Allowed Dependencies (csproj layering)
|
||||
|
||||
| Layer | csproj | May reference |
|
||||
|-------|--------|---------------|
|
||||
| 4. Entry / Host | `Azaion.AdminApi` | `Azaion.Services`, `Azaion.Common` |
|
||||
| 3. Application | `Azaion.Services` | `Azaion.Common` |
|
||||
| 2. Foundation | `Azaion.Common` | (none) |
|
||||
| —. Tests (in-process) | `Azaion.Test` | `Azaion.Services`, `Azaion.Common`, `Azaion.AdminApi` (integration only) |
|
||||
| —. Tests (out-of-process e2e) | `e2e/Azaion.E2E` | (none from production csprojs — HTTP only) |
|
||||
|
||||
A reference from a lower production layer to a higher production layer is an **Architecture** finding (High severity) in `/code-review` Phase 7. Test projects may reference any production csproj; production csprojs may NOT reference test projects.
|
||||
|
||||
## Layout Conventions (reference)
|
||||
|
||||
| Language | Root | Per-component path | Public API file | Test path |
|
||||
|----------|------|-------------------|-----------------|-----------|
|
||||
| C# (.NET) | `./` (this workspace, legacy flat layout) | `./<Csproj>/` | namespace-root types in each csproj | `Azaion.Test/`, `e2e/Azaion.E2E/` |
|
||||
|
||||
## Notes
|
||||
|
||||
- This file was authored 2026-05-13 by `/autodev` Step 10 to satisfy `/implement` Step 4. The `_docs/` artifact set predates the Step 1.5 module-layout addition, so this is a **backfill** rather than a fresh decompose Step 1.5 run.
|
||||
- If the project later splits into multiple deployables (e.g. carving out `Azaion.AnnotationsApi`), re-run `/decompose` Step 1.5 to produce a finer-grained mapping.
|
||||
@@ -5,40 +5,25 @@ Application entry point: configures DI, middleware, authentication, authorizatio
|
||||
|
||||
## Public Interface (HTTP Endpoints)
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — endpoint surface changed by AZ-513 (detection-class CRUD), AZ-196 (device auto-registration), AZ-197 (hardware-binding removal). AZ-183 (OTA update check + publish) was reverted later the same day after the security audit (finding F-1) — the OTA delivery model itself was deemed obsolete; see `_docs/05_security/security_report.md` for context. The table reflects the post-cycle-1 state including that revert.
|
||||
|
||||
| Method | Path | Auth | Summary | Cycle 1 origin |
|
||||
|--------|------|------|---------|----------------|
|
||||
| POST | `/login` | Anonymous | Validates credentials, returns JWT token | — |
|
||||
| POST | `/users` | ApiAdmin | Creates a new user | — |
|
||||
| POST | `/devices` | ApiAdmin | Creates a CompanionPC device user (auto serial / email / 32-hex password) | AZ-196 |
|
||||
| GET | `/users/current` | Any authenticated | Returns current user from JWT claims | — |
|
||||
| GET | `/users` | ApiAdmin | Lists users with optional email/role filters | — |
|
||||
| PUT | `/users/queue-offsets/set` | Any authenticated | Updates user's queue offsets | — |
|
||||
| PUT | `/users/{email}/set-role/{role}` | ApiAdmin | Changes a user's role | — |
|
||||
| PUT | `/users/{email}/enable` | ApiAdmin | Enables a user account | — |
|
||||
| PUT | `/users/{email}/disable` | ApiAdmin | Disables a user account | — |
|
||||
| DELETE | `/users/{email}` | ApiAdmin | Removes a user | — |
|
||||
| POST | `/resources/{dataFolder?}` | Any authenticated | Uploads a resource file | — |
|
||||
| GET | `/resources/list/{dataFolder?}` | Any authenticated | Lists files in a resource folder | — |
|
||||
| POST | `/resources/clear/{dataFolder?}` | ApiAdmin | Clears a resource folder | — |
|
||||
| POST | `/resources/get/{dataFolder?}` | Any authenticated | Downloads an encrypted resource (key derived from `email + password` only) | AZ-197 wire change (no `Hardware` field) |
|
||||
| GET | `/resources/get-installer` | Any authenticated | Downloads latest production installer | — |
|
||||
| GET | `/resources/get-installer/stage` | Any authenticated | Downloads latest staging installer | — |
|
||||
| POST | `/classes` | ApiAdmin | Creates a detection class | AZ-513 |
|
||||
| PATCH | `/classes/{id:int}` | ApiAdmin | Updates a detection class (partial-merge) | AZ-513 |
|
||||
| DELETE | `/classes/{id:int}` | ApiAdmin | Deletes a detection class | AZ-513 |
|
||||
|
||||
### Removed in cycle 1
|
||||
|
||||
The following endpoints were removed during cycle 1 and now return `404`:
|
||||
|
||||
| Method | Path | Reason removed |
|
||||
|--------|------|----------------|
|
||||
| PUT | `/users/hardware/set` | AZ-197 — hardware-binding feature deleted (no fielded clients in target architecture) |
|
||||
| POST | `/resources/check` | AZ-197 — was the hardware-binding side-effect probe; no remaining purpose |
|
||||
| POST | `/get-update` | OTA delivery model retired post-cycle-1 (security audit F-1: endpoint disclosed plaintext per-resource encryption keys to any authenticated caller; the underlying installer-distribution flow is itself obsolete) |
|
||||
| POST | `/resources/publish` | Same revert as `/get-update` — the publish counterpart of the OTA flow |
|
||||
| Method | Path | Auth | Summary |
|
||||
|--------|------|------|---------|
|
||||
| POST | `/login` | Anonymous | Validates credentials, returns JWT token |
|
||||
| POST | `/users` | ApiAdmin | Creates a new user |
|
||||
| GET | `/users/current` | Any authenticated | Returns current user from JWT claims |
|
||||
| GET | `/users` | ApiAdmin | Lists users with optional email/role filters |
|
||||
| PUT | `/users/hardware/set` | ApiAdmin | Sets a user's hardware fingerprint |
|
||||
| PUT | `/users/queue-offsets/set` | Any authenticated | Updates user's queue offsets |
|
||||
| PUT | `/users/{email}/set-role/{role}` | ApiAdmin | Changes a user's role |
|
||||
| PUT | `/users/{email}/enable` | ApiAdmin | Enables a user account |
|
||||
| PUT | `/users/{email}/disable` | ApiAdmin | Disables a user account |
|
||||
| DELETE | `/users/{email}` | ApiAdmin | Removes a user |
|
||||
| POST | `/resources/{dataFolder?}` | Any authenticated | Uploads a resource file |
|
||||
| GET | `/resources/list/{dataFolder?}` | Any authenticated | Lists files in a resource folder |
|
||||
| POST | `/resources/clear/{dataFolder?}` | ApiAdmin | Clears a resource folder |
|
||||
| POST | `/resources/get/{dataFolder?}` | Any authenticated | Downloads an encrypted resource |
|
||||
| GET | `/resources/get-installer` | Any authenticated | Downloads latest production installer |
|
||||
| GET | `/resources/get-installer/stage` | Any authenticated | Downloads latest staging installer |
|
||||
| POST | `/resources/check` | Any authenticated | Validates hardware fingerprint |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
@@ -46,11 +31,10 @@ The following endpoints were removed during cycle 1 and now return `404`:
|
||||
- `IUserService` → `UserService` (Scoped)
|
||||
- `IAuthService` → `AuthService` (Scoped)
|
||||
- `IResourcesService` → `ResourcesService` (Scoped)
|
||||
- `IDetectionClassService` → `DetectionClassService` (Scoped) — added by AZ-513
|
||||
- `IDbFactory` → `DbFactory` (Singleton)
|
||||
- `ICache` → `MemoryCache` (Scoped)
|
||||
- `LazyCache` via `AddLazyCache()`
|
||||
- FluentValidation validators auto-discovered from `RegisterUserValidator` assembly (also picks up `CreateDetectionClassRequest`, `UpdateDetectionClassRequest` validators introduced in cycle 1)
|
||||
- FluentValidation validators auto-discovered from `RegisterUserValidator` assembly
|
||||
- `BusinessExceptionHandler` registered as exception handler
|
||||
|
||||
### Middleware Pipeline
|
||||
@@ -63,8 +47,7 @@ The following endpoints were removed during cycle 1 and now return `404`:
|
||||
|
||||
### Authorization Policies
|
||||
- `apiAdminPolicy`: requires `RoleEnum.ApiAdmin` role
|
||||
|
||||
> The `apiUploaderPolicy` (`RoleEnum.ResourceUploader` OR `ApiAdmin`) was added by AZ-183 and removed in the same cycle when the OTA endpoints it guarded were retired (see "Removed in cycle 1" above). `RoleEnum.ResourceUploader` itself remains as a data value (the seed `uploader@azaion.com` still uses it) but is no longer wired to any endpoint policy.
|
||||
- `apiUploaderPolicy`: requires `RoleEnum.ResourceUploader` OR `RoleEnum.ApiAdmin` role
|
||||
|
||||
### Configuration Sections
|
||||
- `JwtConfig` — JWT signing/validation
|
||||
|
||||
@@ -18,15 +18,14 @@ Custom exception type for domain-level errors, paired with an `ExceptionEnum` ca
|
||||
| `NoEmailFound` | 10 | No such email found |
|
||||
| `EmailExists` | 20 | Email already exists |
|
||||
| `WrongPassword` | 30 | Passwords do not match |
|
||||
| `PasswordLengthIncorrect` | 32 | Password should be at least 12 characters (description text — actual validator threshold is 8 chars per `RegisterUserValidator`) |
|
||||
| `PasswordLengthIncorrect` | 32 | Password should be at least 8 characters |
|
||||
| `EmailLengthIncorrect` | 35 | Email is empty or invalid |
|
||||
| `WrongEmail` | 37 | (no description attribute) |
|
||||
| `UserDisabled` | 38 | User account is disabled |
|
||||
| `HardwareIdMismatch` | 40 | Hardware mismatch — unauthorized hardware |
|
||||
| `BadHardware` | 45 | Hardware should be not empty |
|
||||
| `WrongResourceName` | 50 | Wrong resource file name |
|
||||
| `NoFileProvided` | 60 | No file provided |
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — `HardwareIdMismatch = 40` and `BadHardware = 45` were removed by AZ-197 (admin-side hardware-binding cleanup). Code 40 should NOT be reused for a different meaning — older clients may still surface "Hardware mismatch" UX strings keyed on the integer. `UserDisabled = 38` was added earlier (still part of the baseline). See `_docs/03_implementation/batch_06_report.md`.
|
||||
|
||||
## Internal Logic
|
||||
Static constructor eagerly loads all `ExceptionEnum` descriptions into a dictionary via `EnumExtensions.GetDescriptions<ExceptionEnum>()`. Messages are retrieved by dictionary lookup with fallback to `ToString()`.
|
||||
|
||||
@@ -35,8 +34,8 @@ Static constructor eagerly loads all `ExceptionEnum` descriptions into a diction
|
||||
|
||||
## Consumers
|
||||
- `BusinessExceptionHandler` — catches and serializes to HTTP 409 response
|
||||
- `UserService` — throws for email/password validation failures (`NoEmailFound`, `WrongPassword`, `EmailExists`, `UserDisabled`)
|
||||
- `ResourcesService` — throws `NoFileProvided` for missing file uploads
|
||||
- `UserService` — throws for email/password/hardware validation failures
|
||||
- `ResourcesService` — throws for missing file uploads
|
||||
- FluentValidation validators — reference `ExceptionEnum` codes in `.WithErrorCode()`
|
||||
|
||||
## Data Models
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Module: Azaion.Common.Entities.DetectionClass
|
||||
|
||||
## Purpose
|
||||
Domain entity for a single detection class shown to operators in the Detection Classes admin table. Persisted to the `detection_classes` table; managed via the `/classes` admin endpoints introduced by AZ-513.
|
||||
|
||||
> **Cycle 1 (2026-05-13) origin** — added by AZ-513 to back the new admin `/classes` CRUD endpoints; previously the read path was served by another service (likely `annotations/`) and admin/ had no own model for it.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Id` | `int` | Auto-assigned identity (DB-generated via `InsertWithInt32IdentityAsync`) |
|
||||
| `Name` | `string` | Full display name (max 120 chars per validator) |
|
||||
| `ShortName` | `string` | Short label used in tight UI (max 20 chars) |
|
||||
| `Color` | `string` | UI color (e.g. `"#FF0000"`, max 20 chars — accepts hex strings or named-color tokens) |
|
||||
| `MaxSizeM` | `double` | Maximum real-world object size in meters (must be > 0) |
|
||||
| `PhotoMode` | `string?` | Optional capture-mode hint (max 20 chars when present) |
|
||||
| `CreatedAt` | `DateTime` | UTC creation timestamp set by the service on insert |
|
||||
|
||||
## Internal Logic
|
||||
Plain POCO; no behaviour. Identity is assigned by the database on insert (`InsertWithInt32IdentityAsync`).
|
||||
|
||||
## Dependencies
|
||||
None (no `using` directives on `Azaion.Services` / external libs).
|
||||
|
||||
## Consumers
|
||||
- `Azaion.Services.DetectionClassService` — CRUD operations
|
||||
- `AzaionDb.DetectionClasses` — linq2db table mapping (see `common_database_azaion_db.md`)
|
||||
- `Azaion.AdminApi.Program` — `POST/PATCH/DELETE /classes` endpoints
|
||||
|
||||
## Data Models
|
||||
Maps 1:1 to the `detection_classes` PostgreSQL table.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None directly; persisted via `IDbFactory` → PostgreSQL.
|
||||
|
||||
## Security
|
||||
Data is operator-controlled metadata; no PII or secrets.
|
||||
|
||||
## Tests
|
||||
- `e2e/Azaion.E2E/Tests/DetectionClassesTests.cs` — covers AZ-513 ACs 1–9
|
||||
@@ -1,51 +0,0 @@
|
||||
# Module: Azaion.Common.Requests.CreateDetectionClassRequest
|
||||
|
||||
## Purpose
|
||||
Request DTO + FluentValidation validator for `POST /classes` (AZ-513).
|
||||
|
||||
> **Cycle 1 (2026-05-13) origin** — added by AZ-513.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### CreateDetectionClassRequest
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Name` | `string` | Full display name |
|
||||
| `ShortName` | `string` | Short label |
|
||||
| `Color` | `string` | UI color string (hex or named) |
|
||||
| `MaxSizeM` | `double` | Max real-world size in meters |
|
||||
| `PhotoMode` | `string?` | Optional capture-mode hint |
|
||||
|
||||
### CreateDetectionClassValidator
|
||||
| Rule | Constraint |
|
||||
|------|-----------|
|
||||
| `Name` | NotEmpty, ≤ 120 chars |
|
||||
| `ShortName` | NotEmpty, ≤ 20 chars |
|
||||
| `Color` | NotEmpty, ≤ 20 chars |
|
||||
| `MaxSizeM` | > 0 |
|
||||
| `PhotoMode` | ≤ 20 chars when present |
|
||||
|
||||
## Internal Logic
|
||||
Plain DTO; validator runs in the `/classes` POST handler before the service call. Validation failures are surfaced via `Results.ValidationProblem(...)` (HTTP 400).
|
||||
|
||||
## Dependencies
|
||||
- FluentValidation
|
||||
|
||||
## Consumers
|
||||
- `Azaion.AdminApi.Program` `POST /classes`
|
||||
- `Azaion.Services.DetectionClassService.Create`
|
||||
|
||||
## Data Models
|
||||
Maps to the writable subset of `DetectionClass` (see `common_entities_detection_class.md`).
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None.
|
||||
|
||||
## Security
|
||||
ApiAdmin-only endpoint; FluentValidation enforces field bounds. No HTML/JS sanitisation — the UI is responsible for safe rendering of `Name`, `ShortName`, `Color`.
|
||||
|
||||
## Tests
|
||||
- e2e: `AC1_Post_classes_creates_class_with_assigned_id`, `AC2_Post_classes_*`
|
||||
@@ -1,22 +1,27 @@
|
||||
# Module: Azaion.Common.Requests.GetResourceRequest
|
||||
|
||||
## Purpose
|
||||
Request DTO and validator for the `POST /resources/get/{dataFolder?}` endpoint. The user's password is supplied per-request so the server can derive the per-user AES encryption key for the response stream.
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — the `Hardware` property and its `BadHardware` validator rule were removed by AZ-197 (admin-side hardware-binding cleanup). The wire-compat policy was "drop entirely" — any client still sending `Hardware` will not see it deserialized. The companion `CheckResourceRequest` was removed along with the `POST /resources/check` endpoint. See `_docs/03_implementation/batch_06_report.md`.
|
||||
Request DTOs and validator for resource access endpoints. Contains both `GetResourceRequest` and `CheckResourceRequest`.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### CheckResourceRequest
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Hardware` | `string` | Hardware fingerprint to validate |
|
||||
|
||||
### GetResourceRequest
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Password` | `string` | User's password (used to derive the encryption key) |
|
||||
| `Password` | `string` | User's password (used to derive encryption key) |
|
||||
| `Hardware` | `string` | Hardware fingerprint for authorization |
|
||||
| `FileName` | `string` | Resource file to retrieve |
|
||||
|
||||
### GetResourceRequestValidator
|
||||
| Rule | Constraint | Error Code |
|
||||
|------|-----------|------------|
|
||||
| `Password` min length | >= 8 chars | `PasswordLengthIncorrect` |
|
||||
| `Hardware` not empty | Required | `BadHardware` |
|
||||
| `FileName` not empty | Required | `WrongResourceName` |
|
||||
|
||||
## Internal Logic
|
||||
@@ -27,7 +32,7 @@ Validator uses `BusinessException.GetMessage()` to derive user-facing error mess
|
||||
- FluentValidation
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` `POST /resources/get/{dataFolder?}` endpoint
|
||||
- `Program.cs` `/resources/get/{dataFolder?}` and `/resources/check` endpoints
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
@@ -39,8 +44,7 @@ None.
|
||||
None.
|
||||
|
||||
## Security
|
||||
- Password is sent in the POST body (not URL) to avoid logging in access logs.
|
||||
- Per-user encryption key derivation now uses `email + password` only (see `services_security.md`).
|
||||
Password is sent in the POST body (not URL) to avoid logging in access logs. Hardware fingerprint validates device authorization.
|
||||
|
||||
## Tests
|
||||
- `e2e/Azaion.E2E/Tests/ResourceTests.cs` (encrypted download / round-trip) — updated by AZ-197 to stop sending `Hardware`
|
||||
None.
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Module: Azaion.Common.Requests.RegisterDeviceResponse
|
||||
|
||||
## Purpose
|
||||
Response DTO returned by `POST /devices` (AZ-196) — provides the provisioning script with the freshly-generated `Serial`, `Email`, and one-shot plaintext `Password` for a new CompanionPC device user.
|
||||
|
||||
> **Cycle 1 (2026-05-13) origin** — added by AZ-196.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Serial` | `string` | Server-assigned device serial in the form `azj-NNNN` (zero-padded to 4 digits) |
|
||||
| `Email` | `string` | `{Serial}@azaion.com` — the persisted user's login email |
|
||||
| `Password` | `string` | Plaintext 32-char hex password — exposed exactly once at provisioning; never re-derivable from the SHA-384 hash that is persisted |
|
||||
|
||||
## Internal Logic
|
||||
Plain POCO. All field values are produced inside `UserService.RegisterDevice` (see `services_user_service.md`).
|
||||
|
||||
## Dependencies
|
||||
None.
|
||||
|
||||
## Consumers
|
||||
- `Azaion.AdminApi.Program` `POST /devices` (returned via `Results.Ok(...)` implicit)
|
||||
- `Azaion.Services.UserService.RegisterDevice` (constructs and returns the response)
|
||||
- Provisioning script (out-of-tree) — embeds the values into `device.conf` on the Jetson
|
||||
|
||||
## Data Models
|
||||
Mirrors a subset of fields written into the `users` row (`Email`, `PasswordHash`).
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None.
|
||||
|
||||
## Security
|
||||
- The `Password` is the only chance to capture the plaintext — once the response is consumed by the provisioning pipeline, the value cannot be recovered from the database (only the SHA-384 hash is persisted).
|
||||
- The endpoint is gated by `apiAdminPolicy`. Treat the response as a credential — log carefully.
|
||||
|
||||
## Tests
|
||||
- e2e: `AC1_Post_devices_returns_serial_email_and_password`, `AC3_Returned_credentials_can_login`
|
||||
@@ -0,0 +1,39 @@
|
||||
# Module: Azaion.Common.Requests.SetHWRequest
|
||||
|
||||
## Purpose
|
||||
Request DTO and validator for setting a user's hardware fingerprint (`PUT /users/hardware/set`).
|
||||
|
||||
## Public Interface
|
||||
|
||||
### SetHWRequest
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Email` | `string` | Target user's email |
|
||||
| `Hardware` | `string?` | Hardware fingerprint (null clears it) |
|
||||
|
||||
### SetHWRequestValidator
|
||||
| Rule | Constraint | Error Code |
|
||||
|------|-----------|------------|
|
||||
| `Email` not empty | Required | `EmailLengthIncorrect` |
|
||||
|
||||
## Dependencies
|
||||
- `BusinessException`, `ExceptionEnum`
|
||||
- FluentValidation
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` `/users/hardware/set` endpoint
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None.
|
||||
|
||||
## Security
|
||||
None.
|
||||
|
||||
## Tests
|
||||
None.
|
||||
@@ -1,51 +0,0 @@
|
||||
# Module: Azaion.Common.Requests.UpdateDetectionClassRequest
|
||||
|
||||
## Purpose
|
||||
Request DTO + FluentValidation validator for `PATCH /classes/{id}` (AZ-513). All fields are nullable so callers may send the complete body OR only the changed fields — the service applies partial-merge semantics.
|
||||
|
||||
> **Cycle 1 (2026-05-13) origin** — added by AZ-513.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### UpdateDetectionClassRequest
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Name` | `string?` | If non-null, replace existing |
|
||||
| `ShortName` | `string?` | If non-null, replace existing |
|
||||
| `Color` | `string?` | If non-null, replace existing |
|
||||
| `MaxSizeM` | `double?` | If non-null, replace existing |
|
||||
| `PhotoMode` | `string?` | If non-null, replace existing |
|
||||
|
||||
### UpdateDetectionClassValidator
|
||||
| Rule | Constraint (only checked when field is non-null) |
|
||||
|------|--------------------------------------------------|
|
||||
| `Name` | NotEmpty, ≤ 120 chars |
|
||||
| `ShortName` | NotEmpty, ≤ 20 chars |
|
||||
| `Color` | NotEmpty, ≤ 20 chars |
|
||||
| `MaxSizeM` | > 0 |
|
||||
| `PhotoMode` | ≤ 20 chars |
|
||||
|
||||
## Internal Logic
|
||||
Each rule is gated by `.When(r => r.Field != null)` — fields the caller did not send pass validation untouched. The service then applies the same null-check pattern when writing back.
|
||||
|
||||
## Dependencies
|
||||
- FluentValidation
|
||||
|
||||
## Consumers
|
||||
- `Azaion.AdminApi.Program` `PATCH /classes/{id:int}`
|
||||
- `Azaion.Services.DetectionClassService.Update`
|
||||
|
||||
## Data Models
|
||||
Optional / partial view over `DetectionClass`.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None.
|
||||
|
||||
## Security
|
||||
ApiAdmin-only endpoint. Per the AZ-513 spec, the UI sends the complete body on edit even though partial-merge is supported on the server — that keeps the implementer free to choose either policy without breaking the client.
|
||||
|
||||
## Tests
|
||||
- e2e: `AC3_Patch_classes_full_body_updates_class`, `AC4_Patch_classes_partial_body_only_updates_specified_field`, `AC5_Patch_classes_unknown_id_returns_404`, `AC6_Patch_classes_without_jwt_returns_401`
|
||||
@@ -1,47 +0,0 @@
|
||||
# Module: Azaion.Services.DetectionClassService
|
||||
|
||||
## Purpose
|
||||
CRUD service for `DetectionClass` rows backing the admin Detection Classes table. Wraps `IDbFactory.RunAdmin` calls and translates request DTOs into entity writes.
|
||||
|
||||
> **Cycle 1 (2026-05-13) origin** — added by AZ-513.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### IDetectionClassService
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `Create` | `Task<DetectionClass> Create(CreateDetectionClassRequest request, CancellationToken ct)` | Inserts a new class; returns the entity with the DB-assigned `Id` |
|
||||
| `Update` | `Task<DetectionClass?> Update(int id, UpdateDetectionClassRequest request, CancellationToken ct)` | Partial-merge update; returns `null` when the id doesn't exist |
|
||||
| `Delete` | `Task<bool> Delete(int id, CancellationToken ct)` | Returns `true` when at least one row was deleted; `false` when the id wasn't present |
|
||||
|
||||
## Internal Logic
|
||||
- **Create**: instantiates `DetectionClass`, sets `CreatedAt = DateTime.UtcNow`, calls `db.InsertWithInt32IdentityAsync`, assigns the returned id back to the entity, returns it.
|
||||
- **Update**: loads the row by id under the admin connection, returns `null` if missing. Otherwise applies a null-aware merge: each non-null property on the request overwrites the entity, then `db.UpdateAsync(existing)` persists the row. The route returns 404 when the service returns null.
|
||||
- **Delete**: `db.DetectionClasses.DeleteAsync(x => x.Id == id, ct)`; returns `deleted > 0`. The route returns 404 when the service returns false.
|
||||
|
||||
All writes go through `IDbFactory.RunAdmin` (admin DB connection / role).
|
||||
|
||||
## Dependencies
|
||||
- `IDbFactory` (`Azaion.Common.Database.IDbFactory`)
|
||||
- `DetectionClass` entity
|
||||
- `CreateDetectionClassRequest`, `UpdateDetectionClassRequest`
|
||||
- `LinqToDB` extension methods (`FirstOrDefaultAsync`, `InsertWithInt32IdentityAsync`, `UpdateAsync`, `DeleteAsync`)
|
||||
|
||||
## Consumers
|
||||
- `Azaion.AdminApi.Program` — `POST /classes`, `PATCH /classes/{id:int}`, `DELETE /classes/{id:int}` handlers
|
||||
|
||||
## Data Models
|
||||
Operates on `DetectionClass` via `AzaionDb.DetectionClasses`.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
PostgreSQL via `IDbFactory.RunAdmin`.
|
||||
|
||||
## Security
|
||||
- All endpoints that delegate to this service require `apiAdminPolicy` at the route level.
|
||||
- Validators run before the service (no extra defensive validation inside the service).
|
||||
|
||||
## Tests
|
||||
- `e2e/Azaion.E2E/Tests/DetectionClassesTests.cs` — covers AZ-513 ACs 1–9
|
||||
@@ -41,9 +41,9 @@ Uses `ResourcesConfig` (ResourcesFolder, SuiteInstallerFolder, SuiteStageInstall
|
||||
Local filesystem for resource storage.
|
||||
|
||||
## Security
|
||||
- Resources are encrypted per-user using a key derived from `email + password` (the hardware-hash component was removed by AZ-197 — see `services_security.md`).
|
||||
- File deletion overwrites existing files before writing new ones.
|
||||
- No path traversal protection on `dataFolder` parameter.
|
||||
- Resources are encrypted per-user using a key derived from email + password + hardware hash
|
||||
- File deletion overwrites existing files before writing new ones
|
||||
- No path traversal protection on `dataFolder` parameter
|
||||
|
||||
## Tests
|
||||
None at the module level. End-to-end coverage lives in `e2e/Azaion.E2E/Tests/ResourceTests.cs` (encrypted download / round-trip / 200 MB upload limit) — updated by AZ-197 to stop sending the `Hardware` field.
|
||||
None.
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# Module: Azaion.Services.Security
|
||||
|
||||
## Purpose
|
||||
Static utility class providing cryptographic operations: password hashing, encryption key derivation, and AES-CBC stream encryption/decryption.
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — `GetHWHash` was deleted and `GetApiEncryptionKey` was simplified from `(email, password, hardwareHash)` to `(email, password)` by AZ-197 (admin-side hardware-binding cleanup). The hardware-hash component of the derived key is gone; existing ciphertexts produced under the old derivation are no longer re-derivable from the new signature. See `_docs/03_implementation/batch_06_report.md`.
|
||||
Static utility class providing cryptographic operations: password hashing, hardware fingerprint hashing, encryption key derivation, and AES-CBC stream encryption/decryption.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `ToHash` | `static string ToHash(this string str)` | Extension: SHA-384 hash of input, returned as Base64 |
|
||||
| `GetApiEncryptionKey` | `static string GetApiEncryptionKey(string email, string password)` | Derives the per-user AES encryption key string from email + password (+ static salt) |
|
||||
| `GetHWHash` | `static string GetHWHash(string hardware)` | Derives a salted hash from hardware fingerprint string |
|
||||
| `GetApiEncryptionKey` | `static string GetApiEncryptionKey(string email, string password, string? hardwareHash)` | Derives an AES encryption key from email + password + hardware hash |
|
||||
| `EncryptTo` | `static async Task EncryptTo(this Stream inputStream, Stream toStream, string key, CancellationToken ct)` | AES-256-CBC encrypts a stream; prepends IV to output |
|
||||
| `DecryptTo` | `static async Task DecryptTo(this Stream encryptedStream, Stream toStream, string key, CancellationToken ct)` | Reads IV prefix, then AES-256-CBC decrypts stream |
|
||||
|
||||
## Internal Logic
|
||||
- **Password hashing**: `ToHash` uses SHA-384 with UTF-8 encoding, outputting Base64.
|
||||
- **Encryption key derivation**: `GetApiEncryptionKey` concatenates email and password with the static salt `"-#%@AzaionKey@%#---"`, then hashes via `ToHash` (SHA-384, Base64).
|
||||
- **Hardware hashing**: `GetHWHash` salts the raw hardware string with `"Azaion_{hardware}_%$$$)0_"` before hashing.
|
||||
- **Encryption key derivation**: `GetApiEncryptionKey` concatenates email, password, and hardware hash with a static salt, then hashes.
|
||||
- **Encryption**: AES-256-CBC with PKCS7 padding. Key is SHA-256 of the derived key string. IV is randomly generated and prepended to the output stream. Uses 512 KB buffer for streaming.
|
||||
- **Decryption**: Reads the first 16 bytes as IV, then AES-256-CBC decrypts with PKCS7 padding.
|
||||
|
||||
@@ -25,9 +25,10 @@ Static utility class providing cryptographic operations: password hashing, encry
|
||||
- `System.Text.Encoding`
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` `/resources/get/{dataFolder}` endpoint — calls `GetApiEncryptionKey(user.Email, request.Password)`
|
||||
- `UserService.CheckHardwareHash` — calls `GetHWHash` to verify hardware fingerprint
|
||||
- `Program.cs` `/resources/get` endpoint — calls `GetApiEncryptionKey`
|
||||
- `ResourcesService.GetEncryptedResource` — uses `EncryptTo` extension
|
||||
- `Azaion.Test/SecurityTest` — directly tests `EncryptTo` / `DecryptTo` round-trips (no longer tests hardware-hash derivation)
|
||||
- `SecurityTest` — directly tests `GetApiEncryptionKey`, `EncryptTo`, `DecryptTo`
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
@@ -40,11 +41,11 @@ None.
|
||||
|
||||
## Security
|
||||
Core cryptographic module. Key observations:
|
||||
- Passwords are hashed with SHA-384 (no per-user salt, no key stretching — not bcrypt/scrypt/argon2). This is unchanged by AZ-197.
|
||||
- AES encryption uses SHA-256 of the derived key, with random IV per encryption.
|
||||
- All salts/prefixes are hardcoded constants.
|
||||
- Per AZ-197: device hardware fingerprints no longer participate in key derivation. The threat that hardware binding mitigated (credential reuse via desktop installers) was eliminated by the architectural shift to fTPM-secured Jetsons + browser-only SaaS access.
|
||||
- Passwords are hashed with SHA-384 (no per-user salt, no key stretching — not bcrypt/scrypt/argon2)
|
||||
- Hardware hash uses a static salt
|
||||
- AES encryption uses SHA-256 of the derived key, with random IV per encryption
|
||||
- All salts/prefixes are hardcoded constants
|
||||
|
||||
## Tests
|
||||
- `Azaion.Test/SecurityTest.EncryptDecryptTest` — round-trip encrypt/decrypt of a string
|
||||
- `Azaion.Test/SecurityTest.EncryptDecryptLargeFileTest` — round-trip encrypt/decrypt of a ~400 MB generated file
|
||||
- `SecurityTest.EncryptDecryptTest` — round-trip encrypt/decrypt of a string
|
||||
- `SecurityTest.EncryptDecryptLargeFileTest` — round-trip encrypt/decrypt of a ~400 MB generated file
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Module: Azaion.Services.UserService
|
||||
|
||||
## Purpose
|
||||
Core business logic for user management: registration (web users + provisioned devices), authentication, role management, and account lifecycle.
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — hardware-binding methods (`UpdateHardware`, `CheckHardwareHash`, private `UpdateLastLoginDate`) and the bound `IUserService` declarations were removed by AZ-197 (admin-side hardware-binding cleanup). Device auto-provisioning (`RegisterDevice`) was added by AZ-196. **Post-cycle-1 (security audit F-3)**: `RegisterDevice` was refactored to delegate the row insert to `RegisterUser`, and `RegisterUser` itself now relies on the new `users_email_uidx` UNIQUE INDEX (`env/db/06_users_email_unique.sql`) — the check-then-insert race is gone; `Npgsql.PostgresException(SqlState=23505)` is translated to `BusinessException(EmailExists)`. See `_docs/03_implementation/batch_05_report.md` and `batch_06_report.md`.
|
||||
Core business logic for user management: registration, authentication, hardware binding, role management, and account lifecycle.
|
||||
|
||||
## Public Interface
|
||||
|
||||
@@ -11,45 +9,43 @@ Core business logic for user management: registration (web users + provisioned d
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `RegisterUser` | `Task RegisterUser(RegisterUserRequest request, CancellationToken ct)` | Creates a new user with hashed password |
|
||||
| `RegisterDevice` | `Task<RegisterDeviceResponse> RegisterDevice(CancellationToken ct)` | Creates a new `CompanionPC` user with auto-assigned `azj-NNNN` serial / email and a 32-char hex password (returned plaintext exactly once) |
|
||||
| `ValidateUser` | `Task<User> ValidateUser(LoginRequest request, CancellationToken ct)` | Validates email + password, returns user. Throws `NoEmailFound`, `WrongPassword`, or `UserDisabled` |
|
||||
| `ValidateUser` | `Task<User> ValidateUser(LoginRequest request, CancellationToken ct)` | Validates email + password, returns user |
|
||||
| `GetByEmail` | `Task<User?> GetByEmail(string? email, CancellationToken ct)` | Cached user lookup by email |
|
||||
| `UpdateHardware` | `Task UpdateHardware(string email, string? hardware, CancellationToken ct)` | Sets/clears user's hardware fingerprint |
|
||||
| `UpdateQueueOffsets` | `Task UpdateQueueOffsets(string email, UserQueueOffsets offsets, CancellationToken ct)` | Updates user's annotation queue offsets |
|
||||
| `GetUsers` | `Task<IEnumerable<User>> GetUsers(string? searchEmail, RoleEnum? searchRole, CancellationToken ct)` | Lists users with optional email/role filters |
|
||||
| `CheckHardwareHash` | `Task<string> CheckHardwareHash(User user, string hardware, CancellationToken ct)` | Validates or initializes hardware binding |
|
||||
| `ChangeRole` | `Task ChangeRole(string email, RoleEnum newRole, CancellationToken ct)` | Changes a user's role |
|
||||
| `SetEnableStatus` | `Task SetEnableStatus(string email, bool isEnabled, CancellationToken ct)` | Enables or disables a user account |
|
||||
| `RemoveUser` | `Task RemoveUser(string email, CancellationToken ct)` | Permanently deletes a user |
|
||||
|
||||
## Internal Logic
|
||||
- **RegisterUser**: hashes password via `Security.ToHash`, inserts via `RunAdmin`. Catches `Npgsql.PostgresException` with `SqlState == PostgresErrorCodes.UniqueViolation` (23505) on the `users_email_uidx` UNIQUE INDEX and rethrows as `BusinessException(EmailExists)`. The previous check-then-insert pattern was removed (race-prone before the index existed; redundant after).
|
||||
- **RegisterDevice**: calls private `NextDeviceIdentity` (read-only) to compute the next `azj-NNNN` serial + matching email, generates a 32-char hex password from `RandomNumberGenerator.GetBytes(16)`, then delegates the row insert to `RegisterUser` (so any future change to user-creation policy applies here too). Returns `{Serial, Email, Password}` (plaintext password exposed exactly once at provisioning time). On a serial-allocation race, the second caller's insert hits the UNIQUE INDEX and surfaces `BusinessException(EmailExists)`; the caller can retry.
|
||||
- **NextDeviceIdentity** (private): queries the most recent `RoleEnum.CompanionPC` user via `dbFactory.Run` (read connection), parses the `azj-NNNN` suffix (chars `[SerialNumberStart, SerialNumberLength)` of the email, constants on the class), increments by 1, returns `(serial, email)`.
|
||||
- **ValidateUser**: finds user by email, compares password hash. Throws `NoEmailFound`, `WrongPassword`, or `UserDisabled`.
|
||||
- **RegisterUser**: checks for duplicate email, hashes password via `Security.ToHash`, inserts via `RunAdmin`.
|
||||
- **ValidateUser**: finds user by email, compares password hash. Throws `NoEmailFound` or `WrongPassword`.
|
||||
- **GetByEmail**: uses `ICache.GetFromCacheAsync` with key `User.{email}`.
|
||||
- **UpdateQueueOffsets**: writes via `RunAdmin`, then invalidates the user cache.
|
||||
- **CheckHardwareHash**: on first access (null hardware), stores the raw hardware string and returns the hash. On subsequent access, compares hashes. Throws `HardwareIdMismatch` on mismatch. Also updates `LastLogin` timestamp.
|
||||
- **UpdateHardware/UpdateQueueOffsets**: use `RunAdmin` for writes, then invalidate cache.
|
||||
- **GetUsers**: uses `WhereIf` for optional filter predicates.
|
||||
|
||||
Private constants (device provisioning):
|
||||
- `DeviceEmailPrefix = "azj-"`, `DeviceEmailDomain = "@azaion.com"`, `SerialNumberStart = 4`, `SerialNumberLength = 4`, `DevicePasswordBytes = 16`.
|
||||
Private method:
|
||||
- `UpdateLastLoginDate` — updates `LastLogin` to `DateTime.UtcNow`.
|
||||
|
||||
## Dependencies
|
||||
- `IDbFactory` (database access)
|
||||
- `ICache` (user caching)
|
||||
- `Security` (hashing — `ToHash`)
|
||||
- `System.Security.Cryptography.RandomNumberGenerator` (device password entropy)
|
||||
- `Npgsql` (`PostgresException`, `PostgresErrorCodes.UniqueViolation` — used to translate UNIQUE-INDEX violations to `BusinessException(EmailExists)`)
|
||||
- `Security` (hashing)
|
||||
- `BusinessException` (domain errors)
|
||||
- `QueryableExtensions.WhereIf`
|
||||
- `User`, `UserConfig`, `UserQueueOffsets`, `RoleEnum`
|
||||
- `RegisterUserRequest`, `LoginRequest`, `RegisterDeviceResponse`
|
||||
- `RegisterUserRequest`, `LoginRequest`
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` — `/users/*` endpoints delegate to `IUserService`
|
||||
- `Program.cs` — `POST /devices` calls `RegisterDevice` (added by AZ-196)
|
||||
- `Program.cs` — all `/users/*` endpoints delegate to `IUserService`
|
||||
- `AuthService.GetCurrentUser` — calls `GetByEmail`
|
||||
- `Program.cs` `/resources/get` — calls `CheckHardwareHash`
|
||||
|
||||
## Data Models
|
||||
Operates on `User` entity via `AzaionDb.Users` table. The `User.Hardware` column is left in place (nullable, unused) per AZ-197 — see the entity doc.
|
||||
Operates on `User` entity via `AzaionDb.Users` table.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
@@ -58,10 +54,9 @@ None.
|
||||
PostgreSQL via `IDbFactory`.
|
||||
|
||||
## Security
|
||||
- Passwords hashed with SHA-384 (via `Security.ToHash`) before storage.
|
||||
- Device passwords are returned plaintext to the caller exactly once at provisioning; the persisted form is the SHA-384 hash. The plaintext is never re-derivable.
|
||||
- Read operations use the read-only DB connection; writes use the admin connection.
|
||||
- Passwords hashed with SHA-384 (via `Security.ToHash`) before storage
|
||||
- Hardware binding prevents resource access from unauthorized devices
|
||||
- Read operations use read-only DB connection; writes use admin connection
|
||||
|
||||
## Tests
|
||||
- `Azaion.Test/UserServiceTest.cs` — unit/integration tests against the live test database (hardware-binding tests removed by AZ-197)
|
||||
- `e2e/Azaion.E2E/Tests/DeviceTests.cs` — e2e for AZ-196 device-provisioning ACs
|
||||
- `UserServiceTest.CheckHardwareHashTest` — integration test against live database
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
# Ripple Log — Cycle 1 (2026-05-13)
|
||||
|
||||
Documentation refresh triggered by the cycle 1 task set: AZ-513, AZ-196, AZ-183, AZ-197.
|
||||
|
||||
> **Post-cycle-1 update (same day, 2026-05-13)** — after the security audit (autodev Step 14) AZ-183 (OTA update check & publish) was reverted in full and a F-3 hardening pass was applied to `RegisterUser`/`RegisterDevice`. See "Post-cycle-1 revert (security audit follow-up)" at the bottom of this log for the doc deltas.
|
||||
|
||||
This log records every doc that was refreshed (directly or via the import-graph ripple from another changed file) during autodev Step 13 (Update Docs) — `document` skill in **Task mode**.
|
||||
|
||||
## Method
|
||||
|
||||
Per `.cursor/skills/document/workflows/task.md` Step 0.5, for each changed source file the consuming files were located via `using` references inside `Azaion.AdminApi/`, `Azaion.Services/`, `Azaion.Common/`, `Azaion.Test/`, and `e2e/Azaion.E2E/`. Each consumer that lives in an already-documented module triggered a doc refresh.
|
||||
|
||||
For C#, the import surface walked was `using Azaion.{Common,Services}*;` plus `ProjectReference` declarations in the four production csprojs (`Azaion.AdminApi`, `Azaion.Services`, `Azaion.Common`, `Azaion.Test`, `e2e/Azaion.E2E`).
|
||||
|
||||
## Direct refreshes (changed source file → existing module doc)
|
||||
|
||||
| Module Doc | Trigger |
|
||||
|------------|---------|
|
||||
| `modules/services_user_service.md` | `Azaion.Services/UserService.cs` — AZ-196 added `RegisterDevice`; AZ-197 removed `UpdateHardware`, `CheckHardwareHash`, `UpdateLastLoginDate`. |
|
||||
| `modules/services_security.md` | `Azaion.Services/Security.cs` — AZ-197 removed `GetHWHash`; `GetApiEncryptionKey` signature simplified. |
|
||||
| `modules/services_resources_service.md` | `Azaion.Services/ResourcesService.cs` — caller-side hardware path gone (security note rewrite). |
|
||||
| `modules/common_requests_get_resource.md` | `Azaion.Common/Requests/GetResourceRequest.cs` — AZ-197 removed `Hardware` field; `CheckResourceRequest` removed. |
|
||||
| `modules/common_business_exception.md` | `Azaion.Common/BusinessException.cs` — AZ-197 removed `HardwareIdMismatch` (40) and `BadHardware` (45). |
|
||||
| `modules/admin_api_program.md` | `Azaion.AdminApi/Program.cs` — AZ-513 added `/classes` CRUD; AZ-196 added `/devices`; AZ-183 added `/get-update` + `/resources/publish`; AZ-197 removed `/users/hardware/set` and `/resources/check`. |
|
||||
| `modules/common_requests_set_hw.md` | **Deleted** — `Azaion.Common/Requests/SetHWRequest.cs` no longer exists. |
|
||||
|
||||
## New module docs (added cycle 1)
|
||||
|
||||
| Module Doc | New Source File |
|
||||
|------------|-----------------|
|
||||
| `modules/common_entities_detection_class.md` | `Azaion.Common/Entities/DetectionClass.cs` (AZ-513) |
|
||||
| `modules/common_entities_resource.md` | `Azaion.Common/Entities/Resource.cs` (AZ-183) |
|
||||
| `modules/common_requests_create_detection_class.md` | `Azaion.Common/Requests/CreateDetectionClassRequest.cs` (AZ-513) |
|
||||
| `modules/common_requests_update_detection_class.md` | `Azaion.Common/Requests/UpdateDetectionClassRequest.cs` (AZ-513) |
|
||||
| `modules/services_detection_class_service.md` | `Azaion.Services/DetectionClassService.cs` (AZ-513) |
|
||||
| `modules/services_resource_update_service.md` | `Azaion.Services/ResourceUpdateService.cs` (AZ-183) |
|
||||
| `modules/common_requests_get_update.md` | `Azaion.Common/Requests/GetUpdateRequest.cs` (AZ-183 — also defines `ResourceUpdateItem`) |
|
||||
| `modules/common_requests_publish_resource.md` | `Azaion.Common/Requests/PublishResourceRequest.cs` (AZ-183) |
|
||||
| `modules/common_requests_register_device_response.md` | `Azaion.Common/Requests/RegisterDeviceResponse.cs` (AZ-196) |
|
||||
|
||||
## Component-level refreshes (parents of refreshed modules)
|
||||
|
||||
| Component Doc | Reason |
|
||||
|---------------|--------|
|
||||
| `components/01_data_layer/description.md` | New entities (`DetectionClass`, `Resource`); new cache key `Resources.Latest.{arch}.{stage}`; storage estimates updated; `User.Hardware` marked tombstoned. |
|
||||
| `components/02_user_management/description.md` | `RegisterDevice` added to interface table; `CheckHardwareHash` / `UpdateHardware` removed from interface table; `SetHWRequest` removed; cache invalidation table simplified. |
|
||||
| `components/03_auth_and_security/description.md` | `Security.GetApiEncryptionKey` signature simplified; `GetHWHash` removed. |
|
||||
| `components/04_resource_management/description.md` | `IResourceUpdateService` added (AZ-183) with separate DB + cache + at-rest column encryption; `GetResourceRequest` no longer carries `Hardware`; `CheckResourceRequest` removed. |
|
||||
| `components/05_admin_api/description.md` | New endpoints (POST `/classes`, PATCH `/classes/{id}`, DELETE `/classes/{id}`, POST `/devices`, POST `/get-update`, POST `/resources/publish`); removed endpoints (PUT `/users/hardware/set`, POST `/resources/check`); `apiUploaderPolicy` is now in use. |
|
||||
|
||||
## System-level refreshes
|
||||
|
||||
| System Doc | Reason |
|
||||
|------------|--------|
|
||||
| `system-flows.md` | F4 (Hardware Check) marked REMOVED; F3 sequence diagram regenerated without hardware step; F8 (Detection Classes CRUD), F9 (Device Auto-Provisioning), F10 (OTA Update Check & Publish) added with full sequence diagrams + error tables. |
|
||||
| `architecture.md` | Data Model Overview lists the new `DetectionClass` and `Resource` entities; the `User` entity caption notes the CompanionPC subset auto-provisioned via AZ-196; ExceptionEnum caption notes HW-related codes are gone. The `Note (AZ-197)` block at the top was already in place pre-Step-13. |
|
||||
| `module-layout.md` | Conceptual Sub-Components table updated: cycle-1-added files annotated; `SetHWRequest` removal noted; new sub-component `4b Detection Classes` added. |
|
||||
| `diagrams/flows/flow_hardware_check.md` | Already converted to a tombstone during AZ-197 implementation; no further action this cycle. |
|
||||
|
||||
## Tooling notes
|
||||
|
||||
- C# import resolution was performed by `Grep` on `using Azaion.*` patterns plus by reading the `.csproj` `ProjectReference` set, since the workspace has no `madge`/`depcruise`-equivalent statically available. Any consumer in `Azaion.AdminApi/Program.cs` was treated as a "system entry point" consumer (Program.cs is the composition root + endpoint table — a single file that legitimately consumes everything).
|
||||
- Tests under `Azaion.Test/` and `e2e/Azaion.E2E/` were considered downstream consumers of `Azaion.Services` and `Azaion.Common`. Their files were NOT promoted into the doc tree (per `module-layout.md` Layout Rules — tests are not public API surface), but their AC coverage was reflected in module-doc "Tests" sections and in `tests/blackbox-tests.md` / `tests/traceability-matrix.md` (autodev Step 12).
|
||||
|
||||
## No-op observations
|
||||
|
||||
- Other module docs in `_docs/02_document/modules/` (e.g., `common_entities_user.md`, `common_database_*.md`, `common_extensions_*.md`, `services_auth_service.md`, `services_cache.md`, `admin_api_business_exception_handler.md`, `common_requests_login_request.md`, `common_requests_register_user.md`, `common_requests_set_queue_offsets.md`, `common_configs_*.md`) were inspected and found to be unaffected by cycle 1 changes — no refresh needed.
|
||||
- `_docs/00_problem/acceptance_criteria.md` and `_docs/00_problem/restrictions.md` were intentionally NOT modified — Task-mode Step 4 only updates problem-level docs when the task changed input parameters or the AC catalogue. Cycle 1 added new behaviours but the baseline AC numbering (AC-1..AC-28) is preserved per `cycle-update` rules; new AC sets live under their tracker IDs in `tests/traceability-matrix.md`.
|
||||
|
||||
---
|
||||
|
||||
## Post-cycle-1 revert (security audit follow-up, 2026-05-13)
|
||||
|
||||
After autodev Step 14 (Security Audit) finished with verdict **FAIL** (3 open Highs: F-1, F-2, F-3), the user instructed:
|
||||
|
||||
> "fix findings right now F-1 get-update is again leftover from the shipping resources era, when we delivered software as an installer. We don't need now IResourceUpdateService. F-3 (AMPLIFIED, AZ-196) — duplicate-email race now reachable on /devices because users.email has no UNIQUE index. first of all, reuse the code in the implementation RegisterDevice -> should call RegisterUser then add index to email"
|
||||
|
||||
### Code changes
|
||||
|
||||
| File | Action | Reason |
|
||||
|------|--------|--------|
|
||||
| `Azaion.Services/ResourceUpdateService.cs` | Deleted | F-1 — entire OTA feature reverted |
|
||||
| `Azaion.Common/Requests/GetUpdateRequest.cs` | Deleted | F-1 — request DTO unused after endpoint deletion |
|
||||
| `Azaion.Common/Requests/PublishResourceRequest.cs` | Deleted | F-1 — request DTO unused after endpoint deletion |
|
||||
| `Azaion.Common/Entities/Resource.cs` | Deleted | F-1 — entity unused after service deletion |
|
||||
| `env/db/05_resources.sql` | Deleted | F-1 — `resources` table no longer needed |
|
||||
| `e2e/Azaion.E2E/Tests/ResourceUpdateTests.cs` | Deleted | F-1 — covers deleted endpoints |
|
||||
| `Azaion.AdminApi/Program.cs` | Edited | F-1 — removed `/get-update`, `/resources/publish`, `IResourceUpdateService` DI registration, `apiUploaderPolicy` |
|
||||
| `Azaion.Common/Database/AzaionDb.cs` | Edited | F-1 — removed `ITable<Resource>` |
|
||||
| `Azaion.Common/Database/AzaionDbShemaHolder.cs` | Edited | F-1 — removed `Resource` entity mapping |
|
||||
| `Azaion.Common/Configs/ResourcesConfig.cs` | Edited | F-1 — removed `EncryptionMasterKey` field (also closes F-5) |
|
||||
| `Azaion.AdminApi/appsettings.json` | Edited | F-1 — removed `EncryptionMasterKey` config value |
|
||||
| `docker-compose.test.yml` | Edited | F-1 — removed `ResourcesConfig__EncryptionMasterKey` env var |
|
||||
| `env/db/06_users_email_unique.sql` | **Created** | F-3 — `CREATE UNIQUE INDEX users_email_uidx ON public.users (email);` |
|
||||
| `e2e/db-init/00_run_all.sh` | Edited | drop `05_resources.sql` line; add `06_users_email_unique.sql` line |
|
||||
| `Azaion.Services/UserService.cs` | Edited | F-3 — `RegisterUser` drops check-then-insert, catches `Npgsql.PostgresException(SqlState=23505)` → `EmailExists`; `RegisterDevice` now delegates the row insert to `RegisterUser` (per user direction) |
|
||||
|
||||
### Doc deltas
|
||||
|
||||
| Doc | What changed |
|
||||
|-----|--------------|
|
||||
| `system-flows.md` | F10 row in flow inventory marked REMOVED; F9 dependency note updated; full F10 section replaced with a tombstone explaining the revert |
|
||||
| `architecture.md` | `Resource` entity removed from data model table; `User` row notes UNIQUE INDEX on email and the `RegisterDevice` → `RegisterUser` consolidation |
|
||||
| `module-layout.md` | `4 Resource Management` row updated to drop OTA files; `2 User Management` row notes the F-3 consolidation |
|
||||
| `components/01_data_layer/description.md` | `Resource` entity removed; UNIQUE INDEX on email noted; `Resources.Latest.*` cache key removed; storage-estimates row removed; Newtonsoft.Json version bumped to 13.0.4 |
|
||||
| `components/02_user_management/description.md` | `RegisterUser` and `RegisterDevice` rows updated to reflect the F-3 fix |
|
||||
| `components/04_resource_management/description.md` | Rewritten — collapsed back to filesystem-storage scope; OTA references removed; F-2 callout retained as known limitation |
|
||||
| `components/05_admin_api/description.md` | `/get-update`, `/resources/publish`, `apiUploaderPolicy` removed from endpoint and policy tables |
|
||||
| `modules/admin_api_program.md` | Endpoint table no longer lists OTA endpoints; "Removed in cycle 1" section absorbs them; DI list and policies updated |
|
||||
| `modules/services_user_service.md` | F-3 fix detailed in Internal Logic; Npgsql added to Dependencies |
|
||||
| `modules/services_resource_update_service.md` | **Deleted** |
|
||||
| `modules/common_entities_resource.md` | **Deleted** |
|
||||
| `modules/common_requests_get_update.md` | **Deleted** |
|
||||
| `modules/common_requests_publish_resource.md` | **Deleted** |
|
||||
| `tests/traceability-matrix.md` | AZ-183 section marked REVERTED; FT-P-21..23 strikethroughs |
|
||||
| `tests/blackbox-tests.md` | OTA section collapsed to ID-placeholder table; bodies removed |
|
||||
| `_docs/05_security/security_report.md` | Verdict flipped from FAIL → PASS_WITH_WARNINGS; F-1, F-3, D-1 marked CLOSED; F-2 deferred |
|
||||
| `_docs/05_security/static_analysis.md` | F-1, F-3, F-5 marked CLOSED with resolution notes |
|
||||
| `_docs/05_security/owasp_review.md` | A01 / A02 / A04 / A07 categories upgraded to PASS_WITH_WARNINGS or PASS where the only failing finding was a now-closed cycle-1 entry |
|
||||
| `_docs/05_security/dependency_scan.md` | (already updated during the audit) D-1 marked RESOLVED |
|
||||
|
||||
### Verification
|
||||
|
||||
- `dotnet build Azaion.AdminApi/Azaion.AdminApi.csproj` — green, 0 warnings.
|
||||
- `dotnet test Azaion.Test/Azaion.Test.csproj` — 2/2 passed.
|
||||
- `./scripts/run-tests.sh` (e2e) — 44/44 passed (down from 48/48; the 4 deleted `ResourceUpdateTests` are accounted for).
|
||||
|
||||
### Follow-up tickets filed in Jira
|
||||
|
||||
| Ticket | Title | Points |
|
||||
|--------|-------|--------|
|
||||
| [AZ-516](https://denyspopov.atlassian.net/browse/AZ-516) | F-2: Sanitize `dataFolder` route segment to prevent path traversal | 3 |
|
||||
| [AZ-517](https://denyspopov.atlassian.net/browse/AZ-517) | F-4: Harden `/devices` response (Cache-Control, runbook) | 2 |
|
||||
| [AZ-518](https://denyspopov.atlassian.net/browse/AZ-518) | F-6: Run admin API container as non-root | 2 |
|
||||
| [AZ-519](https://denyspopov.atlassian.net/browse/AZ-519) | F-7: Migrate password hashing to Argon2id with per-user salt | 5 |
|
||||
| [AZ-520](https://denyspopov.atlassian.net/browse/AZ-520) | F-8: Add rate limiting to `/login` endpoint | 2 |
|
||||
| [AZ-521](https://denyspopov.atlassian.net/browse/AZ-521) | Low-severity security hygiene bundle (F-9, F-11, F-12, F-13) | 3 |
|
||||
|
||||
A revert comment was added to AZ-183 (the OTA task that was deleted as part of the F-1 fix).
|
||||
@@ -1,7 +1,5 @@
|
||||
# Azaion Admin API — System Flows
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — F4 (Hardware Check) was deleted by AZ-197; F3 no longer depends on hardware. Two new flows were added: F8 Detection Classes CRUD (AZ-513), F9 Device Auto-Provisioning (AZ-196). F10 OTA Update Check & Publish (AZ-183) was reverted later the same day after the security audit (finding F-1) — the OTA delivery model itself was deemed obsolete; see `_docs/05_security/security_report.md` for context. F3's narrative was updated to drop the hardware-check step.
|
||||
|
||||
## Flow Inventory
|
||||
|
||||
| # | Flow Name | Trigger | Primary Components | Criticality |
|
||||
@@ -9,26 +7,22 @@
|
||||
| F1 | User Login | POST /login | Admin API, User Mgmt, Auth & Security | High |
|
||||
| F2 | User Registration | POST /users | Admin API, User Mgmt | High |
|
||||
| F3 | Encrypted Resource Download | POST /resources/get | Admin API, Auth, User Mgmt, Resource Mgmt | High |
|
||||
| ~~F4~~ | ~~Hardware Check~~ | ~~POST /resources/check~~ | — | **REMOVED — AZ-197** |
|
||||
| F4 | Hardware Check | POST /resources/check | Admin API, Auth, User Mgmt | High |
|
||||
| F5 | Resource Upload | POST /resources | Admin API, Resource Mgmt | Medium |
|
||||
| F6 | Installer Download | GET /resources/get-installer | Admin API, Auth, Resource Mgmt | Medium |
|
||||
| F7 | User Management (CRUD) | Various /users/* | Admin API, User Mgmt | Medium |
|
||||
| F8 | Detection Classes CRUD *(AZ-513)* | POST/PATCH/DELETE /classes | Admin API, DetectionClassService | High |
|
||||
| F9 | Device Auto-Provisioning *(AZ-196)* | POST /devices | Admin API, User Mgmt | High |
|
||||
| ~~F10~~ | ~~OTA Update Check & Publish~~ | ~~POST /get-update + POST /resources/publish~~ | — | **REMOVED — post-cycle-1 (AZ-183 reverted, see security audit F-1)** |
|
||||
|
||||
## Flow Dependencies
|
||||
|
||||
| Flow | Depends On | Shares Data With |
|
||||
|------|-----------|-----------------|
|
||||
| F1 | — | All other flows (produces JWT token) |
|
||||
| F2 | — | F1, F9 (creates user records — including device users via F9) |
|
||||
| F3 | F1 (requires JWT) | — (post-AZ-197: no hardware-binding dependency) |
|
||||
| F2 | — | F1, F3, F4 (creates user records) |
|
||||
| F3 | F1 (requires JWT), F4 (hardware must be bound) | F4 (via hardware hash) |
|
||||
| F4 | F1 (requires JWT) | F3 (hardware binding) |
|
||||
| F5 | F1 (requires JWT) | F3 (uploaded resources are later downloaded) |
|
||||
| F6 | F1 (requires JWT) | — |
|
||||
| F7 | F1 (requires JWT, ApiAdmin role) | F3 (user data) |
|
||||
| F8 | F1 (requires JWT, ApiAdmin role) | UI Detection Classes table |
|
||||
| F9 | F1 (requires JWT, ApiAdmin role) | F2 (writes a user row, but reuses `RegisterUser` end-to-end), F1 (provisioned devices later log in) |
|
||||
| F7 | F1 (requires JWT, ApiAdmin role) | F3, F4 (user data) |
|
||||
|
||||
---
|
||||
|
||||
@@ -114,13 +108,12 @@ sequenceDiagram
|
||||
|
||||
## Flow F3: Encrypted Resource Download
|
||||
|
||||
> **Updated by AZ-197 (2026-05-13)** — the hardware-binding precondition and the `CheckHardwareHash` / `GetHWHash` steps were removed; the encryption key is now derived from `email + password` only. The diagram below reflects the post-cycle-1 path.
|
||||
|
||||
### Description
|
||||
An authenticated user requests a resource file. The system derives a per-user encryption key from email + password, encrypts the file with AES-256-CBC, and streams the encrypted content.
|
||||
An authenticated user requests a resource file. The system validates hardware binding, derives a per-user encryption key, encrypts the file with AES-256-CBC, and streams the encrypted content.
|
||||
|
||||
### Preconditions
|
||||
- User is authenticated (JWT)
|
||||
- User's hardware is bound (via prior F4 call)
|
||||
- Resource file exists on server
|
||||
|
||||
### Sequence Diagram
|
||||
@@ -130,15 +123,20 @@ sequenceDiagram
|
||||
participant Client
|
||||
participant API as Admin API
|
||||
participant Auth as AuthService
|
||||
participant US as UserService
|
||||
participant Sec as Security
|
||||
participant RS as ResourcesService
|
||||
participant FS as Filesystem
|
||||
|
||||
Client->>API: POST /resources/get {password, fileName}
|
||||
Client->>API: POST /resources/get {password, hardware, fileName}
|
||||
API->>Auth: GetCurrentUser()
|
||||
Auth-->>API: User
|
||||
API->>Sec: GetApiEncryptionKey(email, password)
|
||||
Sec-->>API: AES key string
|
||||
API->>US: CheckHardwareHash(user, hardware)
|
||||
US->>Sec: GetHWHash(hardware)
|
||||
Sec-->>US: hash
|
||||
US-->>API: hwHash
|
||||
API->>Sec: GetApiEncryptionKey(email, password, hwHash)
|
||||
Sec-->>API: AES key
|
||||
API->>RS: GetEncryptedResource(folder, fileName, key)
|
||||
RS->>FS: Read file
|
||||
FS-->>RS: FileStream
|
||||
@@ -153,15 +151,48 @@ sequenceDiagram
|
||||
| Error | Where | Detection | Recovery |
|
||||
|-------|-------|-----------|----------|
|
||||
| Not authenticated | API | No/invalid JWT | 401 Unauthorized |
|
||||
| Hardware mismatch | UserService.CheckHardwareHash | Hash comparison fails | 409: HardwareIdMismatch (code 40) |
|
||||
| File not found | ResourcesService | FileStream throws | 500 Internal Server Error |
|
||||
|
||||
---
|
||||
|
||||
## Flow F4: Hardware Check (REMOVED by AZ-197)
|
||||
## Flow F4: Hardware Check (First Login / Validation)
|
||||
|
||||
The hardware-fingerprint binding flow (`POST /resources/check`, `UserService.CheckHardwareHash`, `Security.GetHWHash`, error code 40 `HardwareIdMismatch`, error code 45 `BadHardware`) was removed entirely in cycle 1.
|
||||
### Description
|
||||
Client submits its hardware fingerprint. On first call, the hardware is stored for the user. On subsequent calls, the stored hash is compared against the provided hardware.
|
||||
|
||||
Reason: the threat the binding mitigated (credential reuse via desktop installers) was eliminated by the architectural shift to fTPM-secured Jetsons + browser-only SaaS access. See `_docs/03_implementation/batch_06_report.md` and the obsolete diagram `diagrams/flows/flow_hardware_check.md`.
|
||||
### Preconditions
|
||||
- User is authenticated (JWT)
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant API as Admin API
|
||||
participant Auth as AuthService
|
||||
participant US as UserService
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Client->>API: POST /resources/check {hardware}
|
||||
API->>Auth: GetCurrentUser()
|
||||
Auth-->>API: User
|
||||
API->>US: CheckHardwareHash(user, hardware)
|
||||
alt First time (no stored hardware)
|
||||
US->>DB: UPDATE user SET hardware = ? (admin conn)
|
||||
US->>DB: UPDATE user SET last_login = now()
|
||||
US-->>API: hwHash
|
||||
else Hardware already bound
|
||||
US->>US: Compare hashes
|
||||
alt Match
|
||||
US->>DB: UPDATE user SET last_login = now()
|
||||
US-->>API: hwHash
|
||||
else Mismatch
|
||||
US-->>API: throw HardwareIdMismatch
|
||||
end
|
||||
end
|
||||
API-->>Client: 200 OK (true) / 409
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -229,126 +260,9 @@ sequenceDiagram
|
||||
## Flow F7: User Management (CRUD)
|
||||
|
||||
### Description
|
||||
Admin operations: list users, change role, enable/disable, update queue offsets, delete user. (The "set hardware" operation was removed by AZ-197 — see F4.)
|
||||
Admin operations: list users, change role, enable/disable, set hardware, update queue offsets, delete user.
|
||||
|
||||
### Preconditions
|
||||
- Caller has ApiAdmin role (for most operations)
|
||||
|
||||
All operations follow the same pattern: API endpoint → UserService method → DbFactory.RunAdmin → PostgreSQL UPDATE/DELETE. Cache is invalidated for affected user keys after writes (the `UpdateQueueOffsets` path is the only remaining cache-invalidation site post-AZ-197).
|
||||
|
||||
---
|
||||
|
||||
## Flow F8: Detection Classes CRUD *(AZ-513, 2026-05-13)*
|
||||
|
||||
### Description
|
||||
ApiAdmin manages the detection-class catalogue exposed to operators in the UI: create new entries, partial-merge edits, delete entries. The UI's existing add/delete affordances start working end-to-end once this flow exists; the in-place edit affordance arrives via UI cycle AZ-512.
|
||||
|
||||
### Preconditions
|
||||
- Caller has ApiAdmin role (`apiAdminPolicy`)
|
||||
- `detection_classes` table exists in the admin DB
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant API as Admin API
|
||||
participant VAL as FluentValidation
|
||||
participant DCS as DetectionClassService
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Client->>API: POST /classes {name, shortName, color, maxSizeM, photoMode?}
|
||||
API->>VAL: Validate CreateDetectionClassRequest
|
||||
VAL-->>API: OK / 400
|
||||
API->>DCS: Create(request)
|
||||
DCS->>DB: InsertWithInt32IdentityAsync (admin conn)
|
||||
DB-->>DCS: new id
|
||||
DCS-->>API: DetectionClass {id, …}
|
||||
API-->>Client: 200 OK {DetectionClass}
|
||||
|
||||
Client->>API: PATCH /classes/{id} {…partial fields}
|
||||
API->>VAL: Validate UpdateDetectionClassRequest
|
||||
VAL-->>API: OK / 400
|
||||
API->>DCS: Update(id, request)
|
||||
alt id exists
|
||||
DCS->>DB: UPDATE row applying non-null fields (admin conn)
|
||||
DCS-->>API: DetectionClass
|
||||
API-->>Client: 200 OK {DetectionClass}
|
||||
else id missing
|
||||
DCS-->>API: null
|
||||
API-->>Client: 404 Not Found
|
||||
end
|
||||
|
||||
Client->>API: DELETE /classes/{id}
|
||||
API->>DCS: Delete(id)
|
||||
DCS->>DB: DELETE WHERE id = ? (admin conn)
|
||||
alt deleted > 0
|
||||
DCS-->>API: true
|
||||
API-->>Client: 204 No Content
|
||||
else
|
||||
DCS-->>API: false
|
||||
API-->>Client: 404 Not Found
|
||||
end
|
||||
```
|
||||
|
||||
### Error Scenarios
|
||||
|
||||
| Error | Where | Detection | Recovery |
|
||||
|-------|-------|-----------|----------|
|
||||
| Not authenticated | API | No JWT | 401 Unauthorized |
|
||||
| Wrong role | API | Non-ApiAdmin JWT | 403 Forbidden |
|
||||
| Validation failure | FluentValidation | Field bounds violated | 400 Bad Request |
|
||||
| Missing id (PATCH/DELETE) | DetectionClassService | Row not found | 404 Not Found |
|
||||
|
||||
---
|
||||
|
||||
## Flow F9: Device Auto-Provisioning *(AZ-196, 2026-05-13)*
|
||||
|
||||
### Description
|
||||
ApiAdmin requests a fresh CompanionPC device user. The server allocates the next sequential serial (`azj-NNNN`), generates a 32-char hex password, persists the user with the SHA-384 hash, and returns the plaintext credentials exactly once. The provisioning script (out-of-tree) embeds the values into the device's `device.conf`.
|
||||
|
||||
### Preconditions
|
||||
- Caller has ApiAdmin role (`apiAdminPolicy`)
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Admin
|
||||
participant API as Admin API
|
||||
participant US as UserService
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Admin->>API: POST /devices (no body)
|
||||
API->>US: RegisterDevice()
|
||||
US->>DB: SELECT TOP 1 email FROM users WHERE role = 'CompanionPC' ORDER BY created_at DESC
|
||||
DB-->>US: lastEmail (or null)
|
||||
US->>US: nextNumber = parse(lastEmail.suffix) + 1 (or 0)
|
||||
US->>US: serial = "azj-" + nextNumber.PadLeft(4)
|
||||
US->>US: password = ToHex(RandomBytes(16)) // 32 hex chars
|
||||
US->>DB: INSERT user {Email=serial@domain, PasswordHash=SHA384(password), Role=CompanionPC, IsEnabled=true} (admin conn)
|
||||
DB-->>US: OK
|
||||
US-->>API: RegisterDeviceResponse {Serial, Email, Password}
|
||||
API-->>Admin: 200 OK {Serial, Email, Password}
|
||||
```
|
||||
|
||||
### Error Scenarios
|
||||
|
||||
| Error | Where | Detection | Recovery |
|
||||
|-------|-------|-----------|----------|
|
||||
| Not authenticated / wrong role | API | JWT missing or non-ApiAdmin | 401 / 403 |
|
||||
| Email already exists | UserService.RegisterUser (called by RegisterDevice) | DB UNIQUE INDEX `users_email_uidx` violation translated to `EmailExists` (5) | 409 — caller retries (the next call recomputes a fresh `azj-NNNN`) |
|
||||
|
||||
> **Implementation note** — `RegisterDevice` reuses `UserService.RegisterUser` for the row insert (post-security-audit consolidation, finding F-3). The `users.email` column has a UNIQUE INDEX (`env/db/06_users_email_unique.sql`); concurrent provisioning calls that race on the same serial surface the violation atomically.
|
||||
|
||||
---
|
||||
|
||||
## Flow F10: OTA Update Check & Publish *(REMOVED — post-cycle-1 revert)*
|
||||
|
||||
The `POST /get-update` and `POST /resources/publish` endpoints, the `IResourceUpdateService` / `ResourceUpdateService` / `ResourceColumnEncryption` types, the `Resource` entity, the `resources` table, the `apiUploaderPolicy`, and the `ResourcesConfig.EncryptionMasterKey` field were all removed shortly after AZ-183 shipped.
|
||||
|
||||
Reasons:
|
||||
1. Security audit finding F-1 — `/get-update` was registered with `.RequireAuthorization()` (any authenticated caller) and returned the per-resource decrypted `EncryptionKey` in the response body, defeating the at-rest column encryption.
|
||||
2. The OTA delivery model is itself a leftover from the installer-shipping era; the target architecture (browser-only SaaS + fTPM-secured Jetsons) does not need it.
|
||||
|
||||
The `apiUploaderPolicy` definition was removed from `Program.cs`; the `RoleEnum.ResourceUploader` enum value remains as data (the seed `uploader@azaion.com` user still uses it for negative-auth tests) but is no longer wired to any endpoint.
|
||||
All operations follow the same pattern: API endpoint → UserService method → DbFactory.RunAdmin → PostgreSQL UPDATE/DELETE. Cache is invalidated for affected user keys after writes.
|
||||
|
||||
@@ -473,349 +473,3 @@
|
||||
|
||||
**Expected outcome**: HTTP 400 with password length validation error
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
## Cycle 1 Additions (2026-05-13)
|
||||
|
||||
The scenarios below were appended during the existing-code cycle 1 Test-Spec Sync (autodev Step 12) for tasks AZ-513, AZ-196, AZ-183, AZ-197. Numbering continues from the legacy IDs above; existing IDs are preserved.
|
||||
|
||||
### Cycle 1 Obsoletion Note
|
||||
|
||||
The following legacy entries describe behaviour removed by AZ-197 (admin-side hardware-binding cleanup). Their bodies are intentionally left intact to preserve traceability IDs per the cycle-update rule "preserve existing traceability IDs"; they should be treated as obsolete and superseded by FT-N-15 below:
|
||||
|
||||
- FT-P-04 (First Hardware Check Stores Fingerprint) — superseded; the `POST /resources/check` endpoint and the hardware-store side-effect were removed.
|
||||
- FT-P-05 (Subsequent Hardware Check Matches) — superseded; same endpoint removed.
|
||||
- FT-N-06 (Hardware Mismatch) — superseded; the `HardwareIdMismatch` / error code 40 path no longer exists in `ExceptionEnum`.
|
||||
- FT-P-09 / FT-P-10 wire shape — the `hardware` field on `POST /resources/get/{dataFolder}` is no longer required; the encryption key is now derived from `email + password` only. The tests still pass without the field; do not regenerate spec bodies until a full `/test-spec` rerun.
|
||||
|
||||
See `_docs/03_implementation/batch_06_report.md` for the full AZ-197 implementation rationale and the wire-compat policy decision (drop entirely).
|
||||
|
||||
---
|
||||
|
||||
### Detection Classes CRUD (AZ-513)
|
||||
|
||||
#### FT-P-14: POST /classes Creates Detection Class
|
||||
|
||||
**Summary**: ApiAdmin creates a new detection class and the response includes the assigned id.
|
||||
**Traces to**: AZ-513 AC-1
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Preconditions**:
|
||||
- Caller authenticated as ApiAdmin
|
||||
- `detection_classes` table exists
|
||||
|
||||
**Input data**: `{"name":"Tank","shortName":"T","color":"#FF0000","maxSizeM":5.0}`
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | POST /classes with valid body and ApiAdmin JWT | HTTP 200/201 with body containing assigned `id` and the submitted fields |
|
||||
|
||||
**Expected outcome**: HTTP 200 or 201, response body has integer `id` and matches input fields
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-P-15: PATCH /classes/{id} Full Body Update
|
||||
|
||||
**Summary**: Updating a detection class with a full body replaces the changed fields.
|
||||
**Traces to**: AZ-513 AC-3
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Preconditions**:
|
||||
- A detection class with id `7` exists with `name: "Tank"`
|
||||
|
||||
**Input data**: `{"name":"Heavy Tank","shortName":"T","color":"#FF0000","maxSizeM":5.0}` to PATCH /classes/7
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | PATCH /classes/7 with full body and ApiAdmin JWT | HTTP 200, response body shows `name: "Heavy Tank"` |
|
||||
|
||||
**Expected outcome**: HTTP 200, updated entity reflects the changed field
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-P-16: PATCH /classes/{id} Partial Body Update
|
||||
|
||||
**Summary**: PATCH with only the changed field updates that field and leaves others intact.
|
||||
**Traces to**: AZ-513 AC-4
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Preconditions**:
|
||||
- A detection class with id `7` exists with `name: "Tank", color: "#FF0000", maxSizeM: 5.0`
|
||||
|
||||
**Input data**: `{"color":"#00FF00"}` to PATCH /classes/7
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | PATCH /classes/7 with partial body and ApiAdmin JWT | HTTP 200, response body shows `color: "#00FF00"`; other fields unchanged |
|
||||
|
||||
**Expected outcome**: HTTP 200, partial-merge semantics confirmed
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-P-17: DELETE /classes/{id} Removes Class
|
||||
|
||||
**Summary**: ApiAdmin deletes a detection class and it disappears from the DB.
|
||||
**Traces to**: AZ-513 AC-7
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Preconditions**:
|
||||
- A detection class with id `7` exists
|
||||
|
||||
**Input data**: DELETE /classes/7
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | DELETE /classes/7 with ApiAdmin JWT | HTTP 200 or 204 |
|
||||
| 2 | GET the class list (or PATCH the same id) | id 7 no longer present |
|
||||
|
||||
**Expected outcome**: HTTP 200/204; class removed from DB
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-N-09: POST /classes Without ApiAdmin JWT
|
||||
|
||||
**Summary**: POST /classes requires the same `apiAdminPolicy` as `/users`; non-admin / unauthenticated calls are rejected.
|
||||
**Traces to**: AZ-513 AC-2
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Preconditions**: None (negative path)
|
||||
|
||||
**Input data**: Valid body, but caller has no JWT or a non-ApiAdmin JWT
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | POST /classes without JWT | HTTP 401 |
|
||||
| 2 | POST /classes with non-ApiAdmin JWT | HTTP 403 |
|
||||
|
||||
**Expected outcome**: HTTP 401 (no JWT) or 403 (non-admin)
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-N-10: PATCH /classes/{id} Unknown id Returns 404
|
||||
|
||||
**Summary**: PATCH against a non-existent id returns 404.
|
||||
**Traces to**: AZ-513 AC-5
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Preconditions**: No detection class with id `9999`
|
||||
|
||||
**Input data**: PATCH /classes/9999 with any valid body
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | PATCH /classes/9999 with ApiAdmin JWT | HTTP 404 |
|
||||
|
||||
**Expected outcome**: HTTP 404
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-N-11: PATCH /classes/{id} Without ApiAdmin JWT
|
||||
|
||||
**Summary**: PATCH /classes/{id} requires `apiAdminPolicy`.
|
||||
**Traces to**: AZ-513 AC-6
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Input data**: Any valid body to PATCH /classes/{id}
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | PATCH /classes/{id} without JWT | HTTP 401 |
|
||||
| 2 | PATCH /classes/{id} with non-ApiAdmin JWT | HTTP 403 |
|
||||
|
||||
**Expected outcome**: HTTP 401 or 403
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-N-12: DELETE /classes/{id} Unknown id Returns 404
|
||||
|
||||
**Summary**: DELETE against a non-existent id returns 404 (matching `/users` semantics — non-idempotent).
|
||||
**Traces to**: AZ-513 AC-8
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Preconditions**: No detection class with id `9999`
|
||||
|
||||
**Input data**: DELETE /classes/9999
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | DELETE /classes/9999 with ApiAdmin JWT | HTTP 404 |
|
||||
|
||||
**Expected outcome**: HTTP 404
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-N-13: DELETE /classes/{id} Without ApiAdmin JWT
|
||||
|
||||
**Summary**: DELETE /classes/{id} requires `apiAdminPolicy`.
|
||||
**Traces to**: AZ-513 AC-9
|
||||
**Category**: Detection Classes CRUD
|
||||
|
||||
**Input data**: DELETE /classes/{id}
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | DELETE /classes/{id} without JWT | HTTP 401 |
|
||||
| 2 | DELETE /classes/{id} with non-ApiAdmin JWT | HTTP 403 |
|
||||
|
||||
**Expected outcome**: HTTP 401 or 403
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
### Device Auto-Registration (AZ-196)
|
||||
|
||||
#### FT-P-18: POST /devices Returns Serial / Email / Password
|
||||
|
||||
**Summary**: First call to POST /devices returns the next serial in the `azj-NNNN` sequence with a generated email and 32-char hex password.
|
||||
**Traces to**: AZ-196 AC-1
|
||||
**Category**: Device Provisioning
|
||||
|
||||
**Preconditions**:
|
||||
- Caller authenticated as ApiAdmin
|
||||
- No (or known-prior) CompanionPC users in DB
|
||||
|
||||
**Input data**: POST /devices with no body, ApiAdmin JWT
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | POST /devices with ApiAdmin JWT | HTTP 200 with `serial` matching `^azj-\d{4}$`, `email` = `{serial}@azaion.com`, `password` = 32 lowercase hex chars |
|
||||
|
||||
**Expected outcome**: HTTP 200, all three fields shaped per spec
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-P-19: Sequential Device Serials
|
||||
|
||||
**Summary**: Repeated calls to POST /devices yield strictly increasing serial numbers.
|
||||
**Traces to**: AZ-196 AC-2
|
||||
**Category**: Device Provisioning
|
||||
|
||||
**Preconditions**:
|
||||
- Most recent CompanionPC user has a known serial `azj-NNNN`
|
||||
|
||||
**Input data**: POST /devices twice in succession
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | POST /devices → record serial `S1` | HTTP 200 |
|
||||
| 2 | POST /devices → record serial `S2` | HTTP 200 |
|
||||
| 3 | Parse the numeric suffix of both | numeric(S2) == numeric(S1) + 1 |
|
||||
|
||||
**Expected outcome**: HTTP 200, suffix increments by exactly 1
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-P-20: Returned Device Credentials Can Login
|
||||
|
||||
**Summary**: The plaintext password returned by POST /devices succeeds against POST /login (and the persisted hash is therefore correct).
|
||||
**Traces to**: AZ-196 AC-3, AZ-196 AC-4
|
||||
**Category**: Device Provisioning
|
||||
|
||||
**Preconditions**:
|
||||
- Caller authenticated as ApiAdmin
|
||||
|
||||
**Input data**: Use the response from POST /devices as `{Email, Password}` to POST /login
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | POST /devices with ApiAdmin JWT | HTTP 200, `{Serial, Email, Password}` returned |
|
||||
| 2 | POST /login with the returned `Email` and `Password` | HTTP 200 with non-empty JWT |
|
||||
|
||||
**Expected outcome**: HTTP 200 on login; persisted user has Role=CompanionPC, IsEnabled=true (verified by AdminApi behaviour rather than direct DB inspection)
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
#### FT-N-14: POST /devices Without ApiAdmin JWT
|
||||
|
||||
**Summary**: POST /devices requires `apiAdminPolicy`.
|
||||
**Traces to**: AZ-196 AC-5
|
||||
**Category**: Device Provisioning
|
||||
|
||||
**Input data**: POST /devices
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | POST /devices without JWT | HTTP 401 |
|
||||
| 2 | POST /devices with non-ApiAdmin JWT | HTTP 403 |
|
||||
|
||||
**Expected outcome**: HTTP 401 or 403
|
||||
**Max execution time**: 5s
|
||||
|
||||
---
|
||||
|
||||
### Resources OTA Update Check (AZ-183) — REVERTED post-cycle-1
|
||||
|
||||
The OTA update check & publish feature shipped in cycle 1 was reverted later the same day after the security audit (finding F-1: `/get-update` disclosed plaintext per-resource encryption keys to any authenticated caller). The OTA delivery model itself was deemed obsolete in the target architecture.
|
||||
|
||||
The scenarios `FT-P-21`, `FT-P-22`, `FT-P-23` are retained here as ID placeholders so previously-cited references resolve. Their bodies are intentionally collapsed because the underlying endpoints, service, entity, table, and the e2e test class `ResourceUpdateTests.cs` were all removed. See `_docs/02_document/system-flows.md` (Flow F10) and `_docs/05_security/security_report.md` (finding F-1) for context.
|
||||
|
||||
| Removed Test ID | Was tracing | Disposition |
|
||||
|-----------------|-------------|-------------|
|
||||
| FT-P-21 | AZ-183 AC-2 | Removed — endpoint and test deleted |
|
||||
| FT-P-22 | AZ-183 AC-3 | Removed — endpoint and test deleted |
|
||||
| FT-P-23 | AZ-183 AC-5 | Removed — endpoint and test deleted |
|
||||
|
||||
---
|
||||
|
||||
### Hardware-Binding Removal (AZ-197)
|
||||
|
||||
#### FT-N-15: Hardware Endpoints Removed
|
||||
|
||||
**Summary**: The legacy `PUT /users/hardware/set` endpoint and the `POST /resources/check` endpoint have been removed and now return 404.
|
||||
**Traces to**: AZ-197 AC-2
|
||||
**Category**: Authorization & Routing
|
||||
|
||||
**Preconditions**:
|
||||
- Updated admin API build (post-AZ-197)
|
||||
|
||||
**Input data**: PUT /users/hardware/set and POST /resources/check
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | PUT /users/hardware/set with ApiAdmin JWT | HTTP 404 |
|
||||
| 2 | POST /resources/check with ApiAdmin JWT | HTTP 404 |
|
||||
|
||||
**Expected outcome**: HTTP 404 on both routes
|
||||
**Max execution time**: 5s
|
||||
|
||||
Note: AZ-197 AC-1 (resource download works without `Hardware`) is implicitly covered by the existing FT-P-09 / FT-P-10 scenarios once their request bodies are aligned with the new wire shape. AZ-197 AC-3..AC-8 are internal-signature / build-system invariants and are verified at build/CI time, not via a blackbox HTTP scenario.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Performance Tests
|
||||
|
||||
> **Cycle 1 update (2026-05-13)**: NFT-PERF-02 and NFT-PERF-03 (encrypted resource download, small/large file) were removed because the OTA / encrypted-resource-download endpoints (`POST /resources/get/...`) and the hardware-binding flow they depended on were reverted in cycle 1 (AZ-183 OTA revert, AZ-197 hardware removal). When OTA returns under the new architecture, perf scenarios for it must be re-derived from the new endpoints.
|
||||
|
||||
### NFT-PERF-01: Login Endpoint Latency
|
||||
|
||||
**Summary**: Login endpoint responds within acceptable latency under normal load.
|
||||
@@ -9,46 +7,77 @@
|
||||
**Metric**: Response time (p95)
|
||||
|
||||
**Preconditions**:
|
||||
- System running with seed data (admin user from `e2e/db-init/99_test_seed.sql`)
|
||||
- 10 concurrent virtual users
|
||||
- System running with seed data
|
||||
- 10 concurrent users
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Measurement |
|
||||
|------|----------------|-------------|
|
||||
| 1 | 10 VUs send POST /login for 30s | Measure p50, p95, p99 response times |
|
||||
| 1 | Send 100 login requests (10 concurrent) | Measure p50, p95, p99 response times |
|
||||
|
||||
**Pass criteria**: p95 latency < 500ms
|
||||
**Duration**: 30 seconds
|
||||
|
||||
---
|
||||
|
||||
### NFT-PERF-04: User List Endpoint Under Load
|
||||
### NFT-PERF-02: Resource Download Latency (Small File)
|
||||
|
||||
**Summary**: `GET /users` responds within limits when DB has many users.
|
||||
**Traces to**: AC-11
|
||||
|
||||
> **Note**: this scenario originally referenced AC-9. Post-cycle-1, AC-9 is "Registration rejects duplicate email". The user-listing criterion is AC-11 (filter support). The thresholds below verify the listing path under volume; the filter semantics are covered by functional tests.
|
||||
|
||||
**Metric**: Response time (p95)
|
||||
**Summary**: Encrypted resource download for a small file (1 KB) completes quickly.
|
||||
**Traces to**: AC-14
|
||||
**Metric**: Response time including encryption
|
||||
|
||||
**Preconditions**:
|
||||
- Database seeded with 500 users (perf seed inserts dummy rows alongside the functional seed; see `scripts/run-performance-tests.sh`)
|
||||
- Caller is `admin@azaion.com` (ApiAdmin)
|
||||
- 1 KB test file uploaded
|
||||
- User authenticated with bound hardware
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Measurement |
|
||||
|------|----------------|-------------|
|
||||
| 1 | 10 VUs send GET /users for 30s, sharing one cached JWT | Measure p50, p95, p99 response times |
|
||||
| 1 | Send 50 encrypted download requests (5 concurrent) | Measure p50, p95 response times |
|
||||
|
||||
**Pass criteria**: p95 latency < 1000ms
|
||||
**Duration**: 30 seconds
|
||||
|
||||
---
|
||||
|
||||
## Runner
|
||||
### NFT-PERF-03: Resource Download Latency (Large File)
|
||||
|
||||
Both scenarios are implemented in `scripts/perf-scenarios.js` (k6, JS) and orchestrated by `scripts/run-performance-tests.sh`. The runner spins up the SUT via `docker-compose.test.yml`, seeds 500 perf users into `test-db`, executes k6, captures the JSON summary to `e2e/test-results/perf-summary.json`, and tears down.
|
||||
**Summary**: Encrypted resource download for a larger file (50 MB) completes within limits.
|
||||
**Traces to**: AC-13, AC-14
|
||||
**Metric**: Response time including encryption + transfer
|
||||
|
||||
To run locally: `./scripts/run-performance-tests.sh`. Requires `k6` (`brew install k6`) and Docker.
|
||||
**Preconditions**:
|
||||
- 50 MB test file uploaded
|
||||
- User authenticated with bound hardware
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Measurement |
|
||||
|------|----------------|-------------|
|
||||
| 1 | Send 5 sequential encrypted download requests | Measure p50, p95 response times |
|
||||
|
||||
**Pass criteria**: p95 latency < 30000ms (30s)
|
||||
**Duration**: 3 minutes
|
||||
|
||||
---
|
||||
|
||||
### NFT-PERF-04: User List Endpoint Under Load
|
||||
|
||||
**Summary**: User list endpoint responds within limits when DB has many users.
|
||||
**Traces to**: AC-9
|
||||
**Metric**: Response time
|
||||
|
||||
**Preconditions**:
|
||||
- 500 users in database
|
||||
- Caller is ApiAdmin
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Measurement |
|
||||
|------|----------------|-------------|
|
||||
| 1 | Send 50 GET /users requests (10 concurrent) | Measure p50, p95 response times |
|
||||
|
||||
**Pass criteria**: p95 latency < 1000ms
|
||||
**Duration**: 30 seconds
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user