Authored by GPT-5 (Codex Desktop). Session 6ca1b510-51c3-4fac-aa39-a0fd6941318c.
FAIR-band: over-target [18/30] - taking this lane despite over-target because this is operator-directed Sandman/REM incident mitigation, #12072 Part A was unblocked by the Contract Ledger backfill requested by GPT, and the implementation depends directly on GPT-owned PR #12061.
Status: DRAFT / stacked on PR #12061. Do not review or merge this PR before #12061 lands. Once #12061 is merged, this branch should be rebased so the visible delta collapses to the top #12072 extraction commit.
Extracts the graph-provider readiness helpers from ai/scripts/runners/runSandman.mjs into ai/services/graph/providerReadiness.mjs, leaving runSandman as a thin consumer of the shared helper surface. The new module owns target resolution, provider probing, bounded retry metadata, durable failure diagnostics, and terminal/log failure recording for future orchestrator consumers.
Evidence: L2 (unit tests for provider dispatch, readiness target resolution, bounded timeout metadata, durable log breadcrumb, and secret redaction) -> L4 required for full #12072 orchestrator/REM run-state integration. Residual: AC3/AC4/AC5/AC6/AC7/AC8 integration remains on #12072 / Sub 3 follow-up.
Dependency / Stacking
This PR is intentionally stacked on PR #12061 because current origin/dev does not yet contain graphProvider, resolveGraphModelProvider, or the post-#12061 readiness-target code that #12072 explicitly consumes.
Treat 14465cc8 as the #12072 implementation delta.
After PR #12061 merges, rebase this branch onto fresh origin/dev and mark ready for review.
Deltas from ticket
Implemented the OQ3 placement decision as ai/services/graph/providerReadiness.mjs via structural fast-path against sibling providerDispatch.mjs.
Added providerConfig and requestTimeoutMs seams to waitForProvider() so callers can pass an aiConfig-shaped object and tests can assert the bounded timeout contract without slow sleeps.
Kept runSandman.mjs behavior equivalent for Part A: it still performs the provider gate before DreamService.ready() and records the same durable diagnostic shape, now through the shared helper.
Did not wire Orchestrator.executeRemCycle() / refreshGoldenPath() or decay placement. Those are explicitly deferred to Sub 3 / Part B per #12072.
This PR implements #12072 Part A only and does not attempt to close the parent epic or the subissue.
Unresolved Dissent
No new dissent for this Part A extraction. Residual full-ticket scope remains explicit above and on #12072.
Unresolved Liveness
Gemini-family liveness remains carried by Epic #12065 AC18. This PR adds no new liveness dependency beyond the epic ledger.
Post-Merge Validation
After PR #12061 merges, rebase this branch onto fresh origin/dev, confirm the PR diff shows only #12072 files, and mark ready for review.
After this PR lands, rerun npm run ai:run-sandman with the configured graph provider offline and confirm the failure diagnostic still carries provider, graphProvider, endpoint, url, attempts, elapsedMs, timeoutMs, lifecycleStatus, and nextAction without apiKey leakage.
During Sub 3, consume providerReadiness.mjs from executeRemCycle() / refreshGoldenPath() instead of duplicating readiness logic.
14465cc8 - #12072 Part A: extract provider readiness helpers.
Operator directive (verbatim)
wait. i think this one should get rejected.
[...9 contract violations + old resolveGraphModelProvider shape quoted...]
and so much more magic numbers, hidden additional default values, bloat, confusion anti patterns. FRICTION. this must not happen. we can not keep fucked up tickets like that one open either. this would throw us 40 PRs back.
V-B-A on contract violations in ai/services/graph/providerReadiness.mjs
PR #12084's new module contains 9 patterns vetoed under the operator's foundational contract (#12061 cycle-4 anchor — "NO HIDDEN DEFAULT VALUE FALLBACKS IN CODE"):
Plus the regression to the pre-cycle-4b resolveGraphModelProvider() with if (config.graphProvider) return X; return derivation; shape.
PR was authored at 06:18Z — before the operator-VETO + cycle-4b contract enforcement landed (10:06Z). The PR's substance is structurally incompatible with the contract; reworking would require the same audit-pass discipline as cycle-4b, applied to a fresh extraction module + matched test suite.
Closure rationale
Per operator directive: "we can not keep fucked up tickets like that one open either. this would throw us 40 PRs back."
The ticket #12072 itself baked in the wrong shape — its prescription invited the extraction-with-fallbacks pattern. Future work doesn't need this intelligence; it would actively mislead future implementers. Wrong-shape close per feedback_close_vs_defer_for_premature_tickets discriminator ("does future work need this intelligence?" → no) AND operator-direct close authorization.
If extraction is later judged needed for orchestrator-as-SSOT consolidation (#12069 Sub 3 work), it should be authored fresh against the contract from cycle-one, not reworked from this PR's diff.
Authored by GPT-5 (Codex Desktop). Session 6ca1b510-51c3-4fac-aa39-a0fd6941318c. FAIR-band: over-target [18/30] - taking this lane despite over-target because this is operator-directed Sandman/REM incident mitigation, #12072 Part A was unblocked by the Contract Ledger backfill requested by GPT, and the implementation depends directly on GPT-owned PR #12061.
Status: DRAFT / stacked on PR #12061. Do not review or merge this PR before #12061 lands. Once #12061 is merged, this branch should be rebased so the visible delta collapses to the top #12072 extraction commit.
Refs #12072 Related: #12065 Related: #12062 Depends on: #12061
Extracts the graph-provider readiness helpers from
ai/scripts/runners/runSandman.mjsintoai/services/graph/providerReadiness.mjs, leavingrunSandmanas a thin consumer of the shared helper surface. The new module owns target resolution, provider probing, bounded retry metadata, durable failure diagnostics, and terminal/log failure recording for future orchestrator consumers.Evidence: L2 (unit tests for provider dispatch, readiness target resolution, bounded timeout metadata, durable log breadcrumb, and secret redaction) -> L4 required for full #12072 orchestrator/REM run-state integration. Residual: AC3/AC4/AC5/AC6/AC7/AC8 integration remains on #12072 / Sub 3 follow-up.
Dependency / Stacking
This PR is intentionally stacked on PR #12061 because current
origin/devdoes not yet containgraphProvider,resolveGraphModelProvider, or the post-#12061 readiness-target code that #12072 explicitly consumes.Review guidance while draft:
434356bddas PR #12061 dependency context.14465cc8as the #12072 implementation delta.origin/devand mark ready for review.Deltas from ticket
ai/services/graph/providerReadiness.mjsvia structural fast-path against siblingproviderDispatch.mjs.providerConfigandrequestTimeoutMsseams towaitForProvider()so callers can pass an aiConfig-shaped object and tests can assert the bounded timeout contract without slow sleeps.runSandman.mjsbehavior equivalent for Part A: it still performs the provider gate beforeDreamService.ready()and records the same durable diagnostic shape, now through the shared helper.Orchestrator.executeRemCycle()/refreshGoldenPath()or decay placement. Those are explicitly deferred to Sub 3 / Part B per #12072.Test Evidence
node --check ai/services/graph/providerReadiness.mjspassed.node --check ai/scripts/runners/runSandman.mjspassed.npm run test-unit -- test/playwright/unit/ai/services/graph/providerDispatch.spec.mjs test/playwright/unit/ai/services/graph/providerReadiness.spec.mjs test/playwright/unit/ai/scripts/runners/runSandman.spec.mjs-> 13 passed.git diff --check origin/dev...HEADpassed after rebasing onto currentorigin/dev.Signal Ledger
Unresolved Dissent
No new dissent for this Part A extraction. Residual full-ticket scope remains explicit above and on #12072.
Unresolved Liveness
Gemini-family liveness remains carried by Epic #12065 AC18. This PR adds no new liveness dependency beyond the epic ledger.
Post-Merge Validation
origin/dev, confirm the PR diff shows only #12072 files, and mark ready for review.npm run ai:run-sandmanwith the configured graph provider offline and confirm the failure diagnostic still carries provider, graphProvider, endpoint, url, attempts, elapsedMs, timeoutMs, lifecycleStatus, and nextAction without apiKey leakage.providerReadiness.mjsfromexecuteRemCycle()/refreshGoldenPath()instead of duplicating readiness logic.Commits
434356bdd- PR #12061 dependency context: graphProvider routing.14465cc8- #12072 Part A: extract provider readiness helpers.Operator directive (verbatim)
V-B-A on contract violations in
ai/services/graph/providerReadiness.mjsPR #12084's new module contains 9 patterns vetoed under the operator's foundational contract (#12061 cycle-4 anchor — "NO HIDDEN DEFAULT VALUE FALLBACKS IN CODE"):
const DEFAULT_OPENAI_COMPATIBLE_HOST = 'http://127.0.0.1:8000'const DEFAULT_OLLAMA_HOST = 'http://127.0.0.1:11434'return config.openAiCompatible?.host || DEFAULT_OPENAI_COMPATIBLE_HOST? config.ollama?.host || DEFAULT_OLLAMA_HOSTconfig.ollama?.model || null : config.openAiCompatible?.model || nullconfig.ollama?.embeddingModel || null/config.openAiCompatible?.embeddingModel || nullmodelProvider : config.modelProvider || nulldiagnostic.endpoint || ''Plus the regression to the pre-cycle-4b
resolveGraphModelProvider()withif (config.graphProvider) return X; return derivation;shape.PR was authored at 06:18Z — before the operator-VETO + cycle-4b contract enforcement landed (10:06Z). The PR's substance is structurally incompatible with the contract; reworking would require the same audit-pass discipline as cycle-4b, applied to a fresh extraction module + matched test suite.
Closure rationale
Per operator directive: "we can not keep fucked up tickets like that one open either. this would throw us 40 PRs back."
The ticket #12072 itself baked in the wrong shape — its prescription invited the extraction-with-fallbacks pattern. Future work doesn't need this intelligence; it would actively mislead future implementers. Wrong-shape close per
feedback_close_vs_defer_for_premature_ticketsdiscriminator ("does future work need this intelligence?" → no) AND operator-direct close authorization.Closing PR + linked ticket #12072 as
not_planned.If extraction is later judged needed for orchestrator-as-SSOT consolidation (#12069 Sub 3 work), it should be authored fresh against the contract from cycle-one, not reworked from this PR's diff.