You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: DECISIONS.md
+39Lines changed: 39 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -189,6 +189,11 @@ Removed from v1 vs. Ch. 16: `IRetryPolicy`, `IExceptionClassifier`, `IAuthProvid
189
189
| D18 | Jobs packaging | Base package `NeoReports.Jobs` (shared worker `ReportJobWorker` + `InMemoryJobStore` + `InMemoryJobScheduler` + `NoOpCheckpointStore` + DI) and `NeoReports.Jobs.Hangfire` extending it. Avoids a 3rd package just to share the worker; the plan's "InMemory" lives in the base package |
190
190
| D19 | Worker and cancellation |`ReportJobWorker` is the single lifecycle core (running→completed/failed/cancelled), used by both schedulers. Idempotent restart (AC-16) comes from the pipeline (per-job temp + upload only at the end; temp cleanup is best-effort and never changes the status). InMemory: cancels via a per-job `CancellationTokenSource`. Hangfire: `CancellationToken` injected into the invoker; `CancelAsync` deletes the job; in-process id↔hangfire-id map (single-server; cross-restart is post-MVP, D2). Parameters travel as JSON (`JobParameters`, dates as ISO-8601 round-trip) |
191
191
| D20 | Endpoints + artifact store |`MapNeoReports("/api")` (Minimal API). Download/sync need to retain the file beyond the pipeline's temp: `IReportArtifactStore` (+ `FileSystemArtifactStore`) in **Core** (an engine concern, not a plugin contract → outside `Abstractions`); the `ReportRunner` saves into it only if registered (opt-in via DI). Sync = single-output (multi → 400, AC-10); multi-output on download becomes a zip. Job status serialized as a **string** (`JsonStringEnumConverter` on the DTOs, without touching the enum in `Abstractions`). Auth inherits from the host (`RequireAuthorization` optional; no auth chain). Outputs with the same extension (e.g. two CSVs) get a **disambiguated** file name in the pipeline (`name.csv`, `name-2.csv`) so they don't collide on disk/artifact and can go together in the zip |
192
+
| D21 | Dynamic path | Reopened in v2. Row type = positional `ReportRecord` (`object?[]` + `ReportSchema`), not a dictionary; reuses the whole v1 pipeline because the writer edge already speaks `object?[]` + schema. Config (JSON) + `IReportConfigParser` + JsonLogic filter return additively to `Abstractions` (SemVer-minor) |
193
+
| D22 | Multi-sheet XLSX | Planned for v2 (Epic B). One workbook, several named sheets from different filters/sources. Monetization TBD. Design recorded before coding |
194
+
| D23 | Multi-source | Planned for v2 (Epic B). Any report assembled from several sources (join/enrich). Likely paid. Design recorded before coding |
195
+
| D24 | UI ordering | Blazor UI is the **last** v2 epic (after dynamic path + multi-source + a user-validation gate), per the maintainer. Always built from the Claude Design handoff, never invented |
196
+
| D25 | v2 additivity | Every v2 addition is additive and SemVer-minor on `Abstractions`; v1's frozen surface is never broken, only extended. Removing anything stays SemVer-major |
192
197
| — | Design | Already done in Claude Design; export per the handoff; UI post-MVP |
193
198
194
199
---
@@ -225,3 +230,37 @@ Removed from v1 vs. Ch. 16: `IRetryPolicy`, `IExceptionClassifier`, `IAuthProvid
225
230
- The `Schema` declared by the source is a minimal placeholder — the pipeline's projection uses the builder's columns (D1), not the source's schema.
226
231
227
232
**Why.** Keyset with an opaque `@cursor` and connection-per-page satisfies AC-2 (reads all pages in order, without skipping/repeating) and keeps constant memory, leaving checkpoint/multi-worker viable later without rework. Connection-by-name is configuration sugar that doesn't change the contract — cut from v1.
233
+
234
+
---
235
+
236
+
# v2 decisions (post-MVP — reopening scope)
237
+
238
+
> v1 (1.0.0) is published. The decisions below reopen scope **deliberately and additively**.
239
+
> Locked order with the maintainer: **Epic A (dynamic path) → Epic B (multi-source / multi-sheet) → validation gate → Epic C (Blazor UI) last.** Nothing here breaks v1's frozen `Abstractions` (D25) — it only extends it.
**Decision.** The dynamic (config-driven) path returns in v2. It does **not** revisit rule 1 ("never a dictionary as the row type"): the dynamic row is a **positional `ReportRecord`** — an `object?[]` aligned to a declared `ReportSchema`, exactly the shape the writer edge already consumes (D1). The pipeline stays generic over `T`; the dynamic path simply runs with `T = ReportRecord`. Writers, destinations, jobs and resilience are **untouched**.
244
+
245
+
-`ReportRecord` and a `ReportRecord``IBatchSource` return to `Abstractions` (additive, SemVer-minor — D25).
246
+
- Config is **JSON**, parsed by `IReportConfigParser` into a runnable registration (the same internal model `AddReport<T>` produces). Columns/source/outputs/destinations/retry/onFailure mirror the fluent builder one-to-one.
247
+
- The dynamic **filter** is a **JsonLogic** expression compiled to `Func<ReportRecord,bool>` (D10's deferred half). Typed delegates remain the code-first option; JsonLogic is the dynamic one. DynamicLinq stays out unless a concrete need appears.
248
+
- No new execution path: a config report is just another registration. Same `ReportRunner`, same jobs, same endpoints.
249
+
250
+
**Why.** Reusing the positional edge means the dynamic path is *configuration on top of the existing engine*, not a parallel engine. Constant memory, resilience and the writer contract all carry over for free. This is the single biggest reason `Abstractions` was kept minimal-but-open in v1.
251
+
252
+
## D22 — Multi-sheet XLSX (v2 / Epic B — design before code)
253
+
254
+
**Decision (directional).** A single XLSX workbook with several **named sheets**, each fed by a different filter (and later a different source), without breaking the single-pass read (D14's ClosedXML in-memory model already holds the whole workbook, so multi-sheet fits naturally). Exact API and whether it is a **paid** capability are **TBD with the maintainer**; a concrete decision is recorded here before coding. Tracked in `memory/open-questions.md`.
255
+
256
+
## D23 — Multi-source reports (v2 / Epic B — design before code)
257
+
258
+
**Decision (directional).** Any report assembled from **several sources** (join/enrich) into one output — likely the headline **paid** feature. Monetization model (free core vs paid) and the join semantics are **TBD with the maintainer**; recorded before coding. Tracked in `memory/open-questions.md`.
259
+
260
+
## D24 — UI is the last v2 epic (Blazor + Claude Design handoff)
261
+
262
+
**Decision.** The Blazor Server + MudBlazor UI is built **last** in v2, after the dynamic path, multi-source, and a real user-validation gate — by the maintainer's explicit ordering. It is built **only** from the Claude Design handoff (`tokens.css`, `components.html`, per-screen `.html`, `handoff.md`); design is never invented or diverged from the Design System tokens.
263
+
264
+
## D25 — v2 additivity / SemVer discipline
265
+
266
+
**Decision.** Every v2 addition to `Abstractions` is **additive** (new types/members) and ships as **SemVer-minor**. v1's published surface is never changed in place; removing or changing a signature would be SemVer-major and is avoided. External plugins built against 1.x keep compiling against any 1.y.
Copy file name to clipboardExpand all lines: PLAN.md
+58-2Lines changed: 58 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -67,6 +67,62 @@ Small, independent PRs, in order. Each one closes with green tests and closes on
67
67
68
68
---
69
69
70
-
## Post-MVP (do not start before validating with users)
70
+
#NeoReports — Implementation Plan (v2 / post-MVP)
71
71
72
-
Likely order once there's traction: dynamic path (config + JsonLogic) → **Blazor UI from the Claude Design handoff** → variants/pipeline → multi-worker + mid-job resume → remaining sources/formats/destinations. Reminder: any UI work always starts from the Claude Design deliverables, never from invented design.
72
+
v1 shipped (PR 0–8, published to NuGet as 1.0.0). v2 reopens scope deliberately:
73
+
every epic below was kept *possible* by the frozen `Abstractions` (D1/D9 — "close no
74
+
door"), so each addition is an **additive, SemVer-minor** change, never a rework.
75
+
76
+
**Order (locked with the maintainer):** Epic A (dynamic path) → Epic B (multi-source /
77
+
multi-sheet) → validation gate → **Epic C (Blazor UI) last**. Same rules as v1: one
78
+
small PR per item, green tests, and a recorded ADR entry before any out-of-v1-scope
79
+
code. See **D21–D25** in `DECISIONS.md`.
80
+
81
+
## Epic A — Dynamic path (config-driven reports)
82
+
83
+
Run reports from JSON config with no compile-time POCO, reusing the *entire* existing
84
+
pipeline. The row type is a positional `ReportRecord` (`object?[]` aligned to a declared
85
+
`ReportSchema`); the writer edge already speaks `object?[]` + schema (D1), so writers,
86
+
destinations and jobs are untouched. See **D21**.
87
+
88
+
-[ ]**A1 — `ReportRecord` + dynamic pipeline.** Reintroduce the positional row type
89
+
(Abstractions, additive) and a `ReportRecord``IBatchSource`. Prove the existing
90
+
pipeline runs end-to-end with `T = ReportRecord` against an in-memory source.
91
+
**Acceptance:** dynamic rows reach CSV/XLSX byte-identically to the typed path for the
0 commit comments