Skip to content

feat(docs-sync): gh-aw hub, agent knowledge base and release notes (IP-2864, IP-2863, IP-2866) - #18

Open
DemidovVladimir wants to merge 3 commits into
mainfrom
feature/ip-2745-docs-sync
Open

feat(docs-sync): gh-aw hub, agent knowledge base and release notes (IP-2864, IP-2863, IP-2866)#18
DemidovVladimir wants to merge 3 commits into
mainfrom
feature/ip-2745-docs-sync

Conversation

@DemidovVladimir

@DemidovVladimir DemidovVladimir commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

⚠️ This is the first CI in this repository

moleculeprotocol/docs has had no .github/ directory until now. This PR adds one, along with two
new Actions secrets and a variable. The security review that gh aw's safe-update mode requires is
in Security review below — please read it before approving.

The docs-repo half of the docs-sync pipeline from the
IP-2745 spike. Implements DOCS-4
(IP-2864), DOCS-3
(IP-2863) and DOCS-6
(IP-2866).

The spoke half lives in moleculeprotocol/desci-infra#675.

What lands

File Ticket What it is
.github/workflows/docs-sync.md + .lock.yml DOCS-4 The gh-aw hub workflow and its compiled output. Both are reviewed; the lock is what actually runs.
.github/prompts/docs-sync.md DOCS-3 The agent knowledge base — page↔source map, style, guardrails, PR-body contract.
release-notes/** + SUMMARY.md DOCS-6 Consumer-facing per-version release notes, wired into the nav.
.github/aw/actions-lock.json, .gitattributes Generated by gh aw compile. Pins every action to a SHA.

This is inert on merge. Nothing runs until the credentials in
IP-2861 exist. Until then a dispatch is accepted with
204 and does nothing.

gh-aw pin: v0.86.2

Pinned and documented in the workflow header (with the reason — an IP-2864 acceptance criterion).
History: v0.85.4 was chosen 2026-08-07 to stay clear of the 0.68.4–0.71.3 billing bug; bumped to
v0.86.2 on 2026-08-14 for the Claude-harness retry fix
(github/gh-aw#51793 — at v0.85.4 a permission_denied
on a compound bash command could burn the whole retry budget, and this workflow's
engine: claude + strict bash allow-list is exactly that scenario) and for enforced secret
redaction in step summaries and patch artifacts (#50777/#50778). The bump is behaviour-neutral for
the write strategy
: safe-output schemas and the close_older_pull_requests handler are unchanged
between the two versions.

How it works

desci-infra fires a repository_dispatch (docs-sync) on each production release. The hub then:

  1. Gates on relevance in pre_activation, before the agent job exists. One compare API call, no
    clone. A release touching no documented surface stops here at zero AI credits — which is most
    releases. The gate is a deliberate triggering subset of the knowledge-base map; ride-along
    surfaces (deprecated IPNFT lambdas, secondary lib/ stacks) update only when a triggering path
    changed in the same release.
  2. Mints a read-only App token and checks desci-infra out at the released SHA into ./source.
  3. Fetches the release body into ./source/RELEASE_NOTES.md with the same read-only App
    (pre-agent-steps, on the runner, outside the agent container). The dispatch payload no longer
    carries the notes: it lands in a public repo's workflow run, and source release bodies now carry
    real content naming private infrastructure (desci-infra 2.0.0, 2026-08-12). The Releases API is
    covered by contents: read, so the App's permission set is unchanged.
  4. Runs the agent against the knowledge base, read-only.
  5. Opens a draft PR against this repo via gh-aw's sanitised safe-output job.

The proposal suggested skip-if-match for the gate. That does not work: skip-if-match evaluates
GitHub search queries, not changed paths, so it cannot express "did this release touch
graphql/schemas/**". Hence the custom pre-step.

Security review

Required by gh aw compile's safe-update mode, which flagged two new restricted secrets.

New secrets

  • ANTHROPIC_API_KEY — the engine key. The same org key desci-infra, desci-ecosystem and
    Labs already use for claude-review.yml and claude-release-notes.yml. gh-aw's Claude engine
    requires an API key as an Actions secret; provider OAuth is unsupported.
  • DOCS_SYNC_APP_KEY (+ variable DOCS_SYNC_APP_CLIENT_ID) — private key of a GitHub App holding
    contents: read on desci-infra and nothing else. It mints a ~1h token scoped per run to a
    single repo. It cannot write anywhere, and it is not installed on this repo.

Neither grants write access to any source repository. The only cross-repo write credential in
the whole design is the dispatch credential, and it lives in desci-infra and points at this
repo
— which is the property the hub-and-spoke shape exists to preserve.

Verified in the generated .lock.yml after the v0.86.2 recompile, not assumed:

  • The agent job has permissions: {contents: read}. Every write happens in the separate
    safe_outputs / conclusion jobs. gh-aw's strict mode rejects a write permission on the agent job
    at compile time.
  • Both secrets are excluded from the agent container (--exclude-env) and registered for log
    redaction. The release-body fetch runs on the runner, outside the container — its token never
    enters the agent's environment.
  • Network egress is deny-by-default against gh-aw's defaults allow-list.
  • Every action is pinned by SHA by the compiler (actions/checkout@3d3c42e… v7.0.1,
    actions/create-github-app-token@bcd2ba4… v3.2.0, gh-aw-actions/setup@6aab9e5… v0.86.2).

Prompt-injection posture. The release body originates in a pull-request description and is
untrusted. The knowledge base instructs the agent to treat ./source/RELEASE_NOTES.md as data to
summarise, never as instructions, and to report anything that looks like directions addressed to it.
Structurally the agent is read-only and cannot push — it can only emit a PR through the sanitised
safe-output job.

DOCS-2: the write strategy, and why it isn't a rolling PR

IP-2745 wanted the agent to append to one long-lived sync PR. Verified against the shipped handler
source at v0.85.4 (and re-checked at v0.86.2):

  • create-pull-request has one pulls.create call and zero pulls.update/pulls.list calls,
    and no branch: key. It can never update an existing PR.
  • push-to-pull-request-branch cannot bootstrap a PR, and under repository_dispatch its default
    target: "triggering" hard-fails. A rolling PR is technically constructible via
    target: "*" (the agent supplies the PR number itself) — the decision record explains why we
    rejected that shape: more prompt surface, PR-list access for the agent, and a wider push blast
    radius than "create a fresh draft".
  • update-pull-request cannot create.

So: new PR per run + close-older-pull-requests: true, which delivers the property actually
wanted — at most one open docs PR at a time — with no force-push or lost-review-history hazards.
Quick-succession dispatches serialize under the compiled workflow's concurrency group, so two runs
never race. Full rationale in desci-infra/docs/docs-sync-write-strategy.md.

The trade-off, stated plainly: an unreviewed docs PR is closed, not merged, when the next
release lands. The relevance gate keeps the real rate well below the release rate, and closed
branches are not deleted, so nothing is unrecoverable.

Pilot expiry is built in

stop-after: +14d is baked to an absolute 2026-08-28 10:16:52 UTC (refreshed at the v0.86.2
recompile), after which the trigger auto-disables. A plain recompile preserves the baked time —
it can only be extended deliberately, via gh aw compile --refresh-stop-time.

⚠️ If this PR has not merged by 2026-08-28, refresh the stop time before merging or the pilot
arrives already expired.

Release notes (DOCS-6)

New release-notes/ section, one page per API area, newest-first. It complements
api-reference/changelog.md rather than duplicating it: this section answers "what shipped in
1.0.14", that page answers "how do I migrate off ipnftUid". Cross-linked both ways.

The section ships unseeded, on purpose (commit aecc762): the entry-format template in
release-notes/README.md is the whole specification, and a hand-written worked example was the only
content in this PR asserting facts to external integrators. The first real entry comes from a pilot
run, which also exercises the pipeline properly. (Linear note: IP-2866's "seeded with real past
releases" AC should be amended to record this deviation.)

Known debt tracked separately: 43 stale isSuccess references across 9 reference pages from the
1.0.14 error-contract cutover.

Verification

gh aw compile (v0.86.2), gh aw validate --strict and gh aw lint all exit clean, and a
recompile produces zero drift against the committed lock. All SUMMARY.md and inter-page links
resolve.

Not verifiable until DOCS-1 lands: an end-to-end run. gh aw trial cannot help — it requires
workflow_dispatch, which this workflow does not have — so the first real exercise is the runbook's
manual dispatch test.

Two things to know before merging

  1. repository_dispatch only triggers workflows on the default branch. This must be on main
    before any dispatch does anything; it cannot be tested from this branch.
  2. The bots: allow-list must match the App slug exactly. repository_dispatch is not a gh-aw
    "safe event", so the pre-activation membership check runs against github.actor. If
    molecule-docs-sync[bot] does not match the App created in DOCS-1, every run is rejected
    silently
    . Confirm the literal actor string from the first run's pre_activation log.

🤖 Generated with Claude Code

DemidovVladimir and others added 3 commits August 10, 2026 10:18
…P-2864, IP-2863, IP-2866)

The docs-repo half of the docs-sync pipeline from the IP-2745 spike. Inert until
the credentials in IP-2861 exist and this lands on the default branch.

DOCS-4 (IP-2864) — .github/workflows/docs-sync.md + compiled docs-sync.lock.yml
  Triggered by repository_dispatch (docs-sync) from desci-infra on release.
  Pinned to gh-aw v0.85.4, outside the 0.68.4-0.71.3 billing bug.
  The agent job is contents:read; every write happens in the separate sanitized
  safe-outputs job. Verified in the generated lock.
  A deterministic relevance gate runs in pre_activation, before the agent job
  exists, so a release touching no documented surface costs zero AI credits.
  It uses one compare API call, not a clone. skip-if-match cannot do this - it
  evaluates GitHub search queries, not changed paths.
  stop-after +14d bakes the DOCS-7 pilot expiry to an absolute timestamp.

DOCS-3 (IP-2863) — .github/prompts/docs-sync.md
  Page-to-source map, house style, guardrails and the PR body contract. Points
  the agent at hand-authored sources and away from graphql/autogen and
  prisma/generated, which are build artefacts.

DOCS-6 (IP-2866) — release-notes/ + SUMMARY.md
  Consumer-facing per-version notes, complementing the thematic
  api-reference/changelog.md. Seeded from release 1.0.14, whose isSuccess
  removal is verifiable in PR #652 of desci-infra.

DOCS-2 (IP-2862) is materialized here as close-older-pull-requests: true. A
rolling sync PR is not achievable - create-pull-request calls pulls.create
unconditionally and can never update an existing PR. Rationale and the
alternatives are recorded in desci-infra/docs/docs-sync-write-strategy.md.

Security review of new restricted secrets, per gh-aw safe update mode:
  ANTHROPIC_API_KEY - engine key, same org key the other repos already use.
  DOCS_SYNC_APP_KEY - private key of a GitHub App holding contents:read on
  desci-infra and nothing else. Neither grants write on any source repo. The
  compiled lock excludes both from the agent container and redacts them in logs.

Verified: gh aw compile, gh aw validate --strict, gh aw lint all clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drops the hand-written 1.0.14 entry. The entry-format template in
release-notes/README.md is the whole specification; a worked example was a
marginal gain and it was the only content in this PR asserting facts to
external integrators on a public site. The first real entry now comes from a
pilot run, which also exercises the pipeline properly.

Removes the same assumption from the agent knowledge base, which told the agent
to imitate examples that no longer exist.

Note the 1.0.14 breaking change this entry described is real and still
undocumented: 43 references to the removed isSuccess field remain across 9
reference pages. Tracked separately - it needs the reference pages corrected,
not a release-notes entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e, close IP-2864 AC gaps

- Re-pin v0.85.4 -> v0.86.2 and record the pin + its reason in the workflow
  header (IP-2864 AC). v0.86.2 carries the Claude-harness retry fix
  (github/gh-aw#51793) whose trigger scenario is exactly this workflow's
  engine + strict bash allow-list, plus enforced secret redaction in step
  summaries and patch artifacts (#50777/#50778). Verified behaviour-neutral
  for the DOCS-2 write strategy: safe-output schemas and the
  close_older_pull_requests handler are unchanged between the two versions.

- Release notes now travel hub-side, not in the dispatch payload: a
  pre-agent step mints a token from the read-only source App (Releases API
  is covered by contents: read - no permission change) and writes the body
  to ./source/RELEASE_NOTES.md. The payload lands in a public repo's
  workflow run, and source release bodies now carry real content naming
  private infrastructure (desci-infra 2.0.0, 2026-08-12). Contract change
  lands in desci-infra#675 in the same breath.

- Add the claude-code-action fallback note (IP-2864 AC), flag it as
  unvalidated for repository_dispatch.

- Relevance gate: add docs/service-auth.md (mapped for authentication and
  roles pages, previously unreachable); document that the gate is a
  deliberate triggering subset of the map, with the ride-along rows named
  in the knowledge base.

- Knowledge base: account for the previously unmapped pages
  (technical-deep-dive/onchain-lab.md, module-registry/**, data/README.md,
  and the orphan api-reference/IPNFT-api.md) in the out-of-scope list.

- stop-after refreshed at compile: expiry is now 2026-08-28 10:16:52. If
  this PR has not merged by then, run `gh aw compile --refresh-stop-time`
  again before merging or the pilot arrives expired.

Recompiled with gh-aw v0.86.2; `gh aw validate --strict` and `gh aw lint`
both clean; agent job verified still contents: read only, bots allow-list
and max-ai-credits unchanged in the regenerated lock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant