Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/serialize-platform-shell-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@fluojs/runtime': major
---

Replace uncoordinated PlatformShell lifecycle overlap with strict exclusive transitions. Every overlapping `start()` or `stop()` now rejects immediately with `PlatformLifecycleConflictError` and structured `PLATFORM_LIFECYCLE_CONFLICT` metadata.

In 2.x, overlapping `start()` calls could start the same components more than once, and `stop()` called during an in-flight startup could return before startup settled and leave resources running. Consumers must now give one application boundary ownership of each transition, wait for that owned promise to settle, and retry explicitly when a rejected operation is still required. Lifecycle callbacks receive the same typed conflict after synchronous or arbitrarily awaited reentry.
2 changes: 2 additions & 0 deletions docs/CONTEXT.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Slack discoverability도 패키지 README, governed package-surface docs, NestJS

Discord discoverability도 패키지 README, governed package-surface docs, NestJS migration docs, intermediate chat book chapter로 나뉜다. `packages/discord/README.ko.md`는 `DiscordModule.forRoot(...)` / `forRootAsync({ inject, useFactory, global? })`, `global: false` local opt-out을 포함한 기본 global provider visibility, singleton compatibility token `DISCORD`와 `DISCORD_CHANNEL`, 의도적으로 private인 내부 provider helper/token, 직접 `DiscordService` 전달, `DISCORD_CHANNEL`을 통한 `@fluojs/notifications` channel 통합, lifecycle-gated send, factory-owned와 app-owned transport resource ownership, optional `verify()`를 노출하는 transport를 위한 `verifyOnModuleInit` bootstrap 검증, `DiscordTemplateRenderer` notification template rendering, webhook-first transport boundary, 그리고 `DiscordService.createPlatformStatusSnapshot()`과 `createDiscordPlatformStatusSnapshot(...)` 양쪽을 통한 status snapshot을 문서화한다. [`docs/reference/package-surface.ko.md`](./reference/package-surface.ko.md)는 직접 `process.env`를 읽지 않는 webhook-first, transport-agnostic Discord delivery에 대한 정식 `@fluojs/discord` responsibility boundary를 기록하고, [`docs/getting-started/migrate-from-nestjs.ko.md`](./getting-started/migrate-from-nestjs.ko.md)는 NestJS async module/custom-provider assumption 및 `isGlobal`에서 Discord injected factory와 `global?: boolean`로 옮기는 migration boundary를 기록하며, [`book/intermediate/ch17-slack-discord.ko.md`](../book/intermediate/ch17-slack-discord.ko.md)는 FluoShop 학습 경로에서 standalone `DiscordService`, notifications channel routing, embed payload, Discord module visibility, Discord status snapshot observability를 설명한다.

PlatformShell lifecycle exclusivity는 `packages/runtime/README.ko.md`, [`docs/architecture/platform-consistency-design.ko.md`](./architecture/platform-consistency-design.ko.md), [`docs/contracts/third-party-extension-contract.ko.md`](./contracts/third-party-extension-contract.ko.md), [`docs/contracts/platform-conformance-authoring-checklist.ko.md`](./contracts/platform-conformance-authoring-checklist.ko.md)에 동기화되어 있다. 정확히 하나의 `start()` 또는 `stop()` transition만 active일 수 있고 모든 overlap은 root-exported `PlatformLifecycleConflictError`와 `PLATFORM_LIFECYCLE_CONFLICT` metadata로 즉시 reject된다. Callback reentry도 임의의 await 이후 동일한 계약을 따르며 caller는 lifecycle work를 공유하거나 queue하지 말고 settlement 이후 명시적 retry를 조정해야 한다.

HTTP adapter raw-body portability discoverability도 testing package와 governed platform docs로 나뉜다. `packages/testing/README.md`는 byte-sensitive payload를 위한 `createHttpAdapterPortabilityHarness(...)`와 `assertPreservesExactRawBodyBytesForByteSensitivePayloads()`를 문서화한다. [`docs/contracts/platform-conformance-authoring-checklist.ko.md`](./contracts/platform-conformance-authoring-checklist.ko.md)는 HTTP adapter가 Unicode replacement, newline normalization, re-encoding 없이 정확한 `rawBody` byte를 보존해야 한다고 요구하며, [`docs/contracts/testing-guide.ko.md`](./contracts/testing-guide.ko.md)는 HTTP adapter byte preservation behavior가 바뀔 때 실행할 platform portability test와 governance command를 안내한다.

Bun adapter lifecycle 및 migration discoverability는 `packages/platform-bun/README.ko.md`, [`book/intermediate/ch22-bun.ko.md`](../book/intermediate/ch22-bun.ko.md), [`docs/getting-started/migrate-from-nestjs.ko.md`](./getting-started/migrate-from-nestjs.ko.md), website runtime/realtime guide로 나뉜다. 패키지 README는 duplicate `listen()` idempotency, pre-listen realtime binding requirement, byte-exact raw-body 및 stale native handoff regression coverage, log/`process.exitCode`를 통한 signal-driven shutdown reporting, 동기 `createBunFetchHandler(...)` 사용법을 문서화한다. Intermediate book은 manual fetch-host lifecycle boundary와 `runBunApplication(...)` signal helper boundary를 담고, NestJS migration docs는 Bun fetch-style hosting이 late host mutation 또는 reflection metadata 가정을 되살리지 않는다고 기록하며, website guide는 `createBunFetchHandler(...)`가 동기임을 유지하면서 adapter-owned shutdown, raw fetch, `server.upgrade(...)` 제어가 realtime binding seam 밖에 남는다는 점을 맞춘다.
Expand Down
2 changes: 2 additions & 0 deletions docs/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Slack discoverability is split across the package README, governed package-surfa

Discord discoverability is split across the package README, governed package-surface docs, NestJS migration docs, and the intermediate chat book chapter: `packages/discord/README.md` documents `DiscordModule.forRoot(...)` / `forRootAsync({ inject, useFactory, global? })`, default global provider visibility with `global: false` local opt-out, singleton compatibility tokens `DISCORD` and `DISCORD_CHANNEL`, intentionally private internal provider helpers/tokens, direct `DiscordService` delivery, `@fluojs/notifications` channel integration through `DISCORD_CHANNEL`, lifecycle-gated sends, factory-owned versus app-owned transport resource ownership, `verifyOnModuleInit` bootstrap verification for transports that expose optional `verify()`, `DiscordTemplateRenderer` notification template rendering, webhook-first transport boundaries, and status snapshots through both `DiscordService.createPlatformStatusSnapshot()` and `createDiscordPlatformStatusSnapshot(...)`; [`docs/reference/package-surface.md`](./reference/package-surface.md) records the canonical `@fluojs/discord` responsibility boundary for webhook-first, transport-agnostic Discord delivery without direct `process.env` reads; [`docs/getting-started/migrate-from-nestjs.md`](./getting-started/migrate-from-nestjs.md) records the migration boundary from NestJS async module/custom-provider assumptions and `isGlobal` to Discord's injected factory plus `global?: boolean`; and [`book/intermediate/ch17-slack-discord.md`](../book/intermediate/ch17-slack-discord.md) teaches standalone `DiscordService`, notifications channel routing, embed payloads, Discord module visibility, and Discord status snapshot observability in the FluoShop learning path.

PlatformShell lifecycle exclusivity is synchronized across `packages/runtime/README.md`, [`docs/architecture/platform-consistency-design.md`](./architecture/platform-consistency-design.md), [`docs/contracts/third-party-extension-contract.md`](./contracts/third-party-extension-contract.md), and [`docs/contracts/platform-conformance-authoring-checklist.md`](./contracts/platform-conformance-authoring-checklist.md): exactly one `start()` or `stop()` transition may be active, every overlap immediately rejects with root-exported `PlatformLifecycleConflictError` and `PLATFORM_LIFECYCLE_CONFLICT` metadata, callback reentry follows the same contract after arbitrary awaits, and callers must coordinate explicit retries after settlement instead of sharing or queueing lifecycle work.

HTTP adapter raw-body portability discoverability is split across the testing package and governed platform docs: `packages/testing/README.md` documents `createHttpAdapterPortabilityHarness(...)` and `assertPreservesExactRawBodyBytesForByteSensitivePayloads()` for byte-sensitive payloads; [`docs/contracts/platform-conformance-authoring-checklist.md`](./contracts/platform-conformance-authoring-checklist.md) requires HTTP adapters to preserve exact `rawBody` bytes without Unicode replacement, newline normalization, or re-encoding; and [`docs/contracts/testing-guide.md`](./contracts/testing-guide.md) identifies platform portability tests and governance commands to run when HTTP adapter byte preservation behavior changes.

