Skip to content

Commit acfea01

Browse files
committed
docs: close the plan items that already shipped, record the D86 gap
PLAN.md still listed Q3b and Q3c as open. Both shipped on 2026-08-08 as one PR (ADR D83): the maintainer generated the production signing pair locally and vaulted the private half, and release.yml now pushes the three Pro packages to nuget.org on a v*.*.* tag with no human step in the publish path. A plan that claims shipped work is pending is worse than no plan — it is the first thing read at the start of a session. The backlog's live-items summary had the same drift: it named the CA1068 next-major bundling (§1) and the CI hardening (§2) as open, and both are done — §1 went out in v2.0.0 and §2 is DockerGate. §1 is marked as shipped rather than deleted, since the entry explains WHY those signatures changed. Added §1b for the one thing D86 knowingly did not fix: report editing has no optimistic concurrency, so between GET .../config and PUT another editor can reorder the destinations and the carried address then resolves to the wrong section's credential. The fix is an ETag/If-Match contract — new API surface, so it wants a decision rather than being folded into the ADR that shipped the round-trip. That leaves exactly two open items, both needing the maintainer: §1b and the PostgreSQL timetz zone loss in §5. Docs only — no code, no test changes.
1 parent 1322472 commit acfea01

2 files changed

Lines changed: 37 additions & 11 deletions

File tree

PLAN.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,12 +1376,14 @@ maintainer during Q1) — `LicenseToken` deliberately has no per-product field.
13761376
issue a license**: `LicenseSigner` existed as a library API that nothing called, so the maintainer
13771377
could not produce a key at all. Reuses `LicenseToken`/`LicenseSigner` rather than restating the
13781378
token shape, so an issued key can't drift from what the validator accepts.
1379-
- [ ] **Q3b — Rotate to a production signing key.** Q1's embedded `ProLicense.PublicKeyBase64` is a
1380-
**placeholder whose private half was generated in a chat session** — plaintext, unrotatable,
1381-
unaudited. Run `keygen` locally, move the private key straight into a vault, embed the new public
1382-
key, and ship it **before** any customer license is issued. Blocks Q3c.
1383-
- [ ] **Q3c — Publish the Pro packages.** Lift D30's "not published" posture: the three Pro projects
1384-
are `IsPackable=false` with `PackageLicenseExpression` cleared, so `release.yml` (which packs the
1385-
whole solution) skips them today. Needs a decision on the mechanism — extend `release.yml`, or make
1386-
`pack-pro.yml` push instead of only uploading artifacts — plus the feed/registry choice.
1387-
`NeoReports.Licensing` itself is already packable and MIT, so it rides the normal OSS release.
1379+
- [x] **Q3b — Rotate to a production signing key.** **Done (ADR D83, 2026-08-08.)** Q1's embedded
1380+
`ProLicense.PublicKeyBase64` was a **placeholder whose private half was generated in a chat
1381+
session** — plaintext, unrotatable, unaudited. The maintainer generated the production pair locally
1382+
and vaulted the private half; the public key shipped before any customer license was issued.
1383+
Re-rotating is now a breaking change for issued keys.
1384+
- [x] **Q3c — Publish the Pro packages.** **Done (ADR D83, 2026-08-08.)** D30's "not published"
1385+
posture is lifted: the three Pro projects are packable and `release.yml` pushes them to nuget.org on
1386+
a `v*.*.*` tag, with no human step in the publish path — a guard test is the gate. Shipped together
1387+
with Q3b in one PR because the halves are unsafe apart (publishing under the placeholder key would
1388+
have made the placeholder permanent). `NeoReports.Licensing` is MIT and rides the normal OSS
1389+
release.

docs/STATUS-AND-BACKLOG.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,41 @@ enterprise-readiness and test coverage, and shipped everything actionable.
7171
> as a *different, plausible* date rather than being unrepresentable. Measuring also turned up
7272
> `DateTime.MinValue` silently becoming `1899-12-30`, and a year below 100 throwing out of
7373
> `ToOADate` and **aborting the whole workbook**. One range check closes all four.
74-
> 4. **CA1068-style next-major bundling** (§1) and the CI hardening in §2.
74+
> 4. ~~**CA1068-style next-major bundling** (§1) and the CI hardening in §2.~~**both SHIPPED**:
75+
> §1 went out in **v2.0.0** (2026-08-08) and §2 is done (`DockerGate`, hard-fail under
76+
> `NEOREPORTS_REQUIRE_DOCKER=1`).
77+
>
78+
> **State as of 2026-08-18.** With §1–§4 and §6 closed, exactly two things remain open, and each needs
79+
> the maintainer:
80+
>
81+
> - **§1b — no optimistic concurrency on report editing.** New API surface (`ETag` / `If-Match` /
82+
> `412`); the concurrent-editor window can restore the wrong section's credential.
83+
> - **§5 — PostgreSQL `timetz` drops its zone.** Needs a `Time`/`TimeTz` split in the frozen
84+
> `ColumnType` enum plus its own cursor-encoding decision, so it is a next-major item with a design
85+
> question attached, not a cast.
7586
7687

77-
### 1. Next-major breaking cleanup (needs a 2.0 line)
88+
### 1. Next-major breaking cleanup **SHIPPED in v2.0.0 (2026-08-08)**
7889
- **Remove the never-thrown ABI exceptions** — already done in #228, tagged for the next major.
7990
- **CA1068: `CancellationToken` not last** in three **public** health signatures — **done**: the
8091
token was moved to last (after `pingSql`/`content`, both defaulted) in `AdoSourceHealth.PingAsync`,
8192
`AdoSourceHealth.CheckConnectionStringAsync` and `HttpHealthProbe.SendAsync`, and all callers
8293
updated. Source-breaking for positional callers, so tagged **next-major** in `CHANGELOG.md`
8394
(Changed → breaking, public API) alongside the #228 removal.
8495

96+
### 1b. Report editing: no optimistic concurrency on PUT (ADR D86, 2026-08-18)
97+
98+
Two editors open the same report; the second reorders its destinations and saves; the first then saves
99+
a placeholder addressed `destinations[0]`, which resolves against the **reordered** stored document and
100+
restores the wrong section's credential. The carried address is exactly what makes a *single*-editor
101+
reorder safe, and it cannot see a change made on the stored side between the `GET .../config` and the
102+
`PUT`.
103+
104+
Not fixed with D86 because the fix is new API surface — an `ETag` on `GET /reports/{name}/config` and
105+
`If-Match` on `PUT /reports/{name}`, answering `412` when they disagree — and that ADR was already the
106+
secrets round-trip. Single-worker, single-maintainer v1 (architecture rule 6) makes concurrent editors
107+
unlikely, not impossible. Recorded so the next change to these endpoints starts from it.
108+
85109
### 2. CI hardening
86110
- **Fail (not skip) the Testcontainers integration tests when Docker is absent in CI.****done**:
87111
the five container `ServerFixture`s now swallow a start failure only through an exception filter,

0 commit comments

Comments
 (0)