LearnNewsExamplesServices
Frontmatter
id15033
titleProduction sweep: external initAsync() → ready() across src/ + ai/ (rejection-path classification + production-tree lint)
stateOpen
labels
airefactoringcore
assigneesneo-opus-grace
createdAt2:02 PM
updatedAt2:03 PM
githubUrlhttps://github.com/neomjs/neo/issues/15033
authorneo-opus-grace
commentsCount0
parentIssue15031
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 15034 Singleton re-init seam for specs → test-tranche ready() migration, bespoke guard deletion, repo-wide lint

Production sweep: external initAsync() → ready() across src/ + ai/ (rejection-path classification + production-tree lint)

Open Backlog/active-chunk-5 airefactoringcore
neo-opus-grace
neo-opus-grace commented on 2:02 PM

Context

Sub 1 of 2 under #15031 (the split rationale + AC mapping live in the parent's decision comment). The parent's V-B-A (https://github.com/neomjs/neo/issues/15031#issuecomment-4945488536) confirmed the mechanism at source: construct() auto-fires initAsync() (src/core/Base.mjs:314-317), singletons construct at import (src/Neo.mjs:982-983), and ready() gates on full heavy init — so the production tranche migrates today with ZERO framework changes. The bespoke _initPromise guards make the mixed state safe by construction during the transition (an external call and ready() coexist harmlessly until #15031's sub 2 deletes the guards).

Scope facts from the trace: ~39 production call sites (1 src/ + 38 ai/) of external non-super await X.initAsync(), e.g. the ai/scripts/lifecycle/* boot blocks, SystemLifecycleService.mjs:44-51 (the if (!X._initPromise) await X.initAsync() dance — which then ALSO awaits ready()), ai/agent/AgentOrchestrator.mjs:410.

Proposed Solution

  1. Migrate every external non-super await X.initAsync() in src/ + ai/ to await X.ready(), per-site (never mechanical):
    • Instantiation-path check per site: ready() only exists for construct()-ed instances. Neo.create/singleton-import paths are safe; injected-factory seams (e.g. AgentOrchestrator.agentFactory — test doubles must expose ready()) get verified individually.
    • Rejection-path classification per service (the error-visibility delta): an external await initAsync() inside try/catch OBSERVES a rejection; ready() hangs forever when the construct-fired init rejects (isReady never flips). For each service whose initAsync can reject without an internal catch, either adopt the GraphService catch-and-degrade precedent (GraphService.mjs IIFE: internal catch → degraded mode → init resolves) or keep an explicit error surface. ai/scripts/lifecycle/* exit-code consumers are the first check.
    • Remove the now-pointless if (!X._initPromise) production reach-ins in the same pass (SystemLifecycleService, DreamService.mjs:242) — the trailing await X.ready() lines already there become the whole story.
  2. Production-tree lint (freeze at zero): a grep-class CI check that fails on any NEW external non-super await *.initAsync() or ._initPromise reach-in scoped to src/ + ai/ (the test tranche stays allowed until sub 2 lands its reset seam and extends the lint repo-wide).
  3. Do NOT touch: the bespoke _initPromise guards (deletion is sub 2, blocked on the reset seam), any test/ call site, any core.Base surface.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
~39 production external initAsync() sites Base.mjs:601 contract + parent V-B-A migrated to await X.ready(), per-site verified grep zero in src/ + ai/
services whose initAsync can reject Base.mjs:314-317 (rejection = ready() hang) classified: internal catch-and-degrade OR documented error surface GraphService precedent service JSDoc classification table in the PR body
production-tree lint this ticket CI fails on new external initAsync() await / _initPromise reach-in in src/ + ai/ lint header comment guard red-test

Acceptance Criteria

  • grep for external non-super await *.initAsync() in src/ + ai/ returns zero (excluding Base.mjs's framework-internal fire + warning comment).
  • grep for ._initPromise reach-ins (read or write) OUTSIDE the owning services returns zero in src/ + ai/.
  • Every migrated service is classified on the rejection path; any service that can reject without internal catch carries the documented disposition (catch-and-degrade or explicit error surface). The ai/scripts/lifecycle/* scripts keep their failure semantics (a failed boot still surfaces, never a silent hang).
  • The production-tree lint exists, is red on a seeded violation, green on head.
  • Full unit suite green; memory-core + orchestrator + KB suites specifically named in the PR evidence.

Out of Scope

  • Bespoke _initPromise guard deletion (sub 2 — unsafe until external calls are gone repo-wide AND specs have a reset seam).
  • The test/ tranche (86 call sites — mechanically blocked: specs null _initPromise to force re-init, which ready() cannot express until sub 2's seam exists).
  • Any core.Base / Neo.mjs change.

Related

Parent #15031 · origin PR #15016 · src/core/Base.mjs:266,305,314,601,963 lifecycle · #12597 (CLOSED — the spec-leak history that shaped the test-tranche coupling)

Origin Session ID: ef6b9a4a-54ec-4afb-8438-f89a3ee46ad2

Retrieval Hint: "production sweep external initAsync ready migration rejection-path classification lint src ai tranche"