LearnNewsExamplesServices
Frontmatter
id17162
titleAn intake credential fault freezes corpus publication before a corpus exists
stateClosed
labels
bugaiarchitecturebuild
assigneesneo-opus-ada
createdAtAug 15, 2026, 11:32 AM
updatedAtAug 15, 2026, 2:54 PM
githubUrlhttps://github.com/neomjs/neo/issues/17162
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 15, 2026, 2:54 PM

An intake credential fault freezes corpus publication before a corpus exists

Closed Backlog/active-chunk-16 bugaiarchitecturebuild
neo-opus-ada
neo-opus-ada commented on Aug 15, 2026, 11:32 AM

Context

#17148 established the principle for this pipeline: an optional DevIndex intake failure must not decide whether the corpus publishes. It fixed that at the stage level — the four intake stages now defer instead of aborting emission.

The same defect exists one layer up, at the step level, and it is currently the larger exposure. Found while verifying the #17150 credential work, not from a live outage — filing before it bites rather than after.

The Problem

.github/workflows/data-sync-pipeline.yml runs, in order:

line step
69 Mint Publisher installation token
83 Mint Intake installation token
125 Checkout repository
164 Setup Node.js
176 Run bounded Data Sync emission and publish
190 Push Data to neomjs/pages

The intake mint at :83 has no continue-on-error and no if:, so any failure there fails the job — before checkout, before Node, before a single corpus byte is generated. The corpus then freezes for a reason that has nothing to do with the corpus: the DevIndex intake identity is an enrichment credential, and the corpus is emitted under the reader identity, which the intake mint has no bearing on.

actions/create-github-app-token fails the step when the credential cannot serve the request. Measured both ways on live probes, and both are real triggers:

injected fault mint result run
permission not granted to the installation HTTP 422"The permissions requested are not granted to this installation" 31877595849
named repository has no installation 404 Not Found 31879164402

Reading caution, learned the hard way on this ticket. Once continue-on-error is in place, a failed mint reports conclusion: success (only outcome keeps the failure), so it renders green in gh run view --json jobs. I read that field, concluded the mint could not fail, and briefly re-scoped this whole ticket on it — the guard had masked the failures that justify it. Check the mint by reading its log. Caught by @neo-gpt.

A third trigger, a rotated or expired private key, is untestable here. So the realistic triggers are ordinary administration, not exotic faults:

  • a permission is revoked on neomjs-devindex-intake while the workflow still requests it;
  • DATA_SYNC_INTAKE_PRIVATE_KEY is rotated or expires;
  • the installation is removed from one of the DevIndex repos.

This is newly likely. #17150 adds permission-contents: write to that mint because GitHub now infers collaborator status from contents-write, and @tobiu granted the App several permissions during that investigation with an explicit intent to clean up the extras. The workflow line and the App grant are now a coupled pair that must move together — and if they drift apart in the wrong direction, the failure is not "DevIndex opt-in is stale", it is "the whole corpus stops publishing", which is the 19-hour freeze #17131 recorded.

The Architectural Reality

  • .github/workflows/data-sync-pipeline.yml:83-109 — the intake mint, unguarded, ahead of everything.
  • .github/workflows/data-sync-pipeline.yml:125 — checkout; nothing before this contributes to the corpus.
  • buildScripts/dataSyncPipeline.mjsemissionCommands; the corpus stage runs under tokenScope: 'reader' (the implicit Actions token), and content indexes and SEO likewise. Neither consumes the intake token.
  • buildScripts/dataSyncPipeline.mjsemitGeneratedData already models the correct semantics for exactly this: a preflight denial is deferred, every stage still runs, the corpus publishes, and the deferred error is rethrown so the run still exits non-zero (#17148).

The runtime already knows how to degrade. The workflow does not, and it fails earlier.

The Fix

Make an intake mint failure degrade rather than abort, mirroring the semantics #17148 already shipped one layer down:

  1. continue-on-error: true on the intake mint step (the Publisher mint stays fatal — it is the publish credential, and without it there is nothing to fix).
  2. Pass the intake token through as it does today; when the mint failed, steps.intake-token.outputs.token is empty, and scopedStageEnv('intake') already produces a credential-less child, so the intake stages fail on their own terms and defer via publishGeneratedProgressOnFailure. No new error path is needed — the existing deferral absorbs it.
  3. Ensure the run still exits non-zero and names the mint as the cause, so this cannot become a silent channel where the corpus publishes forever while DevIndex intake is quietly dead.

Explicitly rejected — do NOT simply mark the step non-fatal and move on. Without (3), a revoked intake permission would produce indefinitely green runs with a permanently dead intake, which is strictly worse than today's loud failure. The point is to move the failure later, not to remove it.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Intake mint step .github/workflows/data-sync-pipeline.yml:83 tolerate failure; corpus still emits and publishes Publisher mint stays fatal inline comment dispatch with a deliberately invalid intake permission ⇒ corpus commit still produced
Run exit status on mint failure buildScripts/dataSyncPipeline.mjs deferral path non-zero, naming the mint unchanged when the mint succeeds inline comment same dispatch ⇒ run red, cause named

Decision Record impact

none — extends the stage-isolation property #17148 established to the step layer; introduces no new architectural authority.

Acceptance Criteria

  • An intake mint failure no longer prevents checkout, emission, or publication: a corpus commit is still produced.
  • That run still exits non-zero and names the intake mint as the cause — no silent green.
  • The Publisher mint remains fatal.
  • Verified by workflow_dispatch with a deliberately unsatisfiable intake permission, which fails the mint for real rather than simulating it.
  • The coupling note added by #17150 (workflow line and App grant move together) is updated to reflect that drift now degrades rather than freezes.

Out of Scope

  • The #17150 permission decision itself.
  • The DevIndex opt-in phase isolation (#17150), which is the same class inside the service rather than in the workflow.
  • Any change to the Publisher identity or the two-App split (#15744).

Avoided Traps

  • Assuming the runtime deferral already covers this. It does not: dataSyncPipeline.mjs never executes, because the job dies three steps before it is invoked. The fix has to be in the workflow.
  • Making the step non-fatal without preserving the failure. That trades a loud freeze for a silent dead intake — the carve-out-opens-a-silent-channel shape this pipeline has been bitten by before.
  • Treating this as hypothetical. The trigger is a scheduled permission cleanup that is already planned.

Related

  • #17148 / PR #17149 — the same blast-radius principle at the stage layer; this is its step-layer sibling.
  • #17150 / PR #17160 — adds the contents coupling that makes this newly reachable.
  • #17131 — the standing alarm that records what a corpus freeze costs (~19h).
  • #15744 — the two-identity split whose Publisher/intake asymmetry is the reason only one mint should be fatal.

Retrieval Hint: "intake mint failure aborts data sync before checkout; corpus freeze from an enrichment credential"

tobiu referenced in commit c03f3a5 - "fix(devindex): restore the stargazer read and stop a dead phase killing the working one (#17150) (#17160) on Aug 15, 2026, 2:53 PM
tobiu closed this issue on Aug 15, 2026, 2:54 PM
tobiu referenced in commit 6772ea0 - "fix(build): an intake credential fault no longer freezes corpus publication (#17162) (#17165) on Aug 15, 2026, 2:54 PM