Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b5f5e5a
docs(intake): per-channel initial-state opt-out for reactive-only cha…
nzneit Aug 1, 2026
05e7b5f
docs(plans): per-channel initial-state opt-out implementation plan
nzneit Aug 2, 2026
6b53d18
docs: allocate R-040 — per-channel initial-state opt-out (specified)
nzneit Aug 2, 2026
72c4f68
feat(model,config): topicOverrides.initialState passthrough (R-040)
nzneit Aug 2, 2026
d10c9b1
feat(registry): resolve initialState onto Channel + the topicOverride…
nzneit Aug 2, 2026
8b67cea
feat(registry): initial-state-cross-service warning at the merge seam…
nzneit Aug 2, 2026
73df52c
test(registry): strengthen cross-service warning assertions (R-040)
nzneit Aug 2, 2026
ba7b4d8
feat(engine): initialState:false gates the L1 floor at the one emissi…
nzneit Aug 2, 2026
4b32d48
docs(plans): unjoin Task 6 heading from separator
nzneit Aug 2, 2026
fed86a3
feat(compose,engine): flag-vs-handler contradiction warn-log, re-chec…
nzneit Aug 2, 2026
82b5369
feat(control-plane,cli): expose initialState:false on /v1/topics + th…
nzneit Aug 2, 2026
d7d4cd8
style: apply biome formatting to R-040 template literals
nzneit Aug 2, 2026
2534caa
docs(specs): contracts §1/§2/§5/§6 + design §7a — initialState: false…
nzneit Aug 2, 2026
5a749c1
docs(guides): wiring §7 — reactive-only channels (R-040)
nzneit Aug 2, 2026
f2cbc63
docs: D-025 — per-channel initial-state opt-out; resolve initial-stat…
nzneit Aug 2, 2026
6dd39ba
fix(registry,docs): scope initialState to toClient records; refresh s…
nzneit Aug 2, 2026
d368609
fix(registry,docs): cross-service warning names the true match winner…
nzneit Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Guidance for any agent (or human) working in this repo. `CLAUDE.md` is a symlink
2. **Fixture-semantics** — *"does this fixture actually test what it claims?"* See the **Fixture quality bar** in `fixtures/asyncapi/README.md` (no vacuous values; claim↔content; full-path/both-direction coverage; internal consistency; negative cases). The validity-only angle misses all of these.

