Context
The Data Sync watchdog shipped in PR #15956 fired its first live alarm — #15962 — and it is not a run-status breach. Run axis healthy (streak 0, last success 2026-07-26T00:17:01Z); corpus axis breaching at 214 hours.
The alarm is correct and it points here. This ticket lands the backlog it names.
The Problem
resources/content/** on dev has not advanced since 2026-07-17T05:13:29Z. The current corpus exists only as uncommitted working-tree state in the primary checkout — 388 paths (387 additions, 1 deletion), current through #15955.
Two things lapsed on 2026-07-17: the Data Sync Pipeline's last successful run, and the last hand-authored chore: ticket sync commit. The coincidence is why this read as one outage; they are two distinct mechanisms.
Correction, 2026-07-26, post-merge. Measured after merge on @neo-opus-grace's challenge: runs?branch=dev&per_page=100 gives 98 failures / 2 successes, and the last dev success is 2026-07-17T03:20:56Z (run 29552162329). So the pipeline half was not closed by #15744 — that ticket is closed while dev has failed 98 times since. The two-distinct-mechanisms framing survives (credential-less stage vs. no corpus publisher); the "root cause closed" clause does not, and neither does "only one had an owner" if it is read as the pipeline half being resolved. It is not. The amended commit message on this branch does not carry the claim.
Why the pipeline recovering does not fix this. GENERATED_DATA_PATHS in buildScripts/dataSyncPipeline.mjs covers devindex data, portal data, sitemap.xml and llms.txt — not resources/content/**. rebuildContentIndexesAndSeo.mjs re-chunks and indexes the existing local tree — it has no fetch/octokit call sites and never pulls from GitHub, so the pipeline does not regenerate this corpus at all. The scheduled generator is the separate syncGithubWorkflow daemon lane (orchestrator task, 2h interval); what has no scheduled owner is the publisher that commits its output, tracked as #15977. Every chore: ticket sync commit in the repository's history is hand-authored (Ada, tobiu) — never github-actions[bot].
Who is actually affected. Local MCP reads resolve against the primary checkout's working tree, so agent ISSUE lookups were verified current during review (get_local_issue_by_id). That receipt covers the issue facet only — pull and discussion currency was never proved, and given those mirrors cap at 15350 / 15297 those local reads are likely stale too. What builds from committed dev is not: fresh clones, CI, and container KB ingestion (ai/deploy/Dockerfile.dockerignore keeps resources/content specifically for that). The corpus also survives only as uncommitted state — one reset --hard from needing full regeneration by a pipeline that took nine days to complete a run.
The Architectural Reality
buildScripts/dataSyncPipeline.mjs — GENERATED_DATA_PATHS excludes resources/content/**; rebuildContentIndexesAndSeo re-chunks into it each run.
.github/workflows/data-sync-pipeline.yml — the Pages step copies resources/content/$dir into the pages clone; nothing commits it back to neo.
ai/mcp/server/knowledge-base/configBase.mjs — TicketSource, DiscussionSource, PullRequestSource, ConceptSource all resolve under resources/content.
ai/mcp/server/github-workflow/configBase.mjs — contentRoot, issuesDir, archiveRoot, metadataFile.
ai/deploy/Dockerfile.dockerignore — "Keep resources/content for KB ingestion".
buildScripts/dataSyncWatchdog.mjs — the corpus axis that raised #15962.
The Fix
Land the 388-path delta on dev as one generated-content commit, advancing the ISSUE facet only to its declared snapshot watermark — issue-facet max id 15955, 903 active files across 10 chunks plus 9,773 archive issue files, extraction ~`2026-07-26T01:34:09Z`.
This does not restore resources/content/** to current, and must not claim to. pulls/ and discussions/ stay stale at 15350 / 15297 and are neither touched nor claimed. Landing one facet is honest only because #15975 / PR #15976 made the freshness witness facet-aware first, so this commit cannot certify siblings it did not touch.
Extraction is path-scoped and leaves the primary checkout untouched (git add -A -- resources/content → git diff --cached --binary → git reset -- resources/content), so agent reads against that tree are unaffected while the change is in review. The unrelated dirty runtime file (.neo-ai-data/concepts/nodes.jsonl) is deliberately excluded.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
resources/content/issues/** + resources/content/archive/issues/** on dev (the ISSUE facet only) |
GitHub, via the syncGithubWorkflow generator that produces the chunks |
advanced to the declared watermark (max id 15955) in one commit; content generated, not hand-edited; sibling facets pulls/ and discussions/ explicitly NOT advanced |
none — the facet is regenerable only by a syncGithubWorkflow run from dev |
this ticket + PR #15964's ## Snapshot watermark |
AC-1/AC-2 |
Decision Record impact
none. This restores generated content to its intended state; it changes no contract, schema or generation shape.
Acceptance Criteria
AC-1 narrowed by its author, 2026-07-26, on @neo-gpt-emmy's review of PR #15964. The original said "matches the current corpus … verified by the highest issue id" — an asymmetric witness that admits the exact false green it should catch. Measured at the PR head: issues reach 15955, but pulls stop at 15350 (live: 15971) and discussions at 15297 (live: D#15958), and .sync-metadata.json is untouched at 2026-07-17T05:12:38Z. One facet advanced; the AC only looked at that facet. I wrote a witness that could not fail on the defect my own PR had.
Out of Scope
- The recurrence mechanism — this lands the backlog, it does not decide who commits the corpus going forward. That is a real open question with a real trade-off (hourly commits of regenerated markdown against repository growth), and it is named in Related rather than silently bundled. The watchdog now detects the next lapse, which is why the one-off landing is safe to separate from the mechanism.
- The Data Sync credential root cause —
#15744, closed as a ticket, but not resolved on dev: 98/100 runs still fail (see the post-merge correction above).
- The watchdog itself —
#15948 / PR #15956.
- Any change to
GENERATED_DATA_PATHS or the generators.
Avoided Traps
- Direct push to
dev. The one-time operator authorization for this was granted and consumed earlier; this goes through a PR. The lint applies to our own work.
- Committing the dirty runtime file.
.neo-ai-data/concepts/nodes.jsonl is dirty in the same tree and is not generated content.
- Moving the primary checkout's HEAD to extract the files. That tree is what every agent's local MCP reads; branching it would have degraded corpus reads fleet-wide for the duration of review.
- Treating
#15962 as the close-target. It is a machine-maintained standing alarm that closes itself on recovery — it can never host a PR.
Related
#15962 — the live alarm this resolves (auto-closes; not a close-target)
#15948 / PR #15956 — the watchdog whose corpus axis raised it
#15744 — the credential root cause, closed as a ticket while dev still fails; @neo-opus-grace's #15986 / PR #15988 carries the live fix
- Recurrence mechanism — my original claim here was WRONG, corrected 2026-07-26.
This said "nothing commits resources/content/** on a schedule." There is a scheduled mechanism. githubWorkflowSync is an orchestrator lane on a 2-hour interval (ai/daemons/orchestrator/taskDefinitions.mjs:366, configBase.mjs:866), gated by githubWorkflowSyncEnabled — leaf(null) in localOnly, which resolves to enabled on a local profile. configBase.mjs:969 already names "a starved heavy peer (e.g. githubWorkflowSync)" as a known failure mode.
How I got it wrong: I grepped .github/workflows/ for a CI job that commits the corpus and found none. The writer is not a CI job — it is a daemon lane. I searched my model of the mechanism instead of searching for the mechanism, which is the same error shape this ticket's own PR review just caught in my AC.
The actual gap, evidence-backed: syncGithubWorkflow writes resources/content/{issues,pulls,discussions,release-notes}/ (and refuses to run off dev precisely because it writes there), while PrimaryRepoSyncService pulls and resets .sync-metadata.json. Neither commits. So the corpus is regenerated into every seat's working tree on a schedule, and the only thing that has ever published it to dev is a human running git commit by hand. That is why the last publish is dated the same day as the last hand-authored chore: ticket sync.
The recurrence question is therefore not "who should commit this?" but "the generator has a scheduled owner and the publisher has none." Still out of scope here, but now correctly stated.
- Watchdog facet-blindness (@neo-kimi-phoebe's lane, surfaced by @neo-gpt-emmy):
dataSyncWatchdog.mjs measures the latest commit anywhere under resources/content, so an issue-only landing resets the corpus clock and certifies stale pulls/ + discussions/ as fresh. The witness has lower cardinality than the corpus. This is why narrowing this PR's claim is necessary but not sufficient — the merge still moves the timestamp.
Live latest-open sweep: checked the latest 20 open issues at 2026-07-26T03:19:11Z; no equivalent found. A2A in-flight sweep: 12 most recent messages across all read-states; no competing claim on the corpus landing.
Retrieval Hint: resources/content corpus backlog committed dev staleness ticket sync landing
Context
The Data Sync watchdog shipped in PR
#15956fired its first live alarm —#15962— and it is not a run-status breach. Run axis healthy (streak 0, last success 2026-07-26T00:17:01Z); corpus axis breaching at 214 hours.The alarm is correct and it points here. This ticket lands the backlog it names.
The Problem
resources/content/**ondevhas not advanced since 2026-07-17T05:13:29Z. The current corpus exists only as uncommitted working-tree state in the primary checkout — 388 paths (387 additions, 1 deletion), current through#15955.Two things lapsed on 2026-07-17: the Data Sync Pipeline's last successful run, and the last hand-authored
chore: ticket synccommit. The coincidence is why this read as one outage; they are two distinct mechanisms.Why the pipeline recovering does not fix this.
GENERATED_DATA_PATHSinbuildScripts/dataSyncPipeline.mjscovers devindex data, portal data,sitemap.xmlandllms.txt— notresources/content/**.rebuildContentIndexesAndSeo.mjsre-chunks and indexes the existing local tree — it has nofetch/octokitcall sites and never pulls from GitHub, so the pipeline does not regenerate this corpus at all. The scheduled generator is the separatesyncGithubWorkflowdaemon lane (orchestrator task, 2h interval); what has no scheduled owner is the publisher that commits its output, tracked as #15977. Everychore: ticket synccommit in the repository's history is hand-authored (Ada,tobiu) — nevergithub-actions[bot].Who is actually affected. Local MCP reads resolve against the primary checkout's working tree, so agent ISSUE lookups were verified current during review (
get_local_issue_by_id). That receipt covers the issue facet only — pull and discussion currency was never proved, and given those mirrors cap at 15350 / 15297 those local reads are likely stale too. What builds from committeddevis not: fresh clones, CI, and container KB ingestion (ai/deploy/Dockerfile.dockerignorekeepsresources/contentspecifically for that). The corpus also survives only as uncommitted state — onereset --hardfrom needing full regeneration by a pipeline that took nine days to complete a run.The Architectural Reality
buildScripts/dataSyncPipeline.mjs—GENERATED_DATA_PATHSexcludesresources/content/**;rebuildContentIndexesAndSeore-chunks into it each run..github/workflows/data-sync-pipeline.yml— the Pages step copiesresources/content/$dirinto thepagesclone; nothing commits it back toneo.ai/mcp/server/knowledge-base/configBase.mjs—TicketSource,DiscussionSource,PullRequestSource,ConceptSourceall resolve underresources/content.ai/mcp/server/github-workflow/configBase.mjs—contentRoot,issuesDir,archiveRoot,metadataFile.ai/deploy/Dockerfile.dockerignore— "Keep resources/content for KB ingestion".buildScripts/dataSyncWatchdog.mjs— the corpus axis that raised#15962.The Fix
Land the 388-path delta on
devas one generated-content commit, advancing the ISSUE facet only to its declared snapshot watermark — issue-facet max id15955, 903 active files across 10 chunks plus 9,773 archive issue files, extraction ~`2026-07-26T01:34:09Z`.This does not restore
resources/content/**to current, and must not claim to.pulls/anddiscussions/stay stale at 15350 / 15297 and are neither touched nor claimed. Landing one facet is honest only because #15975 / PR #15976 made the freshness witness facet-aware first, so this commit cannot certify siblings it did not touch.Extraction is path-scoped and leaves the primary checkout untouched (
git add -A -- resources/content→git diff --cached --binary→git reset -- resources/content), so agent reads against that tree are unaffected while the change is in review. The unrelated dirty runtime file (.neo-ai-data/concepts/nodes.jsonl) is deliberately excluded.Contract Ledger Matrix
resources/content/issues/**+resources/content/archive/issues/**ondev(the ISSUE facet only)syncGithubWorkflowgenerator that produces the chunks15955) in one commit; content generated, not hand-edited; sibling facetspulls/anddiscussions/explicitly NOT advancedsyncGithubWorkflowrun fromdev## Snapshot watermarkDecision Record impact
none. This restores generated content to its intended state; it changes no contract, schema or generation shape.Acceptance Criteria
resources/content/**ondevmatches the generated tree — verified by the highest issue id, and the PR states plainly thatpulls/anddiscussions/remain stale rather than implying whole-corpus recoveryresources/content/**— no runtime state, no.neo-ai-data, nothing hand-editedgit status --porcelainbefore and after#15962claim.#15962was force-closed by recovery run30185925150at03:17:37Z— before the commit existed — so it can neither clear nor close on this merge. The real falsifier, observable now that PR #15976 is merged: on the next scheduled:20evaluation theissuesfacet reports fresh whilepulls/discussionskeep breaching and the standing alarm stays open. A fully green corpus axis would be the FAILING outcome.Out of Scope
#15744, closed as a ticket, but not resolved ondev: 98/100 runs still fail (see the post-merge correction above).#15948/ PR#15956.GENERATED_DATA_PATHSor the generators.Avoided Traps
dev. The one-time operator authorization for this was granted and consumed earlier; this goes through a PR. The lint applies to our own work..neo-ai-data/concepts/nodes.jsonlis dirty in the same tree and is not generated content.#15962as the close-target. It is a machine-maintained standing alarm that closes itself on recovery — it can never host a PR.Related
#15962— the live alarm this resolves (auto-closes; not a close-target)#15948/ PR#15956— the watchdog whose corpus axis raised it#15744— the credential root cause, closed as a ticket whiledevstill fails; @neo-opus-grace's #15986 / PR #15988 carries the live fixdataSyncWatchdog.mjsmeasures the latest commit anywhere underresources/content, so an issue-only landing resets the corpus clock and certifies stalepulls/+discussions/as fresh. The witness has lower cardinality than the corpus. This is why narrowing this PR's claim is necessary but not sufficient — the merge still moves the timestamp.Live latest-open sweep: checked the latest 20 open issues at 2026-07-26T03:19:11Z; no equivalent found. A2A in-flight sweep: 12 most recent messages across all read-states; no competing claim on the corpus landing.
Retrieval Hint:
resources/content corpus backlog committed dev staleness ticket sync landing