Bun adapter lifecycle and migration discoverability is split across `packages/platform-bun/README.md`, [`book/intermediate/ch22-bun.md`](../book/intermediate/ch22-bun.md), [`docs/getting-started/migrate-from-nestjs.md`](./getting-started/migrate-from-nestjs.md), and the website runtime/realtime guides: the package README documents duplicate `listen()` idempotency, pre-listen realtime binding requirements, byte-exact raw-body and stale native handoff regression coverage, signal-driven shutdown reporting through logs/`process.exitCode`, and synchronous `createBunFetchHandler(...)` usage; the intermediate book carries the manual fetch-host lifecycle boundary and `runBunApplication(...)` signal helper boundary; NestJS migration docs record that Bun fetch-style hosting does not restore late host mutation or reflection metadata assumptions; and the website guides keep `createBunFetchHandler(...)` synchronous while keeping adapter-owned shutdown, raw fetch, and `server.upgrade(...)` control outside the realtime binding seam.
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture/platform-consistency-design.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
| `health()` | `packages/runtime/src/platform-contract.ts`의 `PlatformShell.health()` 와 `PlatformComponent.health()` | platform-managed component는 component failure를 숨기지 않고 `healthy`, `unhealthy`, `degraded`를 보고해야 한다. |
| `snapshot()` | `packages/runtime/src/platform-contract.ts`의 `PlatformShell.snapshot()` 와 `PlatformComponent.snapshot()` | platform-managed component는 machine-readable state, ownership, telemetry tag, dependency metadata를 노출해야 한다. |

## Platform Shell Lifecycle Exclusivity

`RuntimePlatformShell.start()`와 `stop()`은 정확히 하나의 active lifecycle transition만 허용한다. 두 operation 중 하나가 active인 동안 겹치는 모든 `start()` 또는 `stop()` 호출은 root-exported `PlatformLifecycleConflictError`를 담은 즉시 reject된 promise를 반환해야 한다. Error code는 `PLATFORM_LIFECYCLE_CONFLICT`이며 `activeOperation`과 `requestedOperation`은 readonly field와 일치하는 structured metadata로 제공된다.

Shell은 겹치는 lifecycle work를 queue하면 안 된다. 또한 공유, coalesce, desired state 기록도 하면 안 된다. Runtime-neutral promise scheduling으로 component work 시작 전에 active transition을 publish하므로 synchronous callback reentry와 임의의 await 이후 reentry 모두 같은 conflict 계약을 관찰한다. Identity-checked cleanup은 성공 또는 실패 뒤 transition을 해제한다. Caller는 settlement 이후에만 명시적으로 retry할 수 있다. Private rollback과 cleanup path는 owning transition 내부에 남아 dependency ordering, diagnostics, rollback retry, sequential idempotency를 보존한다.

## Conformance Rules

- Platform 패키지는 저장소 정책이 `PlatformAdapter`라고 부르는 adapter seam을 구현해야 하며, 현재 HTTP transport 계약은 `HttpApplicationAdapter`가 담당한다.
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture/platform-consistency-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ This document defines the current platform adapter contract used by fluo transpo
| `health()` | `PlatformShell.health()` and `PlatformComponent.health()` in `packages/runtime/src/platform-contract.ts` | Platform-managed components MUST report `healthy`, `unhealthy`, or `degraded` without hiding component failure. |
| `snapshot()` | `PlatformShell.snapshot()` and `PlatformComponent.snapshot()` in `packages/runtime/src/platform-contract.ts` | Platform-managed components MUST expose machine-readable state, ownership, telemetry tags, and dependency metadata. |

## Platform Shell Lifecycle Exclusivity

`RuntimePlatformShell.start()` and `stop()` allow exactly one active lifecycle transition. While either operation is active, every overlapping `start()` or `stop()` call MUST return an immediately rejected promise with root-exported `PlatformLifecycleConflictError`. The error code is `PLATFORM_LIFECYCLE_CONFLICT`; `activeOperation` and `requestedOperation` are available as readonly fields and matching structured metadata.

The shell MUST NOT queue, share, coalesce, or record a desired state for overlapping lifecycle work. It publishes the active transition before component work begins through runtime-neutral promise scheduling, so synchronous callback reentry and reentry after arbitrary awaits observe the same conflict contract. Identity-checked cleanup releases the transition after success or failure. Callers may retry explicitly only after settlement. Private rollback and cleanup paths stay inside the owning transition so dependency ordering, diagnostics, rollback retry, and sequential idempotency remain intact.

