Build(deps): Bump yaml from 2.7.1 to 2.8.0#57
Closed
dependabot[bot] wants to merge 1 commit into
Closed
Conversation
Bumps [yaml](https://github.com/eemeli/yaml) from 2.7.1 to 2.8.0. - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](eemeli/yaml@v2.7.1...v2.8.0) --- updated-dependencies: - dependency-name: yaml dependency-version: 2.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
|
The |
Contributor
Author
|
Superseded by #105. |
joelteply
added a commit
that referenced
this pull request
May 30, 2026
…position handler
Per Joel:
> "Yes please do." (re: chat/send next, the dual-write composition
> stress-test)
chat/send is the chat module's first multi-cross-module-call handler:
chat → data (persist) then chat → airc (publish). The migration forces
the substrate to commit on partial-failure semantics that the
single-call handlers (chat/poll, data/query cursors) never had to face.
# Why this PR pushes the envelope
Two effects across two modules with no kernel-level transaction:
| data | airc | handler returns |
|------|------|----------------------------------------------------------|
| ok | ok | `Ok(result with message_id + event_id)` |
| ok | fail | `Ok(result with message_id, event_id=None, warning=...)` |
| fail | — | `Err(...)` — no airc publish attempted |
The (ok, fail) cell is the substrate-shaped kink the design needed
proof of. An airc-only failure is NOT command-level failure: the
message IS in the local store, consumers see it via chat/poll, a
future retry/sync mechanism heals the broadcast. Surfacing this as
`Err` would tell the caller "your write didn't happen" — which is
wrong; half of the write did. The `warning` field is the right shape:
**degraded success**.
# Design decisions this PR locks in
## Ordering: data first, airc second
Local persistence is the ground truth. The reverse order would risk
publishing a message to peers that this node doesn't know about — a
peer reading back that message would find no local record. With
data-first, the worst case is *we have the message but peers don't* —
a degradation, not a divergence.
A test (`send_calls_data_before_airc`) pins the order via a shared
call-log Mutex. If the ordering ever flips, the bad-divergence case
becomes reachable; the test catches it.
## airc-fail returns Ok+warning, not Err
The `warning` field names the failing surface, surfaces the
underlying error (so callers can diagnose), confirms the message
wasn't lost ("stored locally"), and includes the message id (so
callers can correlate logs). Tested:
- `send_with_airc_failure_returns_warning_and_null_event_id`
## data-fail short-circuits — airc NEVER called
A test tracks airc invocations via `AtomicUsize` and asserts ZERO
calls when data failed. Same invariant for the subtle
data-returns-success=false path:
- `send_with_data_executor_failure_propagates_as_err_and_skips_airc`
- `send_with_data_success_false_propagates_as_err_and_skips_airc`
## Wire contracts pinned by tests, not just docs
Two tests pin the on-the-wire shape chat hands to data + airc. If
either downstream module changes its parse expectations, these tests
catch the drift even though chat doesn't import their typed structs
(coupling lives at the command/wire surface, not at the Rust type
level — the substrate's whole point):
- `send_writes_chat_messages_collection_with_canonical_entity_shape`
→ pins ChatMessageEntity layout (id/roomId/senderId/timestamp/
content/replyToId/metadata.source/status, ISO-8601 UTC timestamps)
- `send_envelope_matches_airc_publish_wire_shape`
→ pins AircRealtimeEnvelope layout (eventId/roomId/sourceId/
createdAtMs/delivery, tagged payload variant with
schema=chat_transcript and inline message data)
# What this PR explicitly does NOT do
- **Does NOT migrate** chat/analyze or chat/export (still fail-loud
stubs naming issue #57).
- **Does NOT register `ChatModule` at runtime startup.** Same reasoning
as #1489 — until ALL chat commands are migrated, registration would
break the remaining stubs at runtime.
- **Does NOT do sender/room name resolution.** Kernel command takes
pre-resolved UUIDs; resolution stays in TS browser/CLI (or a future
channel/resolve + user/resolve pair). Same compositional principle
chat/poll established.
- **Does NOT externalize media.** Text-only for this migration; media
paths (base64 → blob storage via MediaBlobService) are their own
kink-finder.
- **Does NOT do vision pre-warming.** Fire-and-forget visual descriptor
generation is deferred to vision-module migration.
- **Does NOT thread reply-to into threading metadata fully.** The
`replyToId` field flows through to the stored entity + the airc
payload, but the richer thread { threadId, replyCount, lastReplyAt }
shape is deferred until the thread-tracking design is its own scope.
- **Does NOT solve idempotency.** A retried chat/send (network glitch
on the caller side) currently produces two stored messages —
matches today's TS behavior. Future PR can add a `client_dedup_id`
param + TTL'd dedup map; the substrate is ready for it but the
design is its own scope.
# Substrate kinks this PR surfaced
(For potential future refinement — none blocking, all annotated):
1. **No envelope construction helpers for cross-module calls.** Chat
hand-rolls `json!({ "envelope": {...} })` for airc. If many
modules call airc/realtime-publish from Rust, an
`airc::realtime_publish_envelope(builder...) -> Value` helper in
the airc-shared module would distill this. Out of scope here; flag
for if a second consumer appears.
2. **No typed cross-module command call.** Chat calls
`executor.execute_json("data/create", json!({...}))` with raw JSON
and parses the response back via `.get("success")`. A typed
`executor.execute_typed::<DataCreateParams, DataCreateResult>(...)`
would catch wire-shape drift at compile time. Same kink the
handle_id_or_legacy refinement (#1491) solved for a different
surface — flag for potential future refinement after we see if it
reappears with a second consumer.
3. **No transaction primitive across modules.** Today: chat hand-codes
the data-first / airc-best-effort ordering inline. If many modules
need similar dual-write composition, a substrate-level
`dual_write!(primary => ..., best_effort => ...)` macro could
centralize the partial-failure pattern (warning construction,
ordering enforcement, etc.). Flag for if/when a second consumer
appears.
# Tests (28/28 pass)
Pre-existing chat/poll (17, all unchanged behavior):
- StubDataModule extended to dispatch by command — back-compat
`query_only` constructor preserves chat/poll's existing tests
verbatim
- All 17 chat/poll tests still pass through the refactored stub
New chat/send (11):
- `send_happy_path_returns_message_id_and_event_id`
- `send_with_airc_failure_returns_warning_and_null_event_id` ←
partial-failure cell
- `send_with_data_executor_failure_propagates_as_err_and_skips_airc`
← hard-failure + ordering invariant
- `send_with_data_success_false_propagates_as_err_and_skips_airc` ←
the subtle data-success-false path
- `send_calls_data_before_airc` ← ordering invariant via call log
- `send_writes_chat_messages_collection_with_canonical_entity_shape`
← wire contract to data
- `send_envelope_matches_airc_publish_wire_shape` ← wire contract to
airc
- `handle_command_routes_chat_send_through_typed_envelope` ← typed
envelope round-trip end-to-end
- `handle_command_chat_send_accepts_legacy_collaboration_prefix` ←
back-compat
- `unmigrated_commands_fail_loud_and_name_followup` (updated to
exclude chat/send now that it's migrated)
ts-rs bindings (2):
- `export_bindings_chatsendparams`
- `export_bindings_chatsendresult`
# Wire output
```
shared/generated/chat/
├── ChatPollParams.ts
├── ChatPollResult.ts
├── ChatSendParams.ts // { roomId, senderId, text, replyToId? }
├── ChatSendResult.ts // { messageId, eventId?, warning? }
└── index.ts
```
# References
- [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md)
§5 (composition: commands call commands)
- PR #1489 (ChatModule + chat/poll — the first migration)
- PR #1490 (data/query cursors — single-call HandleRef stress test)
- PR #1491 (substrate refinements distilled from #1490)
- Issue #57 (migration tracker)
- Issue #64 (this migration)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…ll in Rust) Per Joel: > "Chat is gonna be airc man. So that's extracted period. Chat is of > course a bonafide command though. Do not cheapen it. So the > commands need to be or at least some to start, entirely rust." The split: - **Substrate** (delivery, pub/sub, peers, signing) → airc - **Commands** (chat/send, chat/poll, chat/analyze, chat/export) → Continuum kernel-level ServiceModule, this PR This is the FIRST real module migration from a TS command to a Rust `ServiceModule`. The chat module exercises every pattern the substrate floor PRs established: - `ServiceModule` trait - `CommandResult` cell shapes (PR #1485) - `CommandRequest<P>` / `CommandResponse<T>` envelopes (PR #1486) - Cross-module dispatch via the kernel executor (chat calls `data/query` — neither knows the other beyond the command surface) - Scaffold shape that GeneratorModule (PR #1487) produces - ts-rs typed wire boundary # Scope of THIS PR Only `chat/poll` ships in Rust. The other three commands (`chat/send`, `chat/analyze`, `chat/export`) are wired into the dispatch table as fail-loud stubs that name issue #57 as the migration tracker. Their TS implementations stay live on canary — consumers see no regression. Why staged: `chat/poll` is the cleanest outlier (pure read, no airc, no media side-effects) which lets us validate the cross-module call pattern (chat → data via the kernel executor) without dragging substrate + media into the first migration. Subsequent commands fold in real behavior incrementally. # Module structure ``` src/workers/continuum-core/src/modules/chat/ ├── mod.rs // ChatModule, ServiceModule impl, poll handler └── types.rs // ChatPollParams, ChatPollResult (ts-rs exports) ``` `mod.rs` follows the GeneratorModule template exactly — `pub struct ChatModule`, `impl ServiceModule`, `ModuleConfig` declaring both `chat/` and `collaboration/chat/` prefixes (legacy back-compat), the `handle_command` dispatch arms, the typed envelope pattern. `types.rs` carries `#[derive(TS)]` on both param + result types, exporting to `shared/generated/chat/`. Wire shape: camelCase, optional fields elided when absent. `CHAT_MESSAGES_COLLECTION` constant + `DEFAULT_POLL_LIMIT` constant centralized here. # Cross-module call pattern `chat/poll` doesn't open a database connection — it calls `data/query` via the kernel executor. Chat is blind to which adapter implements the storage; the data module routes per its own resolution rules. This is exactly MODULE-ARCHITECTURE.md §5: commands call commands; modules don't know about each other beyond the command surface. The chat module accepts an optional executor override at construction (`with_executor(...)`) — production uses the kernel-global, tests inject their own. That lets every test in this module spin up a fresh registry with a `StubDataModule` and exercise the full cross-module path without trampling the global `OnceLock`. # Tests (17/17 pass) types.rs (5): - `poll_params_defaults_to_all_none` - `poll_params_round_trip_through_json_with_camel_case` - `poll_params_accepts_missing_fields` - `poll_result_omits_after_message_id_when_none` - `poll_result_includes_after_message_id_when_set` mod.rs (10): - `config_advertises_both_command_prefixes` - `unknown_command_returns_loud_error_naming_supported_commands` - `unmigrated_commands_fail_loud_and_name_followup` (all 6 stub surfaces: chat/send, chat/analyze, chat/export, + collaboration/ prefixed versions) - `poll_returns_empty_result_when_data_module_returns_no_messages` - `poll_without_anchor_queries_data_desc_and_returns_chronological` - `poll_with_room_id_passes_filter_to_data_module` - `poll_with_anchor_looks_up_timestamp_then_filters_gt` - `poll_with_anchor_returns_err_when_anchor_missing` - `handle_command_routes_chat_poll_through_typed_envelope` - `handle_command_accepts_legacy_collaboration_prefix` ts-rs exports (2): - `export_bindings_chatpollparams` - `export_bindings_chatpollresult` # Wire output ``` shared/generated/chat/ ├── ChatPollParams.ts // { roomId?, afterMessageId?, limit? } ├── ChatPollResult.ts // { messages, count, afterMessageId? } └── index.ts // barrel ``` The master barrel (`shared/generated/index.ts`) gains `export * from './chat'`. Other barrel drift (runtime, persona) is PR #1488's territory — left untouched here so the two PRs don't fight over the same lines. # What this PR explicitly does NOT do - Does NOT migrate `chat/send`, `chat/analyze`, `chat/export`. Stubs name issue #57. Each is a future PR. - Does NOT register `ChatModule` at runtime startup. Adding `runtime.register(Arc::new(ChatModule::new()))` in `ipc::start_server` would route ALL `chat/*` traffic through this module — including the stubbed commands which would then break. Registration happens in the same PR that fills in the first real `chat/send` so consumers see one atomic change. Today: chat module exists, is tested, but the legacy TS path still owns every chat command at runtime. - Does NOT do room-name resolution. The kernel command takes an already-resolved `roomId`; name → id stays in TS browser/CLI callsites (or a future `channel/resolve` command). Keeps the kernel command compositional with the future channel module. - Does NOT auto-rebuild the master barrel from outside the chat directory — that drift was already on canary and is PR #1488's job. This PR only adds the `chat` entry. # References - [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md) §5 (composition: commands call commands) - PR #1486 (CommandRequest/Response envelopes — used here) - PR #1487 (GeneratorModule — chat follows its template) - Issue #57 (migration tracker — stubs name it) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…position handler
Per Joel:
> "Yes please do." (re: chat/send next, the dual-write composition
> stress-test)
chat/send is the chat module's first multi-cross-module-call handler:
chat → data (persist) then chat → airc (publish). The migration forces
the substrate to commit on partial-failure semantics that the
single-call handlers (chat/poll, data/query cursors) never had to face.
# Why this PR pushes the envelope
Two effects across two modules with no kernel-level transaction:
| data | airc | handler returns |
|------|------|----------------------------------------------------------|
| ok | ok | `Ok(result with message_id + event_id)` |
| ok | fail | `Ok(result with message_id, event_id=None, warning=...)` |
| fail | — | `Err(...)` — no airc publish attempted |
The (ok, fail) cell is the substrate-shaped kink the design needed
proof of. An airc-only failure is NOT command-level failure: the
message IS in the local store, consumers see it via chat/poll, a
future retry/sync mechanism heals the broadcast. Surfacing this as
`Err` would tell the caller "your write didn't happen" — which is
wrong; half of the write did. The `warning` field is the right shape:
**degraded success**.
# Design decisions this PR locks in
## Ordering: data first, airc second
Local persistence is the ground truth. The reverse order would risk
publishing a message to peers that this node doesn't know about — a
peer reading back that message would find no local record. With
data-first, the worst case is *we have the message but peers don't* —
a degradation, not a divergence.
A test (`send_calls_data_before_airc`) pins the order via a shared
call-log Mutex. If the ordering ever flips, the bad-divergence case
becomes reachable; the test catches it.
## airc-fail returns Ok+warning, not Err
The `warning` field names the failing surface, surfaces the
underlying error (so callers can diagnose), confirms the message
wasn't lost ("stored locally"), and includes the message id (so
callers can correlate logs). Tested:
- `send_with_airc_failure_returns_warning_and_null_event_id`
## data-fail short-circuits — airc NEVER called
A test tracks airc invocations via `AtomicUsize` and asserts ZERO
calls when data failed. Same invariant for the subtle
data-returns-success=false path:
- `send_with_data_executor_failure_propagates_as_err_and_skips_airc`
- `send_with_data_success_false_propagates_as_err_and_skips_airc`
## Wire contracts pinned by tests, not just docs
Two tests pin the on-the-wire shape chat hands to data + airc. If
either downstream module changes its parse expectations, these tests
catch the drift even though chat doesn't import their typed structs
(coupling lives at the command/wire surface, not at the Rust type
level — the substrate's whole point):
- `send_writes_chat_messages_collection_with_canonical_entity_shape`
→ pins ChatMessageEntity layout (id/roomId/senderId/timestamp/
content/replyToId/metadata.source/status, ISO-8601 UTC timestamps)
- `send_envelope_matches_airc_publish_wire_shape`
→ pins AircRealtimeEnvelope layout (eventId/roomId/sourceId/
createdAtMs/delivery, tagged payload variant with
schema=chat_transcript and inline message data)
# What this PR explicitly does NOT do
- **Does NOT migrate** chat/analyze or chat/export (still fail-loud
stubs naming issue #57).
- **Does NOT register `ChatModule` at runtime startup.** Same reasoning
as #1489 — until ALL chat commands are migrated, registration would
break the remaining stubs at runtime.
- **Does NOT do sender/room name resolution.** Kernel command takes
pre-resolved UUIDs; resolution stays in TS browser/CLI (or a future
channel/resolve + user/resolve pair). Same compositional principle
chat/poll established.
- **Does NOT externalize media.** Text-only for this migration; media
paths (base64 → blob storage via MediaBlobService) are their own
kink-finder.
- **Does NOT do vision pre-warming.** Fire-and-forget visual descriptor
generation is deferred to vision-module migration.
- **Does NOT thread reply-to into threading metadata fully.** The
`replyToId` field flows through to the stored entity + the airc
payload, but the richer thread { threadId, replyCount, lastReplyAt }
shape is deferred until the thread-tracking design is its own scope.
- **Does NOT solve idempotency.** A retried chat/send (network glitch
on the caller side) currently produces two stored messages —
matches today's TS behavior. Future PR can add a `client_dedup_id`
param + TTL'd dedup map; the substrate is ready for it but the
design is its own scope.
# Substrate kinks this PR surfaced
(For potential future refinement — none blocking, all annotated):
1. **No envelope construction helpers for cross-module calls.** Chat
hand-rolls `json!({ "envelope": {...} })` for airc. If many
modules call airc/realtime-publish from Rust, an
`airc::realtime_publish_envelope(builder...) -> Value` helper in
the airc-shared module would distill this. Out of scope here; flag
for if a second consumer appears.
2. **No typed cross-module command call.** Chat calls
`executor.execute_json("data/create", json!({...}))` with raw JSON
and parses the response back via `.get("success")`. A typed
`executor.execute_typed::<DataCreateParams, DataCreateResult>(...)`
would catch wire-shape drift at compile time. Same kink the
handle_id_or_legacy refinement (#1491) solved for a different
surface — flag for potential future refinement after we see if it
reappears with a second consumer.
3. **No transaction primitive across modules.** Today: chat hand-codes
the data-first / airc-best-effort ordering inline. If many modules
need similar dual-write composition, a substrate-level
`dual_write!(primary => ..., best_effort => ...)` macro could
centralize the partial-failure pattern (warning construction,
ordering enforcement, etc.). Flag for if/when a second consumer
appears.
# Tests (28/28 pass)
Pre-existing chat/poll (17, all unchanged behavior):
- StubDataModule extended to dispatch by command — back-compat
`query_only` constructor preserves chat/poll's existing tests
verbatim
- All 17 chat/poll tests still pass through the refactored stub
New chat/send (11):
- `send_happy_path_returns_message_id_and_event_id`
- `send_with_airc_failure_returns_warning_and_null_event_id` ←
partial-failure cell
- `send_with_data_executor_failure_propagates_as_err_and_skips_airc`
← hard-failure + ordering invariant
- `send_with_data_success_false_propagates_as_err_and_skips_airc` ←
the subtle data-success-false path
- `send_calls_data_before_airc` ← ordering invariant via call log
- `send_writes_chat_messages_collection_with_canonical_entity_shape`
← wire contract to data
- `send_envelope_matches_airc_publish_wire_shape` ← wire contract to
airc
- `handle_command_routes_chat_send_through_typed_envelope` ← typed
envelope round-trip end-to-end
- `handle_command_chat_send_accepts_legacy_collaboration_prefix` ←
back-compat
- `unmigrated_commands_fail_loud_and_name_followup` (updated to
exclude chat/send now that it's migrated)
ts-rs bindings (2):
- `export_bindings_chatsendparams`
- `export_bindings_chatsendresult`
# Wire output
```
shared/generated/chat/
├── ChatPollParams.ts
├── ChatPollResult.ts
├── ChatSendParams.ts // { roomId, senderId, text, replyToId? }
├── ChatSendResult.ts // { messageId, eventId?, warning? }
└── index.ts
```
# References
- [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md)
§5 (composition: commands call commands)
- PR #1489 (ChatModule + chat/poll — the first migration)
- PR #1490 (data/query cursors — single-call HandleRef stress test)
- PR #1491 (substrate refinements distilled from #1490)
- Issue #57 (migration tracker)
- Issue #64 (this migration)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…ll in Rust) Per Joel: > "Chat is gonna be airc man. So that's extracted period. Chat is of > course a bonafide command though. Do not cheapen it. So the > commands need to be or at least some to start, entirely rust." The split: - **Substrate** (delivery, pub/sub, peers, signing) → airc - **Commands** (chat/send, chat/poll, chat/analyze, chat/export) → Continuum kernel-level ServiceModule, this PR This is the FIRST real module migration from a TS command to a Rust `ServiceModule`. The chat module exercises every pattern the substrate floor PRs established: - `ServiceModule` trait - `CommandResult` cell shapes (PR #1485) - `CommandRequest<P>` / `CommandResponse<T>` envelopes (PR #1486) - Cross-module dispatch via the kernel executor (chat calls `data/query` — neither knows the other beyond the command surface) - Scaffold shape that GeneratorModule (PR #1487) produces - ts-rs typed wire boundary # Scope of THIS PR Only `chat/poll` ships in Rust. The other three commands (`chat/send`, `chat/analyze`, `chat/export`) are wired into the dispatch table as fail-loud stubs that name issue #57 as the migration tracker. Their TS implementations stay live on canary — consumers see no regression. Why staged: `chat/poll` is the cleanest outlier (pure read, no airc, no media side-effects) which lets us validate the cross-module call pattern (chat → data via the kernel executor) without dragging substrate + media into the first migration. Subsequent commands fold in real behavior incrementally. # Module structure ``` src/workers/continuum-core/src/modules/chat/ ├── mod.rs // ChatModule, ServiceModule impl, poll handler └── types.rs // ChatPollParams, ChatPollResult (ts-rs exports) ``` `mod.rs` follows the GeneratorModule template exactly — `pub struct ChatModule`, `impl ServiceModule`, `ModuleConfig` declaring both `chat/` and `collaboration/chat/` prefixes (legacy back-compat), the `handle_command` dispatch arms, the typed envelope pattern. `types.rs` carries `#[derive(TS)]` on both param + result types, exporting to `shared/generated/chat/`. Wire shape: camelCase, optional fields elided when absent. `CHAT_MESSAGES_COLLECTION` constant + `DEFAULT_POLL_LIMIT` constant centralized here. # Cross-module call pattern `chat/poll` doesn't open a database connection — it calls `data/query` via the kernel executor. Chat is blind to which adapter implements the storage; the data module routes per its own resolution rules. This is exactly MODULE-ARCHITECTURE.md §5: commands call commands; modules don't know about each other beyond the command surface. The chat module accepts an optional executor override at construction (`with_executor(...)`) — production uses the kernel-global, tests inject their own. That lets every test in this module spin up a fresh registry with a `StubDataModule` and exercise the full cross-module path without trampling the global `OnceLock`. # Tests (17/17 pass) types.rs (5): - `poll_params_defaults_to_all_none` - `poll_params_round_trip_through_json_with_camel_case` - `poll_params_accepts_missing_fields` - `poll_result_omits_after_message_id_when_none` - `poll_result_includes_after_message_id_when_set` mod.rs (10): - `config_advertises_both_command_prefixes` - `unknown_command_returns_loud_error_naming_supported_commands` - `unmigrated_commands_fail_loud_and_name_followup` (all 6 stub surfaces: chat/send, chat/analyze, chat/export, + collaboration/ prefixed versions) - `poll_returns_empty_result_when_data_module_returns_no_messages` - `poll_without_anchor_queries_data_desc_and_returns_chronological` - `poll_with_room_id_passes_filter_to_data_module` - `poll_with_anchor_looks_up_timestamp_then_filters_gt` - `poll_with_anchor_returns_err_when_anchor_missing` - `handle_command_routes_chat_poll_through_typed_envelope` - `handle_command_accepts_legacy_collaboration_prefix` ts-rs exports (2): - `export_bindings_chatpollparams` - `export_bindings_chatpollresult` # Wire output ``` shared/generated/chat/ ├── ChatPollParams.ts // { roomId?, afterMessageId?, limit? } ├── ChatPollResult.ts // { messages, count, afterMessageId? } └── index.ts // barrel ``` The master barrel (`shared/generated/index.ts`) gains `export * from './chat'`. Other barrel drift (runtime, persona) is PR #1488's territory — left untouched here so the two PRs don't fight over the same lines. # What this PR explicitly does NOT do - Does NOT migrate `chat/send`, `chat/analyze`, `chat/export`. Stubs name issue #57. Each is a future PR. - Does NOT register `ChatModule` at runtime startup. Adding `runtime.register(Arc::new(ChatModule::new()))` in `ipc::start_server` would route ALL `chat/*` traffic through this module — including the stubbed commands which would then break. Registration happens in the same PR that fills in the first real `chat/send` so consumers see one atomic change. Today: chat module exists, is tested, but the legacy TS path still owns every chat command at runtime. - Does NOT do room-name resolution. The kernel command takes an already-resolved `roomId`; name → id stays in TS browser/CLI callsites (or a future `channel/resolve` command). Keeps the kernel command compositional with the future channel module. - Does NOT auto-rebuild the master barrel from outside the chat directory — that drift was already on canary and is PR #1488's job. This PR only adds the `chat` entry. # References - [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md) §5 (composition: commands call commands) - PR #1486 (CommandRequest/Response envelopes — used here) - PR #1487 (GeneratorModule — chat follows its template) - Issue #57 (migration tracker — stubs name it) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…position handler
Per Joel:
> "Yes please do." (re: chat/send next, the dual-write composition
> stress-test)
chat/send is the chat module's first multi-cross-module-call handler:
chat → data (persist) then chat → airc (publish). The migration forces
the substrate to commit on partial-failure semantics that the
single-call handlers (chat/poll, data/query cursors) never had to face.
# Why this PR pushes the envelope
Two effects across two modules with no kernel-level transaction:
| data | airc | handler returns |
|------|------|----------------------------------------------------------|
| ok | ok | `Ok(result with message_id + event_id)` |
| ok | fail | `Ok(result with message_id, event_id=None, warning=...)` |
| fail | — | `Err(...)` — no airc publish attempted |
The (ok, fail) cell is the substrate-shaped kink the design needed
proof of. An airc-only failure is NOT command-level failure: the
message IS in the local store, consumers see it via chat/poll, a
future retry/sync mechanism heals the broadcast. Surfacing this as
`Err` would tell the caller "your write didn't happen" — which is
wrong; half of the write did. The `warning` field is the right shape:
**degraded success**.
# Design decisions this PR locks in
## Ordering: data first, airc second
Local persistence is the ground truth. The reverse order would risk
publishing a message to peers that this node doesn't know about — a
peer reading back that message would find no local record. With
data-first, the worst case is *we have the message but peers don't* —
a degradation, not a divergence.
A test (`send_calls_data_before_airc`) pins the order via a shared
call-log Mutex. If the ordering ever flips, the bad-divergence case
becomes reachable; the test catches it.
## airc-fail returns Ok+warning, not Err
The `warning` field names the failing surface, surfaces the
underlying error (so callers can diagnose), confirms the message
wasn't lost ("stored locally"), and includes the message id (so
callers can correlate logs). Tested:
- `send_with_airc_failure_returns_warning_and_null_event_id`
## data-fail short-circuits — airc NEVER called
A test tracks airc invocations via `AtomicUsize` and asserts ZERO
calls when data failed. Same invariant for the subtle
data-returns-success=false path:
- `send_with_data_executor_failure_propagates_as_err_and_skips_airc`
- `send_with_data_success_false_propagates_as_err_and_skips_airc`
## Wire contracts pinned by tests, not just docs
Two tests pin the on-the-wire shape chat hands to data + airc. If
either downstream module changes its parse expectations, these tests
catch the drift even though chat doesn't import their typed structs
(coupling lives at the command/wire surface, not at the Rust type
level — the substrate's whole point):
- `send_writes_chat_messages_collection_with_canonical_entity_shape`
→ pins ChatMessageEntity layout (id/roomId/senderId/timestamp/
content/replyToId/metadata.source/status, ISO-8601 UTC timestamps)
- `send_envelope_matches_airc_publish_wire_shape`
→ pins AircRealtimeEnvelope layout (eventId/roomId/sourceId/
createdAtMs/delivery, tagged payload variant with
schema=chat_transcript and inline message data)
# What this PR explicitly does NOT do
- **Does NOT migrate** chat/analyze or chat/export (still fail-loud
stubs naming issue #57).
- **Does NOT register `ChatModule` at runtime startup.** Same reasoning
as #1489 — until ALL chat commands are migrated, registration would
break the remaining stubs at runtime.
- **Does NOT do sender/room name resolution.** Kernel command takes
pre-resolved UUIDs; resolution stays in TS browser/CLI (or a future
channel/resolve + user/resolve pair). Same compositional principle
chat/poll established.
- **Does NOT externalize media.** Text-only for this migration; media
paths (base64 → blob storage via MediaBlobService) are their own
kink-finder.
- **Does NOT do vision pre-warming.** Fire-and-forget visual descriptor
generation is deferred to vision-module migration.
- **Does NOT thread reply-to into threading metadata fully.** The
`replyToId` field flows through to the stored entity + the airc
payload, but the richer thread { threadId, replyCount, lastReplyAt }
shape is deferred until the thread-tracking design is its own scope.
- **Does NOT solve idempotency.** A retried chat/send (network glitch
on the caller side) currently produces two stored messages —
matches today's TS behavior. Future PR can add a `client_dedup_id`
param + TTL'd dedup map; the substrate is ready for it but the
design is its own scope.
# Substrate kinks this PR surfaced
(For potential future refinement — none blocking, all annotated):
1. **No envelope construction helpers for cross-module calls.** Chat
hand-rolls `json!({ "envelope": {...} })` for airc. If many
modules call airc/realtime-publish from Rust, an
`airc::realtime_publish_envelope(builder...) -> Value` helper in
the airc-shared module would distill this. Out of scope here; flag
for if a second consumer appears.
2. **No typed cross-module command call.** Chat calls
`executor.execute_json("data/create", json!({...}))` with raw JSON
and parses the response back via `.get("success")`. A typed
`executor.execute_typed::<DataCreateParams, DataCreateResult>(...)`
would catch wire-shape drift at compile time. Same kink the
handle_id_or_legacy refinement (#1491) solved for a different
surface — flag for potential future refinement after we see if it
reappears with a second consumer.
3. **No transaction primitive across modules.** Today: chat hand-codes
the data-first / airc-best-effort ordering inline. If many modules
need similar dual-write composition, a substrate-level
`dual_write!(primary => ..., best_effort => ...)` macro could
centralize the partial-failure pattern (warning construction,
ordering enforcement, etc.). Flag for if/when a second consumer
appears.
# Tests (28/28 pass)
Pre-existing chat/poll (17, all unchanged behavior):
- StubDataModule extended to dispatch by command — back-compat
`query_only` constructor preserves chat/poll's existing tests
verbatim
- All 17 chat/poll tests still pass through the refactored stub
New chat/send (11):
- `send_happy_path_returns_message_id_and_event_id`
- `send_with_airc_failure_returns_warning_and_null_event_id` ←
partial-failure cell
- `send_with_data_executor_failure_propagates_as_err_and_skips_airc`
← hard-failure + ordering invariant
- `send_with_data_success_false_propagates_as_err_and_skips_airc` ←
the subtle data-success-false path
- `send_calls_data_before_airc` ← ordering invariant via call log
- `send_writes_chat_messages_collection_with_canonical_entity_shape`
← wire contract to data
- `send_envelope_matches_airc_publish_wire_shape` ← wire contract to
airc
- `handle_command_routes_chat_send_through_typed_envelope` ← typed
envelope round-trip end-to-end
- `handle_command_chat_send_accepts_legacy_collaboration_prefix` ←
back-compat
- `unmigrated_commands_fail_loud_and_name_followup` (updated to
exclude chat/send now that it's migrated)
ts-rs bindings (2):
- `export_bindings_chatsendparams`
- `export_bindings_chatsendresult`
# Wire output
```
shared/generated/chat/
├── ChatPollParams.ts
├── ChatPollResult.ts
├── ChatSendParams.ts // { roomId, senderId, text, replyToId? }
├── ChatSendResult.ts // { messageId, eventId?, warning? }
└── index.ts
```
# References
- [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md)
§5 (composition: commands call commands)
- PR #1489 (ChatModule + chat/poll — the first migration)
- PR #1490 (data/query cursors — single-call HandleRef stress test)
- PR #1491 (substrate refinements distilled from #1490)
- Issue #57 (migration tracker)
- Issue #64 (this migration)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…t (first dual-write composition) (#1489) * feat(modules): ChatModule — first proof-of-pattern migration (chat/poll in Rust) Per Joel: > "Chat is gonna be airc man. So that's extracted period. Chat is of > course a bonafide command though. Do not cheapen it. So the > commands need to be or at least some to start, entirely rust." The split: - **Substrate** (delivery, pub/sub, peers, signing) → airc - **Commands** (chat/send, chat/poll, chat/analyze, chat/export) → Continuum kernel-level ServiceModule, this PR This is the FIRST real module migration from a TS command to a Rust `ServiceModule`. The chat module exercises every pattern the substrate floor PRs established: - `ServiceModule` trait - `CommandResult` cell shapes (PR #1485) - `CommandRequest<P>` / `CommandResponse<T>` envelopes (PR #1486) - Cross-module dispatch via the kernel executor (chat calls `data/query` — neither knows the other beyond the command surface) - Scaffold shape that GeneratorModule (PR #1487) produces - ts-rs typed wire boundary # Scope of THIS PR Only `chat/poll` ships in Rust. The other three commands (`chat/send`, `chat/analyze`, `chat/export`) are wired into the dispatch table as fail-loud stubs that name issue #57 as the migration tracker. Their TS implementations stay live on canary — consumers see no regression. Why staged: `chat/poll` is the cleanest outlier (pure read, no airc, no media side-effects) which lets us validate the cross-module call pattern (chat → data via the kernel executor) without dragging substrate + media into the first migration. Subsequent commands fold in real behavior incrementally. # Module structure ``` src/workers/continuum-core/src/modules/chat/ ├── mod.rs // ChatModule, ServiceModule impl, poll handler └── types.rs // ChatPollParams, ChatPollResult (ts-rs exports) ``` `mod.rs` follows the GeneratorModule template exactly — `pub struct ChatModule`, `impl ServiceModule`, `ModuleConfig` declaring both `chat/` and `collaboration/chat/` prefixes (legacy back-compat), the `handle_command` dispatch arms, the typed envelope pattern. `types.rs` carries `#[derive(TS)]` on both param + result types, exporting to `shared/generated/chat/`. Wire shape: camelCase, optional fields elided when absent. `CHAT_MESSAGES_COLLECTION` constant + `DEFAULT_POLL_LIMIT` constant centralized here. # Cross-module call pattern `chat/poll` doesn't open a database connection — it calls `data/query` via the kernel executor. Chat is blind to which adapter implements the storage; the data module routes per its own resolution rules. This is exactly MODULE-ARCHITECTURE.md §5: commands call commands; modules don't know about each other beyond the command surface. The chat module accepts an optional executor override at construction (`with_executor(...)`) — production uses the kernel-global, tests inject their own. That lets every test in this module spin up a fresh registry with a `StubDataModule` and exercise the full cross-module path without trampling the global `OnceLock`. # Tests (17/17 pass) types.rs (5): - `poll_params_defaults_to_all_none` - `poll_params_round_trip_through_json_with_camel_case` - `poll_params_accepts_missing_fields` - `poll_result_omits_after_message_id_when_none` - `poll_result_includes_after_message_id_when_set` mod.rs (10): - `config_advertises_both_command_prefixes` - `unknown_command_returns_loud_error_naming_supported_commands` - `unmigrated_commands_fail_loud_and_name_followup` (all 6 stub surfaces: chat/send, chat/analyze, chat/export, + collaboration/ prefixed versions) - `poll_returns_empty_result_when_data_module_returns_no_messages` - `poll_without_anchor_queries_data_desc_and_returns_chronological` - `poll_with_room_id_passes_filter_to_data_module` - `poll_with_anchor_looks_up_timestamp_then_filters_gt` - `poll_with_anchor_returns_err_when_anchor_missing` - `handle_command_routes_chat_poll_through_typed_envelope` - `handle_command_accepts_legacy_collaboration_prefix` ts-rs exports (2): - `export_bindings_chatpollparams` - `export_bindings_chatpollresult` # Wire output ``` shared/generated/chat/ ├── ChatPollParams.ts // { roomId?, afterMessageId?, limit? } ├── ChatPollResult.ts // { messages, count, afterMessageId? } └── index.ts // barrel ``` The master barrel (`shared/generated/index.ts`) gains `export * from './chat'`. Other barrel drift (runtime, persona) is PR #1488's territory — left untouched here so the two PRs don't fight over the same lines. # What this PR explicitly does NOT do - Does NOT migrate `chat/send`, `chat/analyze`, `chat/export`. Stubs name issue #57. Each is a future PR. - Does NOT register `ChatModule` at runtime startup. Adding `runtime.register(Arc::new(ChatModule::new()))` in `ipc::start_server` would route ALL `chat/*` traffic through this module — including the stubbed commands which would then break. Registration happens in the same PR that fills in the first real `chat/send` so consumers see one atomic change. Today: chat module exists, is tested, but the legacy TS path still owns every chat command at runtime. - Does NOT do room-name resolution. The kernel command takes an already-resolved `roomId`; name → id stays in TS browser/CLI callsites (or a future `channel/resolve` command). Keeps the kernel command compositional with the future channel module. - Does NOT auto-rebuild the master barrel from outside the chat directory — that drift was already on canary and is PR #1488's job. This PR only adds the `chat` entry. # References - [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md) §5 (composition: commands call commands) - PR #1486 (CommandRequest/Response envelopes — used here) - PR #1487 (GeneratorModule — chat follows its template) - Issue #57 (migration tracker — stubs name it) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(modules/chat): chat/send migrates to Rust — first dual-write composition handler Per Joel: > "Yes please do." (re: chat/send next, the dual-write composition > stress-test) chat/send is the chat module's first multi-cross-module-call handler: chat → data (persist) then chat → airc (publish). The migration forces the substrate to commit on partial-failure semantics that the single-call handlers (chat/poll, data/query cursors) never had to face. # Why this PR pushes the envelope Two effects across two modules with no kernel-level transaction: | data | airc | handler returns | |------|------|----------------------------------------------------------| | ok | ok | `Ok(result with message_id + event_id)` | | ok | fail | `Ok(result with message_id, event_id=None, warning=...)` | | fail | — | `Err(...)` — no airc publish attempted | The (ok, fail) cell is the substrate-shaped kink the design needed proof of. An airc-only failure is NOT command-level failure: the message IS in the local store, consumers see it via chat/poll, a future retry/sync mechanism heals the broadcast. Surfacing this as `Err` would tell the caller "your write didn't happen" — which is wrong; half of the write did. The `warning` field is the right shape: **degraded success**. # Design decisions this PR locks in ## Ordering: data first, airc second Local persistence is the ground truth. The reverse order would risk publishing a message to peers that this node doesn't know about — a peer reading back that message would find no local record. With data-first, the worst case is *we have the message but peers don't* — a degradation, not a divergence. A test (`send_calls_data_before_airc`) pins the order via a shared call-log Mutex. If the ordering ever flips, the bad-divergence case becomes reachable; the test catches it. ## airc-fail returns Ok+warning, not Err The `warning` field names the failing surface, surfaces the underlying error (so callers can diagnose), confirms the message wasn't lost ("stored locally"), and includes the message id (so callers can correlate logs). Tested: - `send_with_airc_failure_returns_warning_and_null_event_id` ## data-fail short-circuits — airc NEVER called A test tracks airc invocations via `AtomicUsize` and asserts ZERO calls when data failed. Same invariant for the subtle data-returns-success=false path: - `send_with_data_executor_failure_propagates_as_err_and_skips_airc` - `send_with_data_success_false_propagates_as_err_and_skips_airc` ## Wire contracts pinned by tests, not just docs Two tests pin the on-the-wire shape chat hands to data + airc. If either downstream module changes its parse expectations, these tests catch the drift even though chat doesn't import their typed structs (coupling lives at the command/wire surface, not at the Rust type level — the substrate's whole point): - `send_writes_chat_messages_collection_with_canonical_entity_shape` → pins ChatMessageEntity layout (id/roomId/senderId/timestamp/ content/replyToId/metadata.source/status, ISO-8601 UTC timestamps) - `send_envelope_matches_airc_publish_wire_shape` → pins AircRealtimeEnvelope layout (eventId/roomId/sourceId/ createdAtMs/delivery, tagged payload variant with schema=chat_transcript and inline message data) # What this PR explicitly does NOT do - **Does NOT migrate** chat/analyze or chat/export (still fail-loud stubs naming issue #57). - **Does NOT register `ChatModule` at runtime startup.** Same reasoning as #1489 — until ALL chat commands are migrated, registration would break the remaining stubs at runtime. - **Does NOT do sender/room name resolution.** Kernel command takes pre-resolved UUIDs; resolution stays in TS browser/CLI (or a future channel/resolve + user/resolve pair). Same compositional principle chat/poll established. - **Does NOT externalize media.** Text-only for this migration; media paths (base64 → blob storage via MediaBlobService) are their own kink-finder. - **Does NOT do vision pre-warming.** Fire-and-forget visual descriptor generation is deferred to vision-module migration. - **Does NOT thread reply-to into threading metadata fully.** The `replyToId` field flows through to the stored entity + the airc payload, but the richer thread { threadId, replyCount, lastReplyAt } shape is deferred until the thread-tracking design is its own scope. - **Does NOT solve idempotency.** A retried chat/send (network glitch on the caller side) currently produces two stored messages — matches today's TS behavior. Future PR can add a `client_dedup_id` param + TTL'd dedup map; the substrate is ready for it but the design is its own scope. # Substrate kinks this PR surfaced (For potential future refinement — none blocking, all annotated): 1. **No envelope construction helpers for cross-module calls.** Chat hand-rolls `json!({ "envelope": {...} })` for airc. If many modules call airc/realtime-publish from Rust, an `airc::realtime_publish_envelope(builder...) -> Value` helper in the airc-shared module would distill this. Out of scope here; flag for if a second consumer appears. 2. **No typed cross-module command call.** Chat calls `executor.execute_json("data/create", json!({...}))` with raw JSON and parses the response back via `.get("success")`. A typed `executor.execute_typed::<DataCreateParams, DataCreateResult>(...)` would catch wire-shape drift at compile time. Same kink the handle_id_or_legacy refinement (#1491) solved for a different surface — flag for potential future refinement after we see if it reappears with a second consumer. 3. **No transaction primitive across modules.** Today: chat hand-codes the data-first / airc-best-effort ordering inline. If many modules need similar dual-write composition, a substrate-level `dual_write!(primary => ..., best_effort => ...)` macro could centralize the partial-failure pattern (warning construction, ordering enforcement, etc.). Flag for if/when a second consumer appears. # Tests (28/28 pass) Pre-existing chat/poll (17, all unchanged behavior): - StubDataModule extended to dispatch by command — back-compat `query_only` constructor preserves chat/poll's existing tests verbatim - All 17 chat/poll tests still pass through the refactored stub New chat/send (11): - `send_happy_path_returns_message_id_and_event_id` - `send_with_airc_failure_returns_warning_and_null_event_id` ← partial-failure cell - `send_with_data_executor_failure_propagates_as_err_and_skips_airc` ← hard-failure + ordering invariant - `send_with_data_success_false_propagates_as_err_and_skips_airc` ← the subtle data-success-false path - `send_calls_data_before_airc` ← ordering invariant via call log - `send_writes_chat_messages_collection_with_canonical_entity_shape` ← wire contract to data - `send_envelope_matches_airc_publish_wire_shape` ← wire contract to airc - `handle_command_routes_chat_send_through_typed_envelope` ← typed envelope round-trip end-to-end - `handle_command_chat_send_accepts_legacy_collaboration_prefix` ← back-compat - `unmigrated_commands_fail_loud_and_name_followup` (updated to exclude chat/send now that it's migrated) ts-rs bindings (2): - `export_bindings_chatsendparams` - `export_bindings_chatsendresult` # Wire output ``` shared/generated/chat/ ├── ChatPollParams.ts ├── ChatPollResult.ts ├── ChatSendParams.ts // { roomId, senderId, text, replyToId? } ├── ChatSendResult.ts // { messageId, eventId?, warning? } └── index.ts ``` # References - [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md) §5 (composition: commands call commands) - PR #1489 (ChatModule + chat/poll — the first migration) - PR #1490 (data/query cursors — single-call HandleRef stress test) - PR #1491 (substrate refinements distilled from #1490) - Issue #57 (migration tracker) - Issue #64 (this migration) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(modules/chat): concurrency stress tests — multi-persona invariants pinned Per Joel 2026-05-30: "Each persona exists in its own threads." The kernel registers ONE ChatModule instance; every persona's thread invokes its `&self` methods concurrently against the same executor. The substrate is designed to be safe under that load — but until now no test PROVED it. Single-threaded `#[tokio::test]` runs serialize even genuinely racy code and would pass a substrate with a data race. This commit adds 4 concurrency stress tests pinning the invariants the dual-write / single-call composition designs depend on. Every test uses `flavor = "multi_thread", worker_threads = 4` so tasks actually preempt each other on distinct OS threads rather than cooperatively interleaving on one. # What's pinned 1. **`send_under_concurrent_load_stores_all_messages_with_distinct_ids`** 50 concurrent personas all call `chat/send` through the same ChatModule. Asserts: every send completes, every send writes exactly once, every returned `message_id` is distinct (no UUID collision, no shared mutable state holding the id), and the SET of stored ids equals the SET of returned ids (no lost writes, no phantom writes). 2. **`send_preserves_per_call_ordering_under_concurrent_load`** 25 concurrent sends interleave globally — but per-call `data/create` MUST still precede per-call `airc/realtime-publish`. The dual-write design's bad-divergence safety net (peers don't see a message the node hasn't stored) depends on this invariant holding under load. Tagging each observation with its `message_id` lets the test reconstruct per-call timelines from the interleaved global log. 3. **`send_isolates_mixed_outcomes_under_concurrent_load`** 30 concurrent sends with half airc-failing (text flag tells the stub to fail). Each call's `warning` must reference THIS call's `message_id`, not a concurrent sibling's. Cross-contamination between concurrent results would mean shared mutable state in the handler — this catches it. 4. **`poll_isolates_results_under_concurrent_load`** 30 concurrent `chat/poll` calls each polling a DIFFERENT room. The stub echoes the requested `roomId` in the synthetic result; the test asserts every task receives ITS OWN room's result. Catches result-swap bugs that would never appear single-threaded. # Why this discipline matters Concurrency tests aren't exercising rare paths — they're the production scenario. A test suite full of single-threaded `#[tokio::test]`s can sign off on a substrate that silently miscomputes under multi-persona load. Pinning the invariants here means the next refactor (e.g., adding a `dual_write!` macro or typed cross-module command call) is held to the same bar. The pattern goes into every future module that consumes the kernel: when you add a new handler that touches shared state, add a matching concurrency stress test. # Tests (23/23 pass — 19 pre-existing + 4 new concurrency) All previously-passing tests still pass. The new ones use real multi-threaded tokio runtime + `Arc<Mutex>` + atomic tracking to observe interleavings the substrate must handle. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps yaml from 2.7.1 to 2.8.0.
Release notes
Sourced from yaml's releases.
Commits
c000eb72.8.01e85fc8style: Apply updated lint rules02f7d5fchore: Refresh lockfile389ca7cdocs: include cli example (#617)0f29ce6feat: Add--mergeoption to CLI tool (#611)e00cab9fix: Improve error for tag resolution error on null value (#616)2a841ccfix: Allow empty string as plain scalar representation, for failsafe schema (...55c5ef4feat: Add node cache for faster alias resolution (#612)ab17552Merge pull request #614 from eemeli/engines-compatb27c124ci: Re-introduce tests for Node.js 14.6 and laterDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot mergewill merge this PR after your CI passes on it@dependabot squash and mergewill squash and merge this PR after your CI passes on it@dependabot cancel mergewill cancel a previously requested merge and block automerging@dependabot reopenwill reopen this PR if it is closed@dependabot closewill close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)