#17131 (the standing DATA-SYNC-ALARM) has been red since 2026-08-14T11:49:16Z — 15 consecutive scheduled failures on dev at filing time. The working hypothesis on pickup was that a merged PR broke it. That is falsified (evidence below): nothing the failing stage executes changed in the regression window. The pipeline is red because of a GitHub-side condition on the DevIndex intake repositories — but the blast radius of that condition is a Neo defect, and that defect is what this ticket fixes.
Observation vs inference. Observed: the failing run/step/error, the last-green control, the window diff, and the live API behaviour of the intake repos. Inferred: the GitHub-side cause of the stargazer 404 — unresolved, and deliberately not this ticket's target.
Regression window, pinned
run
head
UTC
last green
31797752252
45570db8e
2026-08-14T11:49:16Z
first red
31804178775
dd25854d4
2026-08-14T13:19:19Z
Every failure since fails the same job step — 6: Run bounded Data Sync emission and publish — at the same stage, DevIndex Opt-In:
[GitHub] [OptIn Stars] GraphQL Query Failed: GraphQL Query Errors: Resource not accessible by integration
[DataSync] stage "DevIndex Opt-In" failed with an UNRECOGNIZED error class; ... Declared credential scope: `intake`.
Why "a merged PR broke it" is falsified
git diff --stat 45570db8e..dd25854d4 touches only generated data-sync output (resources/content/**, apps/portal/resources/data/**, sitemap.xml), one learn doc, and test/playwright/** specs. Zero changes to apps/devindex/**, buildScripts/dataSync*.mjs, or .github/workflows/**.
apps/devindex/services/OptIn.mjs last changed 2026-02-22; apps/devindex/services/GitHub.mjs2026-07-23; .github/workflows/data-sync-pipeline.yml2026-07-28. Each mechanism predates the window it would have to explain.
Control: in the last green run the identical query ran under the identicalintake credential and succeeded — 12:05:23Z → [OptIn] No new opt-in requests found. Same code, same credential, 90 minutes apart.
What is actually denied
The intake installation is intact: assertDataSyncAccess's repository{id} probe reports reachable in every red run including the latest. Only the stargazers connection is denied. Independently reproducible outside the pipeline with a broad token:
probe
neomjs/neo
neomjs/devindex-opt-in
neomjs/devindex-opt-out
GraphQL stargazers
returns edges
edges: []
—
REST /stargazers
200 (3 rows)
404
404
REST repo root
200
200 (stargazers_count: 16)
200
A public, unarchived, non-disabled repository reporting 16 stars whose stargazer list 404s is a GitHub-side condition, not a Neo regression. Resolving that needs operator/admin action and is out of scope here.
The Problem
emitGeneratedData runs its stages sequentially and rethrows on the first stage failure (buildScripts/dataSyncPipeline.mjs:648). DevIndex Opt-In is stage 3 of 7. When it throws:
stages 4–7 never run — including content indexes and SEO (stage 7), which is what makes the corpus consumable;
the publish path below the loop is never reached, so the corpus generated by stage 2 is discarded;
nothing commits, so resources/content/** on dev stays frozen.
An optional enrichment read for the DevIndex opt-in feature therefore takes down corpus publication for the whole repository. At filing, mirrors/KB are ~19h stale and every downstream consumer (portal data, KB ingestion) is reading a frozen corpus. That coupling is the defect.
The mechanism to decouple it already exists and is already used: publishGeneratedProgressOnFailure. Stage 2 carries it. A stage that sets it defers its error, lets the remaining stages run, publishes what was generated, and then rethrows (:742-744, :844-849) — the run still goes RED. It is precisely "publish what we have, then fail loudly"; it was simply never applied to the intake stages.
The Architectural Reality
buildScripts/dataSyncPipeline.mjs:67-122 — emissionCommands, the 7-stage table. Only stage 2 sets publishGeneratedProgressOnFailure.
buildScripts/dataSyncPipeline.mjs:606-655 — the emission loop; :639-646 defer vs :648 throw.
buildScripts/dataSyncPipeline.mjs:742-744, :844-849 — deferredError is rethrown at both exits, so deferral is not suppression.
buildScripts/dataSyncPreflight.mjs:49-84 — probeRepository issues repository(owner,name){id} only. The purpose string claims a stargazer read the probe never performs, so the preflight logged neomjs/devindex-opt-in reachable (OptIn stargazer read) in the very runs whose stargazer read was denied 12 minutes later.
Second-order cost: because the preflight passed, the failure surfaced 12 minutes into the run as UNRECOGNIZED error class instead of at minute 1 with a named cause.
The Fix
Decouple enrichment from publication. Set publishGeneratedProgressOnFailure: true on the four DevIndex intake stages (DevIndex Opt-In, Opt-Out, Spider, Updater). The corpus then publishes and the run still fails on the deferred error.
Preserve every deferred failure. With four stages able to defer, a single deferredError slot silently drops all but the last. Accumulate and rethrow an aggregate so the run names every stage that failed.
Make the preflight probe what it claims. Extend probeRepository so the devindex-opt-in probe exercises the stargazers connection it advertises. A denial then fails fast at minute 1 naming repo and connection.
Deliberately NOT doing: adding Resource not accessible by integration to OptIn.mjs's NOT_FOUND skip branch. That converts a real permission regression into a silent no-op and the opt-in feature rots undetected — a carve-out that quiets the guard opens a silent channel.
unit spec: intake failure still publishes and still throws
emitGeneratedData return {deferredError}
buildScripts/dataSyncPipeline.mjs:580, :653
carry all deferred stage failures, not the last
single-failure shape unchanged when only one defers
@returns JSDoc
unit spec: 2 deferred stages ⇒ both named
probeRepository({owner,name,connection,token})
buildScripts/dataSyncPreflight.mjs:49
NEW required connection arg; selects that connection AND requires it to resolve
denial ⇒ same {ok:false, reason} shape
JSDoc
unit specs: connection is selected; partial denial (id present, connection null) is a failure
REQUIRED_REPOSITORIES[].connection
buildScripts/dataSyncPreflight.mjs:33
NEW field; names the connection the consuming stage reads, so probe and purpose cannot drift
absent ⇒ probe cannot select, treated as failure
JSDoc
unit spec: stargazers for opt-in, issues for opt-out
assertDataSyncAccess failure entries
buildScripts/dataSyncPreflight.mjs:156
entries now carry connection
additive; existing fields unchanged
inline
unit spec: per-repository classification preserved
denialRemedy(connection) (module-private)
buildScripts/dataSyncPreflight.mjs
NEW; remediation derives from the refused connection instead of one fixed Issues/Metadata string
unmapped connection ⇒ says so, never inherits another's advice
JSDoc
unit spec: stargazer denial must NOT print the Issues remedy
emitGeneratedData preflight handling
buildScripts/dataSyncPipeline.mjs:594
a preflight denial is DEFERRED, not thrown; no stage is skipped
DATA_SYNC_PREFLIGHT_ONLY=true still throws directly
JSDoc on options.preflight
unit specs: denial defers; Opt-Out/Spider/Updater still execute
Decision Record impact
none — this restores the stage-isolation property the existing publishGeneratedProgressOnFailure primitive already encodes; it introduces no new architectural authority.
Acceptance Criteria
A DevIndex Opt-In stage failure no longer aborts emission: stages 4–7 still run and the publish path is reached.
With an intake stage failing, generated corpus changes are committed and pushed to dev.
The run still exits non-zero on that deferred failure — #17131 must not be silenced by this change.
Two or more deferred stage failures are all named in the final error, not just the last.
probeRepository exercises the stargazers connection for devindex-opt-in; a denial is detected before collection starts and names repo + connection. (Corrected during review: originally said the denial "fails the preflight before collection starts". It is deferred, not fatal — rethrowing would re-couple publication to the intake identity one layer above the stage table this ticket decouples, freezing the corpus for the same reason, only faster.)
A denied capability does not stop stages that never consume it: an Opt-In stargazer denial still leaves Opt-Out, Spider, Updater and content indexes and SEO executing. (Added from @neo-gpt's PR #17149 review: the first implementation skipped every tokenScope: 'intake' stage, but Spider queries search/community endpoints and Updater reads users/:name/orgs — neither touches a DevIndex repository.)
Remediation derives from the refused connection: a stargazer denial must not print the Issues: Read and write remedy that the workflow already satisfies.
Unit coverage in test/playwright/unit/ai/buildScripts/DataSyncPipeline.spec.mjs for: intake-failure-still-publishes, still-throws, multi-deferred aggregation, preflight stargazer denial.
No change to OptIn.mjs error classification (no new skip branch).
Out of Scope
The GitHub-side stargazer 404 on both intake repos — operator/admin domain.
Widening the intake App's permissions (.github/workflows/data-sync-pipeline.yml:96-97) — human-owned credential settings.
#16532 (deprecated app-id in token minting) — adjacent, separate auth defect.
The two unrelated isolated failures inside the same window (31755201012: ladies-of-code community-scan 404; 31775846068: penalty-box path). Different causes, both self-recovered.
Avoided Traps
Diagnosing from the newest failing run. The newest run shows only the current top layer. Attribution came from the first red after the last green plus a step-sample across the window — which is also what separated the three distinct failure modes that all land on step 6.
Blaming a merged PR because the timing fits. The window diff and file histories date every candidate mechanism against the window; nothing in it touches this path.
Silencing the denial to turn the pipeline green. Green would then mean "opt-in silently dead". This unfreezes publication and keeps the run red.
Related
#17131 — the standing alarm this unfreezes. It stays open until the GitHub-side stargazer condition is resolved; that is correct behaviour, not a gap.
#16532 — Data Sync mints tokens with a deprecated app-id.
Retrieval Hint: "DevIndex intake denial freezes corpus publication; stargazers 404 on both intake repos"
Retrieval Hint: Commit range 45570db8e..dd25854d4
Context
#17131(the standing DATA-SYNC-ALARM) has been red since 2026-08-14T11:49:16Z — 15 consecutive scheduled failures ondevat filing time. The working hypothesis on pickup was that a merged PR broke it. That is falsified (evidence below): nothing the failing stage executes changed in the regression window. The pipeline is red because of a GitHub-side condition on the DevIndex intake repositories — but the blast radius of that condition is a Neo defect, and that defect is what this ticket fixes.Observation vs inference. Observed: the failing run/step/error, the last-green control, the window diff, and the live API behaviour of the intake repos. Inferred: the GitHub-side cause of the stargazer 404 — unresolved, and deliberately not this ticket's target.
Regression window, pinned
3179775225245570db8e31804178775dd25854d4Every failure since fails the same job step —
6: Run bounded Data Sync emission and publish— at the same stage,DevIndex Opt-In:Why "a merged PR broke it" is falsified
git diff --stat 45570db8e..dd25854d4touches only generated data-sync output (resources/content/**,apps/portal/resources/data/**,sitemap.xml), one learn doc, andtest/playwright/**specs. Zero changes toapps/devindex/**,buildScripts/dataSync*.mjs, or.github/workflows/**.apps/devindex/services/OptIn.mjslast changed 2026-02-22;apps/devindex/services/GitHub.mjs2026-07-23;.github/workflows/data-sync-pipeline.yml2026-07-28. Each mechanism predates the window it would have to explain.intakecredential and succeeded —12:05:23Z→[OptIn] No new opt-in requests found.Same code, same credential, 90 minutes apart.What is actually denied
The
intakeinstallation is intact:assertDataSyncAccess'srepository{id}probe reports reachable in every red run including the latest. Only thestargazersconnection is denied. Independently reproducible outside the pipeline with a broad token:neomjs/neoneomjs/devindex-opt-inneomjs/devindex-opt-outstargazersedges: []/stargazersstargazers_count: 16)A public, unarchived, non-disabled repository reporting 16 stars whose stargazer list 404s is a GitHub-side condition, not a Neo regression. Resolving that needs operator/admin action and is out of scope here.
The Problem
emitGeneratedDataruns its stages sequentially and rethrows on the first stage failure (buildScripts/dataSyncPipeline.mjs:648).DevIndex Opt-Inis stage 3 of 7. When it throws:content indexes and SEO(stage 7), which is what makes the corpus consumable;resources/content/**ondevstays frozen.An optional enrichment read for the DevIndex opt-in feature therefore takes down corpus publication for the whole repository. At filing, mirrors/KB are ~19h stale and every downstream consumer (portal data, KB ingestion) is reading a frozen corpus. That coupling is the defect.
The mechanism to decouple it already exists and is already used:
publishGeneratedProgressOnFailure. Stage 2 carries it. A stage that sets it defers its error, lets the remaining stages run, publishes what was generated, and then rethrows (:742-744,:844-849) — the run still goes RED. It is precisely "publish what we have, then fail loudly"; it was simply never applied to the intake stages.The Architectural Reality
buildScripts/dataSyncPipeline.mjs:67-122—emissionCommands, the 7-stage table. Only stage 2 setspublishGeneratedProgressOnFailure.buildScripts/dataSyncPipeline.mjs:606-655— the emission loop;:639-646defer vs:648throw.buildScripts/dataSyncPipeline.mjs:742-744,:844-849—deferredErroris rethrown at both exits, so deferral is not suppression.buildScripts/dataSyncPreflight.mjs:33-36—REQUIRED_REPOSITORIEScarriespurpose: 'OptIn stargazer read'.buildScripts/dataSyncPreflight.mjs:49-84—probeRepositoryissuesrepository(owner,name){id}only. Thepurposestring claims a stargazer read the probe never performs, so the preflight loggedneomjs/devindex-opt-in reachable (OptIn stargazer read)in the very runs whose stargazer read was denied 12 minutes later.Second-order cost: because the preflight passed, the failure surfaced 12 minutes into the run as
UNRECOGNIZED error classinstead of at minute 1 with a named cause.The Fix
publishGeneratedProgressOnFailure: trueon the four DevIndex intake stages (DevIndex Opt-In,Opt-Out,Spider,Updater). The corpus then publishes and the run still fails on the deferred error.deferredErrorslot silently drops all but the last. Accumulate and rethrow an aggregate so the run names every stage that failed.probeRepositoryso thedevindex-opt-inprobe exercises thestargazersconnection it advertises. A denial then fails fast at minute 1 naming repo and connection.Deliberately NOT doing: adding
Resource not accessible by integrationtoOptIn.mjs's NOT_FOUND skip branch. That converts a real permission regression into a silent no-op and the opt-in feature rots undetected — a carve-out that quiets the guard opens a silent channel.Contract Ledger Matrix
emissionCommands[].publishGeneratedProgressOnFailurebuildScripts/dataSyncPipeline.mjs:610(exists, defaultfalse)trueon the 4 DevIndex intake stagesemitGeneratedDatareturn{deferredError}buildScripts/dataSyncPipeline.mjs:580, :653@returnsJSDocprobeRepository({owner,name,connection,token})buildScripts/dataSyncPreflight.mjs:49connectionarg; selects that connection AND requires it to resolve{ok:false, reason}shapeREQUIRED_REPOSITORIES[].connectionbuildScripts/dataSyncPreflight.mjs:33purposecannot driftstargazersfor opt-in,issuesfor opt-outassertDataSyncAccessfailure entriesbuildScripts/dataSyncPreflight.mjs:156connectiondenialRemedy(connection)(module-private)buildScripts/dataSyncPreflight.mjsemitGeneratedDatapreflight handlingbuildScripts/dataSyncPipeline.mjs:594DATA_SYNC_PREFLIGHT_ONLY=truestill throws directlyoptions.preflightDecision Record impact
none— this restores the stage-isolation property the existingpublishGeneratedProgressOnFailureprimitive already encodes; it introduces no new architectural authority.Acceptance Criteria
DevIndex Opt-Instage failure no longer aborts emission: stages 4–7 still run and the publish path is reached.dev.#17131must not be silenced by this change.probeRepositoryexercises thestargazersconnection fordevindex-opt-in; a denial is detected before collection starts and names repo + connection. (Corrected during review: originally said the denial "fails the preflight before collection starts". It is deferred, not fatal — rethrowing would re-couple publication to the intake identity one layer above the stage table this ticket decouples, freezing the corpus for the same reason, only faster.)content indexes and SEOexecuting. (Added from @neo-gpt's PR #17149 review: the first implementation skipped everytokenScope: 'intake'stage, but Spider queries search/community endpoints and Updater readsusers/:name/orgs— neither touches a DevIndex repository.)Issues: Read and writeremedy that the workflow already satisfies.test/playwright/unit/ai/buildScripts/DataSyncPipeline.spec.mjsfor: intake-failure-still-publishes, still-throws, multi-deferred aggregation, preflight stargazer denial.OptIn.mjserror classification (no new skip branch).Out of Scope
intakeApp's permissions (.github/workflows/data-sync-pipeline.yml:96-97) — human-owned credential settings.#16532(deprecated app-id in token minting) — adjacent, separate auth defect.31755201012:ladies-of-codecommunity-scan 404;31775846068: penalty-box path). Different causes, both self-recovered.Avoided Traps
Related
#17131— the standing alarm this unfreezes. It stays open until the GitHub-side stargazer condition is resolved; that is correct behaviour, not a gap.#16532— Data Sync mints tokens with a deprecated app-id.Retrieval Hint: "DevIndex intake denial freezes corpus publication; stargazers 404 on both intake repos" Retrieval Hint: Commit range
45570db8e..dd25854d4