## Conformance Rules

- Platform packages MUST implement the adapter seam referenced by repository policy as `PlatformAdapter`, with the current HTTP transport contract supplied by `HttpApplicationAdapter`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
- [ ] MUST: side-effect capture를 설정한 경우 `validate()`가 장기 지속 부수 효과를 만들지 않는지 검증합니다.
- [ ] MUST: `start()`가 중복 호출에서도 결정론적인지 검증합니다.
- [ ] MUST: `stop()`이 중복 호출에서도 멱등적인지 검증합니다.
- [ ] MUST: 같은 operation overlap을 포함한 네 가지 `start()` / `stop()` overlap pair가 transition active 상태에서 즉시 `PlatformLifecycleConflictError`로 reject되는지 검증합니다.
- [ ] MUST: Lifecycle callback reentry가 queue, timer, runtime-specific context tracking 없이 synchronous 시점과 임의의 await 이후에 동일한 conflict를 받는지 검증합니다.
- [ ] MUST: 성공 및 실패 이후 active-transition cleanup이 settlement 뒤 명시적 retry를 허용하면서 private rollback과 cleanup의 dependency ordering 및 retry behavior를 유지하는지 검증합니다.
- [ ] MUST: `snapshot()`이 degraded 상태와 failed 상태에서도 호출 가능한지 검증합니다.
- [ ] MUST: diagnostics가 비어 있지 않은 안정적인 `code` 값을 유지하는지 검증합니다.
- [ ] MUST: 하네스 설정에서 완화하지 않는 한 error severity diagnostics에 `fixHint`를 제공합니다.
Expand All @@ -42,6 +45,7 @@
- [ ] MUST: 타입이 있는 구성을 노출하고 bootstrap 중 입력을 검증합니다.
- [ ] MUST: 패키지 동작과 문서에서 health와 readiness를 구분합니다.
- [ ] MUST: 호출자에게 보이는 실패 상태에 대해 안정적인 diagnostic code를 제공합니다.
- [ ] MUST: Platform shell lifecycle ownership을 조정할 때 `PLATFORM_LIFECYCLE_CONFLICT` metadata를 public error contract로 취급합니다.
- [ ] MUST: 소켓, 파일 핸들, 연결 같은 소유 리소스를 선언하고 shutdown 중 해제합니다.
- [ ] MUST NOT: 로그, diagnostics, snapshot을 통해 credential, token, password, API key를 노출하지 않습니다.

Expand Down
4 changes: 4 additions & 0 deletions docs/contracts/platform-conformance-authoring-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Use this checklist when authoring or changing official platform-facing packages
- [ ] MUST: Verify `validate()` does not introduce long-lived side effects when side-effect capture is configured.
- [ ] MUST: Verify `start()` is deterministic across duplicate calls.
- [ ] MUST: Verify `stop()` is idempotent across duplicate calls.
- [ ] MUST: Verify all four `start()` / `stop()` overlap pairs reject immediately with `PlatformLifecycleConflictError` while a transition is active, including same-operation overlaps.
- [ ] MUST: Verify lifecycle callback reentry receives the same conflict synchronously and after arbitrary awaits, without queues, timers, or runtime-specific context tracking.
- [ ] MUST: Verify active-transition cleanup after success and failure permits explicit retry after settlement while private rollback and cleanup retain dependency ordering and retry behavior.
- [ ] MUST: Verify `snapshot()` stays callable in degraded and failed states.
- [ ] MUST: Verify diagnostics keep stable non-empty `code` values.
- [ ] MUST: Provide `fixHint` for error-severity diagnostics unless the harness configuration explicitly relaxes that requirement.
Expand All @@ -42,6 +45,7 @@ Use this checklist when authoring or changing official platform-facing packages
- [ ] MUST: Expose typed configuration and validate inputs during bootstrap.
- [ ] MUST: Distinguish health from readiness in package behavior and package docs.
- [ ] MUST: Emit stable diagnostic codes for caller-visible failure states.
- [ ] MUST: Treat `PLATFORM_LIFECYCLE_CONFLICT` metadata as a public error contract when coordinating platform shell lifecycle ownership.
- [ ] MUST: Declare owned resources such as sockets, file handles, or connections, and release them during shutdown.
- [ ] MUST NOT: expose credentials, tokens, passwords, or API keys through logs, diagnostics, or snapshots.

Expand Down
Loading