## Status & next
All build tiers and v1 gates are `tested` (37 of 39 requirements): `model` → `broker`/`registry`/`ingestion` → `engine`/`validation` → `scenarios`/`control-plane` → `cli` (the full verb set incl. `up`/`down` process management over the G14 runfile, watch modes, `init`), plus the four cross-cutting v1 gates (R-028–R-031). The open items: the two empirical spikes, `R-006` (WS-fidelity) and `R-007` (capture the browser application's `connect()`), which need the real browser application against `broker/`'s Aedes defaults and are hard gates on calling v1 done; plus `R-033` (`tested`): the `demo-app/` spike-harness webapp + connect fingerprint (`docs/specs/demo-app.md`), which rehearses both spikes — the at-work capture is now a no-app-change procedure (point the real client at offbook, read `offbook logs`). The adoption surface is `tested` (R-034–R-036: README + guides with executable quickstart/cookbook gates, `offbook doctor`, the first-run error audit — docs/specs/adoption.md). The AsyncAPI support range is declared and hardened (R-037–R-039, D-018): **2.0.0–2.6.0, 3.0.0, 3.1.0**, payloads validated under **draft-07**, with the R-028 gate extended over `multi-format.yaml` (3.1.0) and `v2-oldest.yaml` (2.0.0).
All build tiers and v1 gates are `tested` (38 of 40 requirements): `model` → `broker`/`registry`/`ingestion` → `engine`/`validation` → `scenarios`/`control-plane` → `cli` (the full verb set incl. `up`/`down` process management over the G14 runfile, watch modes, `init`), plus the four cross-cutting v1 gates (R-028–R-031). The open items: the two empirical spikes, `R-006` (WS-fidelity) and `R-007` (capture the browser application's `connect()`), which need the real browser application against `broker/`'s Aedes defaults and are hard gates on calling v1 done; plus `R-033` (`tested`): the `demo-app/` spike-harness webapp + connect fingerprint (`docs/specs/demo-app.md`), which rehearses both spikes — the at-work capture is now a no-app-change procedure (point the real client at offbook, read `offbook logs`). The adoption surface is `tested` (R-034–R-036: README + guides with executable quickstart/cookbook gates, `offbook doctor`, the first-run error audit — docs/specs/adoption.md). The AsyncAPI support range is declared and hardened (R-037–R-039, D-018): **2.0.0–2.6.0, 3.0.0, 3.1.0**, payloads validated under **draft-07**, with the R-028 gate extended over `multi-format.yaml` (3.1.0) and `v2-oldest.yaml` (2.0.0). The per-channel initial-state opt-out is `tested` (R-040, D-025): `topicOverrides.<address>.initialState: false` declares a reactive-only channel (no L1 floor; four spec-load warnings; handler-wins warn-log; `GET /v1/topics` marks suppressed channels).

## Working notes
- **Git identity is the user's to set** — don't run `git config user.*` on their behalf. Commit/push **only when asked**.
Expand Down
10 changes: 10 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,13 @@ Append-only. Each decision has a stable never-reused `D-###` id, what was decide
**Obligations**: none.
**From**: the "matters of aedes" follow-up to D-021 (2026-08-01): source reads of `aedes-server-factory@0.2.1`, tarball reads of `aedes@1.1.1` and `aedes-persistence@10.3.1`, and the Bun probe described above.
**Folds into**: package.json, bun.lock, src/broker/index.ts, src/broker/fingerprint.test.ts

### D-025: Per-channel initial-state opt-out — `topicOverrides.<address>.initialState: false`
**Date**: 2026-08-01
**What**: `ServiceConfig.topicOverrides` values grow `initialState?: boolean` (absent ⇒ true; only `false` is meaningful). The registry resolves it onto `Channel.initialState` (toClient records only — the floor never runs elsewhere; no spec-binding tier — the override is the field's only author), and the engine's proactive floor (`materializeAndPublish`) returns before the L1 draw when `initialState === false`, silencing every materialization leg (eager startup, concrete subscribe, `seedInstances`, `reset` republish) while the instance ledger, L2/L3 emissions, wildcard retained replay, and the explicit example surfaces (`GET /v1/topics` examples, `POST /v1/publish {example:true}`) stay untouched. An L3 `initialState` handler still wins, with a compose-root warn-log naming channel + handler, re-run after `POST /v1/specs/refresh`. Four `spec-load` warnings make misconfiguration loud: `override-dangling-key`, `initial-state-on-from-client` (address-scoped — a dual-direction address must not warn), `initial-state-non-boolean` (warn + ignore), `initial-state-cross-service` (exact-address duplicates at the merge seam, naming the true match winner — a same-address fromClient record can win, in which case the floor never runs there and every declaration is dead). `TopicInfo.initialState?: false` appears only when suppressed; the CLI topics views carry a `[no initial state]` marker.
**Why**: Reactive-only channels (error/notification topics) have no initial state; the always-on floor emits a synthetic draw on subscribe that can drive a stateful client into a bad state — behavior a real broker (silent on subscribe for non-retained topics) would never produce. The opt-out keeps the zero-config floor as the default: a retain-keyed "faithful" default was rejected because `retain` resolves `false` at the bottom of the §2 chain, so it would silence nearly every channel out of the box. Adjacent prior art: D-009 declined a tick-varying L1 draw partly because churn "would immediately demand a quiet-toggle" — this is that toggle, per channel, for the subscribe-leg floor D-009 ratified.
**Mitigations / notes**: Retained residue is deliberately out of the flag's reach: an L2/L3/`/publish` retained payload on a flagged channel survives `reset` un-overwritten (contracts §2/§5 caveats; the wiring guide recommends `retain: false` there). The flag is boot-time-only like all of `topicOverrides` (`specs update` re-resolves from the boot-time ServiceConfig and never re-reads services.yaml; changing the flag takes a restart). The F21 compiled-registry cache key omits ServiceConfig — safe while services.yaml is read once per process; the invariant is commented at the cache site (`src/cli/boot.ts`). Parametrized cross-service shadowing (a literal address in one service shadowing a flagged `{param}` address in another) stays a known residual — the merge warning covers exact duplicates only.
**Consequences for earlier entries**: none changed — D-008 (drop-and-surface) and D-009 (no tick leg) stand untouched; this narrows where the floor runs, not how it draws or fails.
**Obligations**: if services.yaml ever becomes re-readable mid-process, grow the F21 cache key with a ServiceConfig fingerprint (see the `src/cli/boot.ts` comment).
**From**: docs/archive/intake/2026-08-01-initial-state-optout.md (design dialog + 4-lens adversarial review, 2026-08-01).
**Folds into**: docs/specs/contracts.md §1/§2/§5/§6, docs/specs/design.md §7a, docs/guides/wiring-your-service.md, REQUIREMENTS.md (R-040), src/model/index.ts, src/config/fixtures/services.yaml, src/registry/index.ts, src/engine/index.ts, src/compose/index.ts, src/control-plane/index.ts, src/cli/index.ts, src/cli/boot.ts
8 changes: 8 additions & 0 deletions REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,14 @@ Every error reachable on the clone→demo→init→wire→up→first-publish pat
**TEST**: src/registry/index.test.ts, test/gate-validation.test.ts, test/upstream-drift.test.ts
`registry/` guards binding-supplied `qos`/`retain` values (falling through the §2 precedence chain on a bad value), reports unknown keys against a hand-authored mqtt operation-binding key set that honors the schema's `x-` vendor-extension pattern and is drift-tested against `@asyncapi/specs` (a devDependency, never imported from `src/`; D-019), reports an mqtt CHANNEL binding as ignored, and reports MQTT-5-only binding fields as unhonored under the MQTT 3.1.1-only constraint.

#### Per-channel initial-state opt-out (reactive-only channels)
**UID**: R-040
**STATUS**: tested
**COVERS**: docs/specs/contracts.md#R-040
**IMPL**: src/model/index.ts, src/registry/index.ts, src/engine/index.ts, src/compose/index.ts, src/control-plane/index.ts, src/cli/index.ts
**TEST**: src/config/index.test.ts, src/registry/index.test.ts, src/engine/index.test.ts, src/compose/initial-state.test.ts, src/control-plane/index.test.ts, src/cli/doctor.test.ts, test/cli-dispatch.test.ts
`topicOverrides.<address>.initialState: false` (services.yaml) declares a reactive-only channel: the registry resolves the flag onto `Channel.initialState` (toClient records only; no spec-binding tier; only `false` is meaningful), the engine's L1 proactive floor skips the channel on every materialization leg (concrete subscribe, eager startup, `seedInstances`, `reset` republish) while the instance ledger, L2/L3 emissions, wildcard retained replay, and the explicit example surfaces stay untouched; an L3 `initialState` handler still wins, with a compose-root warn-log naming channel and handler re-run after a specs refresh; four `spec-load` warnings (`override-dangling-key`, `initial-state-on-from-client`, `initial-state-non-boolean`, `initial-state-cross-service`) make misconfiguration loud; `GET /v1/topics` exposes `initialState: false` on suppressed channels only.

<!--
Seeding is staged (doc-system.md §7). Batch 1 (R-001..R-007) + R-008 (M0) + R-009 (broker tier-1 residual): seeded; reconciled where traces exist (the R-006/R-007 spikes remain open). Batch 2+ (R-010..R-031): the full module/spike/gate carve per D-007 and docs/archive/intake/2026-07-21-batch-2-seeding-carve.md.
What remains unseeded resolves case by case (not bulk):
Expand Down
Loading