chore(member-work-sync): clarify lifecycle dispatch boundary
This commit is contained in:
parent
d27c1bcc51
commit
90401b41a1
5 changed files with 18 additions and 16 deletions
|
|
@ -3045,15 +3045,15 @@ Current implementation:
|
|||
|
||||
## 22. Runtime Defaults And No Feature Flags
|
||||
|
||||
Phase 1 should ship without feature flags.
|
||||
Phase 1 shipped without feature flags.
|
||||
|
||||
Reason:
|
||||
|
||||
- Phase 1 has no nudges, no inbox writes, no task mutation, and no runtime restart behavior.
|
||||
- Adding feature flags for passive status/report validation creates extra branches and makes failures harder to reason about.
|
||||
- The safe boundary is architectural, not configurational: Phase 1 code simply does not contain the side-effect dispatcher.
|
||||
- The safe boundary is architectural, not configurational: passive status/report validation stays independent from Phase 2 side effects.
|
||||
|
||||
Phase 1 defaults:
|
||||
Runtime defaults:
|
||||
|
||||
| Behavior | Default | Why |
|
||||
|---|---:|---|
|
||||
|
|
@ -3061,7 +3061,9 @@ Phase 1 defaults:
|
|||
| `member_work_sync_status` | on | read-only diagnostics |
|
||||
| `member_work_sync_report` | on | server-validated, no board mutation |
|
||||
| pending report intent fallback | on only when identity is not terminally invalid | compatibility with old app/runtime boundaries |
|
||||
| nudges/outbox/inbox writes | not implemented | avoids hidden flag branches |
|
||||
| outbox planning | on only for queued reconciles and only when `phase2Readiness=shadow_ready` | prevents status reads from causing side effects |
|
||||
| scheduled nudge dispatch | on only for lifecycle-active teams | stopped teams must not claim or supersede pending nudges |
|
||||
| inbox nudge writes | guarded by dispatcher revalidation | lifecycle, current fingerprint, readiness, busy signal, rate limit, and watchdog cooldown are checked immediately before write |
|
||||
|
||||
Do not add:
|
||||
|
||||
|
|
@ -3073,9 +3075,9 @@ If Phase 1 needs to be disabled during development, revert or patch the narrow c
|
|||
|
||||
Phase 2 policy:
|
||||
|
||||
- Phase 2 is a separate implementation, not a disabled code path hidden behind a flag in Phase 1.
|
||||
- If Phase 2 adds nudges, it must add dispatcher/outbox code in its own cut after metrics review.
|
||||
- Phase 2 may use constants/configuration for rate limits and timing, but not a broad "new vs legacy" branch.
|
||||
- Phase 2 is implemented as a separate outbox/dispatcher/scheduler path, not as hidden branching inside passive diagnostics.
|
||||
- Phase 2 does not bypass shadow readiness. If metrics are noisy, the planner returns `phase2_not_ready`.
|
||||
- Phase 2 uses constants/configuration for rate limits and timing, but not a broad "new vs legacy" branch.
|
||||
|
||||
Phase 2 runtime constants can be normal typed defaults, not feature gates:
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export function createMemberWorkSyncFeature(deps: {
|
|||
kanbanManager: TeamKanbanManager;
|
||||
membersMetaStore: TeamMembersMetaStore;
|
||||
isTeamActive?: (teamName: string) => Promise<boolean> | boolean;
|
||||
listActiveTeamNames?: () => Promise<string[]>;
|
||||
listLifecycleActiveTeamNames?: () => Promise<string[]>;
|
||||
logger?: MemberWorkSyncLoggerPort;
|
||||
}): MemberWorkSyncFeatureFacade {
|
||||
const clock = new SystemClockAdapter();
|
||||
|
|
@ -110,9 +110,9 @@ export function createMemberWorkSyncFeature(deps: {
|
|||
logger: deps.logger,
|
||||
});
|
||||
const router = new MemberWorkSyncTeamChangeRouter(agendaSource, queue);
|
||||
const nudgeDispatchScheduler = deps.listActiveTeamNames
|
||||
const nudgeDispatchScheduler = deps.listLifecycleActiveTeamNames
|
||||
? new MemberWorkSyncNudgeDispatchScheduler({
|
||||
listActiveTeamNames: deps.listActiveTeamNames,
|
||||
listLifecycleActiveTeamNames: deps.listLifecycleActiveTeamNames,
|
||||
dispatchDue: (teamNames) =>
|
||||
nudgeDispatcher.dispatchDue({
|
||||
teamNames,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ function unrefTimer(timer: ReturnType<typeof setTimeout>): void {
|
|||
}
|
||||
|
||||
export interface MemberWorkSyncNudgeDispatchSchedulerDeps {
|
||||
listActiveTeamNames(): Promise<string[]>;
|
||||
listLifecycleActiveTeamNames(): Promise<string[]>;
|
||||
dispatchDue(teamNames: string[]): Promise<MemberWorkSyncNudgeDispatchSummary>;
|
||||
intervalMs?: number;
|
||||
logger?: MemberWorkSyncLoggerPort;
|
||||
|
|
@ -84,7 +84,7 @@ export class MemberWorkSyncNudgeDispatchScheduler {
|
|||
|
||||
private async dispatchOnce(): Promise<void> {
|
||||
try {
|
||||
const teamNames = uniqueNonEmpty(await this.deps.listActiveTeamNames());
|
||||
const teamNames = uniqueNonEmpty(await this.deps.listLifecycleActiveTeamNames());
|
||||
if (teamNames.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1236,7 +1236,7 @@ async function initializeServices(): Promise<void> {
|
|||
isTeamActive: (teamName) =>
|
||||
teamProvisioningService.isTeamAlive(teamName) ||
|
||||
teamProvisioningService.hasProvisioningRun(teamName),
|
||||
listActiveTeamNames: async () => {
|
||||
listLifecycleActiveTeamNames: async () => {
|
||||
const teams = await teamDataService.listTeams();
|
||||
return teams
|
||||
.filter(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ describe('MemberWorkSyncNudgeDispatchScheduler', () => {
|
|||
return { claimed: 1, delivered: 1, superseded: 0, retryable: 0, terminal: 0 };
|
||||
});
|
||||
const scheduler = new MemberWorkSyncNudgeDispatchScheduler({
|
||||
listActiveTeamNames: async () => ['team-a', 'team-a', ' ', 'team-b'],
|
||||
listLifecycleActiveTeamNames: async () => ['team-a', 'team-a', ' ', 'team-b'],
|
||||
dispatchDue,
|
||||
});
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ describe('MemberWorkSyncNudgeDispatchScheduler', () => {
|
|||
it('skips dispatch when there are no active teams', async () => {
|
||||
const dispatchDue = vi.fn();
|
||||
const scheduler = new MemberWorkSyncNudgeDispatchScheduler({
|
||||
listActiveTeamNames: async () => [],
|
||||
listLifecycleActiveTeamNames: async () => [],
|
||||
dispatchDue,
|
||||
});
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ describe('MemberWorkSyncNudgeDispatchScheduler', () => {
|
|||
it('logs and survives list failures without throwing', async () => {
|
||||
const warn = vi.fn();
|
||||
const scheduler = new MemberWorkSyncNudgeDispatchScheduler({
|
||||
listActiveTeamNames: async () => {
|
||||
listLifecycleActiveTeamNames: async () => {
|
||||
throw new Error('list failed');
|
||||
},
|
||||
dispatchDue: vi.fn(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue