docs(plans): Batch B close-out — Phase 1A security register, issue queue, tracking
This commit is contained in:
@@ -22,7 +22,7 @@ posture).
|
||||
|
||||
| Phase | Scope | Branch | Status | PR / merge SHA | Live gate |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | Proto + mappers + goldens; both auth interceptors; `IMeshPskProvider`; options/validators (+`Grpc` value, SplitTopology relaxation); rig dark config; stale-comment fixes; FleetStatus issue filed | `feat/grpc-phase0-contract-auth` | **IN PROGRESS** — Batch A (0.1 + 0.3) done @ `aa21f61f` + close-out `09db9e47`; 0.2 (mapper + 27 wire-level goldens) done @ `545413a8`; all reviews spec ✅ + code ✅, 0 critical/important; Batch B (0.4 + 0.5) next | — | PENDING |
|
||||
| 0 | Proto + mappers + goldens; both auth interceptors; `IMeshPskProvider`; options/validators (+`Grpc` value, SplitTopology relaxation); rig dark config; stale-comment fixes; FleetStatus issue filed | `feat/grpc-phase0-contract-auth` | **IN PROGRESS** — Batch A (0.1 + 0.3) @ `aa21f61f`; 0.2 (mapper + 27 wire-level goldens) @ `545413a8`; Batch B (0.4 + 0.5) @ `fbda015c`/`68a416e4` + review fixes `24e55d33`/`a4631410`; all reviews spec ✅ + code ✅ (Batch B code review on Opus, fix loop closed); only 0.6 remains | — | PENDING |
|
||||
| 1A | Central→node command plane (`NodeCommandService` on the Telemetry listener; `MeshGrpcCommandDialer`; third mode branch in `CentralCommunicationActor`) — **worktree A** | `feat/grpc-node-command` | NOT STARTED | — | PENDING |
|
||||
| 1B | Node→central ack plane (`CentralCommandService` on the ConfigServe listener; `GrpcApplyAckSender`; third branch in `NodeCommunicationActor`) — **worktree B**, rebases on 1A | `feat/grpc-central-ack` | NOT STARTED | — | PENDING |
|
||||
| 2 | Baseline reproduction on pre-flip build, then full-rig cutover (`OTOPCUA_MESH_MODE` default → `Grpc`) + soak + kill drills | (rig commit) | NOT STARTED | — | PENDING |
|
||||
@@ -48,6 +48,39 @@ phase DoD — PR + merge are user-owned.**
|
||||
| 2026-08-06 | `AlarmCommandMessage.operation` is a proto `string`, NOT an enum — the CLR `AlarmCommand.Operation` is an unconstrained string, and an enum would silently decode unknown operations to `UNSPECIFIED` (the drop class this migration removes). Plan T0.1's "AlarmCommand verb" enum wording was a plan defect; only `ApplyOutcome` carries the enum-0 rule. T0.2's goldens must pin pass-through of an unrecognized operation string | proto comment + this log (T0.1 review) |
|
||||
| 2026-08-06 | `correlation_id` lives on the envelope AND as inner fields on the three commands whose CLR record carries one — each command round-trips independently in the T0.2 mapper; the envelope covers `AlarmCommand` (no CLR correlation member). For the three, the inner field is authoritative on decode | proto comment + this log (T0.1 review) |
|
||||
|
||||
## Phase 1A security register (from the Batch B Opus code review — HARD GATE: all four close BEFORE `CentralCommandService` is mapped)
|
||||
|
||||
1. **Known-cluster-set membership gate in `TryGetPsk`** — central rebuilds its cluster set from
|
||||
`ClusterNode` rows every `ContactRefreshSeconds`; refuse a cluster id outside that set in
|
||||
O(1) with no store touch. Closes BOTH open review findings at once: (a) the timing oracle
|
||||
that currently defeats the uniform-denial anti-enumeration text (unknown cluster pays a
|
||||
store read on every probe because negatives are deliberately never cached; known cluster is
|
||||
cached — a stable remotely-measurable distinguisher), and (b) the pre-auth resource
|
||||
exhaustion / audit-flood vector (each distinct forged `x-otopcua-cluster` header costs a
|
||||
secret-store read + a `secret.resolve.not-found` audit row into `ConfigAuditLog` via the
|
||||
audit singleton, attacker-chosen `Target` values, before any credential check). Namespace
|
||||
confinement (`OT-MESH-PSK-` prefix + `SecretName` allow-list) means no disclosure risk —
|
||||
this is flood/oracle only.
|
||||
2. **`bool Matches(string clusterId, string presented)` on `IMeshPskProvider`** — keep
|
||||
`FixedTimeEquals` inside the provider so a future verify-side caller cannot write `==` and
|
||||
have it compile. Do before 1B adds the dial-side caller.
|
||||
3. **DI-shape pin test** — `IMeshPskProvider` is `hasAdmin`-only and its pairing with the
|
||||
central interceptor is by convention; a driver-only node injecting it throws on first call,
|
||||
not at boot. Add a Host.IntegrationTests check (admin host resolves it; driver-only host
|
||||
does NOT) when Phase 1 maps the services.
|
||||
4. **Integration gate over real Kestrel h2c** — both new interceptors have unit coverage only;
|
||||
the one-public-ctor reflection test approximates but cannot prove the interceptor is
|
||||
actually invoked. Wire-probe in the 1A/1B live gates (already in the live-gate tables).
|
||||
|
||||
Non-gating notes from the same review: central-side unseeded-PSK is invisible until a deploy
|
||||
stalls (consider a "PSK seeded for every known cluster?" `/hosts`/health surface — the
|
||||
#521/#541 shape); `SecretName` lowercases + bans `_`, so a cluster id like `Site_A` can never
|
||||
resolve from the store and must use `ClusterPsks` — MUST be documented in T0.6's
|
||||
`docs/Configuration.md` stub (rig ids `MAIN`/`SITE-A`/`SITE-B` are fine); sibling interceptors
|
||||
still use `IsNullOrEmpty` (whitespace key opens them) — family-wide backport is out of this
|
||||
program's scope, file alongside the FleetStatus issue in T0.6; consider a non-throwing counter
|
||||
metric if the poison-guard silent catch ever fires in production.
|
||||
|
||||
## Phase 1 dispatch notes (from Task 0.2 reviews — fold into the 1A/1B implementer prompts)
|
||||
|
||||
- The alarm arm of `FromEnvelope` necessarily discards the envelope correlation
|
||||
@@ -84,6 +117,7 @@ Checks a gate could not drive, named + carried to the next gate (proportionate-g
|
||||
| Issue | What | Status |
|
||||
|---|---|---|
|
||||
| *(Gitea # TBD in Phase 0, T0.6)* | `FleetStatusBroadcaster` derives "fleet" from central's own gossip ring — silently central-pair-blind since mesh Phase 6; pre-existing, transport-independent, out of this program's scope | to file |
|
||||
| *(Gitea # TBD in Phase 0, T0.6)* | The three pre-existing auth interceptors (`TelemetryStream`/`ConfigServe`/`LocalDbSync`) treat a whitespace-only key as SET (`IsNullOrEmpty`), so a whitespace `Telemetry:ApiKey` etc. opens the endpoint to a whitespace bearer; the two new mesh interceptors use `IsNullOrWhiteSpace`. Backport is a behaviour change to shipped endpoints — own issue, out of this program's scope | to file |
|
||||
|
||||
## Known constraints carried from the research (do not rediscover)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
{"id": 0, "subject": "Task 0.1: mesh_transport.proto + contract lock", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "standard", "parallelizableWith": ["0.3 (batch, same agent)"], "status": "completed", "commit": "c0ccfa27", "review": "spec ✅ + code ✅"},
|
||||
{"id": 1, "subject": "Task 0.2: DTO mapper + round-trip goldens", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "high-risk", "parallelizableWith": [], "status": "completed", "blockedBy": [0], "commit": "545413a8", "review": "spec ✅ + code ✅ (serial)"},
|
||||
{"id": 2, "subject": "Task 0.3: MeshTransportOptions + validators + SplitTopology relaxation", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "standard", "parallelizableWith": ["0.1 (batch, same agent)"], "status": "completed", "commit": "aa21f61f", "review": "spec ✅ + code ✅"},
|
||||
{"id": 3, "subject": "Task 0.4: node-side MeshCommandAuthInterceptor", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "high-risk", "parallelizableWith": ["0.5 (batch, same agent)"], "status": "pending", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "Task 0.5: IMeshPskProvider + CentralCommandAuthInterceptor", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "high-risk", "parallelizableWith": ["0.4 (batch, same agent)"], "status": "pending", "blockedBy": [2]},
|
||||
{"id": 3, "subject": "Task 0.4: node-side MeshCommandAuthInterceptor", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "high-risk", "parallelizableWith": ["0.5 (batch, same agent)"], "status": "completed", "blockedBy": [2], "commit": "fbda015c + fixes 24e55d33/a4631410", "review": "spec ✅ + code ✅ (opus, fix loop closed)"},
|
||||
{"id": 4, "subject": "Task 0.5: IMeshPskProvider + CentralCommandAuthInterceptor", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "high-risk", "parallelizableWith": ["0.4 (batch, same agent)"], "status": "completed", "blockedBy": [2], "commit": "68a416e4 + fixes 24e55d33/a4631410", "review": "spec ✅ + code ✅ (opus, fix loop closed); 4 items gated on Phase 1A — see tracking §Phase 1A security register"},
|
||||
{"id": 5, "subject": "Task 0.6: rig config + FleetStatus issue + docs stub + Phase 0 gate", "phase": "0", "branch": "feat/grpc-phase0-contract-auth", "classification": "small", "parallelizableWith": [], "status": "pending", "blockedBy": [0, 1, 2, 3, 4]},
|
||||
{"id": 6, "subject": "Task 1A.1: NodeCommandGrpcService on the telemetry listener", "phase": "1A", "branch": "feat/grpc-node-command", "classification": "standard", "parallelizableWith": ["1B.1 (other worktree)"], "status": "pending", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 1A.2: MeshGrpcCommandDialer + third branch in CentralCommunicationActor", "phase": "1A", "branch": "feat/grpc-node-command", "classification": "high-risk", "parallelizableWith": ["1B.2 (other worktree)"], "status": "pending", "blockedBy": [6]},
|
||||
|
||||
Reference in New Issue
Block a user