Skip to content

ci: publish the schemas and spec to contextgraphprotocol.org on merge - #78

Merged
macanderson merged 2 commits into
mainfrom
ci/publish-spec-to-aws
Aug 22, 2026
Merged

ci: publish the schemas and spec to contextgraphprotocol.org on merge#78
macanderson merged 2 commits into
mainfrom
ci/publish-spec-to-aws

Conversation

@macanderson

@macanderson macanderson commented Aug 22, 2026

Copy link
Copy Markdown
Owner

What

On merge to main, this repository publishes its artifacts to
contextgraphprotocol.org and asks the microsite to rebuild.

Source URL
schema/*.json /schema/…
schema/reference-vectors.ndjson /schema/reference-vectors.ndjson
SPEC.md /spec/SPEC.md
docs/** /spec/docs/…

This repository has no website of its own — the site is built from
macanderson/cgp-website — so what it publishes is artifacts, into two prefixes
of that site's bucket that belong to this repository and nothing else.

What it deliberately does not change

The schemas' $id still resolves to raw.githubusercontent.com, and
validate-examples.py still checks the copy served there byte-for-byte. What
lands on the site is a mirror on a branded host, not a new canonical
location. Promoting $id would change what every validator resolves — a
protocol-visible decision, not a deployment one, and out of scope here. Filed as
a follow-up.

Design notes worth a reviewer's attention

  • validate-examples.py runs in this workflow, not just in ci.yml.
    Observing another workflow's result needs a workflow_run trigger, whose
    failure mode is publishing anyway when the dependency is skipped. The
    distinction that matters is between "the schema validated somewhere" and "the
    bytes about to be published validated".
  • No --delete on the schema upload. A published schema URL is a contract
    other implementations resolve; removing one is a breaking change and must not
    be what a rename in this repository does silently on merge. Retiring one is an
    aws s3 rm made out loud. The spec/ upload does use --delete — those are
    documents, and renaming them is ordinary editing.
  • --content-type is spelled out. S3 guesses from the extension and would
    serve .ndjson as application/octet-stream, which browsers download rather
    than show.
  • The invalidation is scoped to /schema/* and /spec/*. A /* would also
    evict the microsite's pages, which this merge did not change — paid for, and a
    cold edge for readers who were not affected.
  • The verification step checks the body, not just the status. A static site
    serves its 404 page with a 200, so a status check alone would pass for a
    publish that landed nowhere. It pipes the response through jq -e '.title'.
  • Actions here are pinned to commit SHAs, unlike the rest of this
    repository's workflows, which use floating tags. A tag is mutable and this is
    the only job here holding credentials that can write to a production bucket.
    I did not touch the other workflows.

The seam with cgp-website

Both repositories write the same bucket. Two arrangements keep them apart, and
they are belt and braces on purpose:

  • cgp-website's deploy excludes schema/ and spec/ from its --delete
    sync (ci: publish contextgraphprotocol.org to AWS on merge to main cgp-website#14). Without it, --delete would remove
    everything this publishes — silently, because deleting a file the sync did not
    expect is not an error.
  • This repository's AWS role can write only those two prefixes, and the
    site's role is explicitly denied them. So if the excludes are ever dropped,
    that deploy fails loudly instead of corrupting this output.

Verification

  • python3 schema/validate-examples.py run locally: OK — all examples
    validate
    , including the $id-resolves-byte-identical check. That matters
    here because the publish job is gated on it — if it were failing today,
    nothing would ever publish.
  • Every run: block extracted and bash -n-checked.
  • All four uses: SHAs resolved against the GitHub API rather than copied.
  • The CloudFront function in front of this bucket passes through any path whose
    last segment contains a dot, so /schema/*.json and /spec/**/*.md reach S3
    unrewritten. Read from modules/static-site/rewrite.js.tftpl, not assumed.
  • Not yet proven: the publish itself, which cannot run until this is on main.
    First merge is the test; workflow_dispatch re-runs it.

One-time setup needed

  • production environment — created. The AWS role trusts exactly
    repo:macanderson/context-graph-protocol:environment:production; there is no
    stored AWS key.
  • SITE_DISPATCH_TOKENnot created, and I cannot create it. Writing to
    another repository is something GITHUB_TOKEN cannot do by design, so the
    rebuild trigger needs a fine-grained PAT with Contents: read-write on
    macanderson/cgp-website. Until it exists that step warns and the job still
    succeeds: the schemas and spec publish either way, and only the site rebuild
    waits for cgp-website's next own merge. This is the single manual step.

Infrastructure (OIDC provider, the role, its prefix-scoped policy) is applied and
lives in oxagen-aws-infrastacks/ci-deploy/.

Summary by Sourcery

Publish validated protocol schemas and documentation to contextgraphprotocol.org without changing their canonical raw GitHub identities.

New Features:

  • Publish schemas, reference vectors, the specification, and documentation to dedicated prefixes on contextgraphprotocol.org after changes are merged to main.
  • Trigger the cgp-website microsite to rebuild after protocol artifacts are published.

Bug Fixes:

  • Update deployment hygiene checks to recognize only the branded schema prefix as a served location while continuing to reject unsupported artifact URLs.

Enhancements:

  • Validate schema examples in the publishing workflow before uploading artifacts and verify the published schema body through the public hostname.
  • Protect schema URLs from accidental deletion while allowing ordinary document synchronization, with scoped CDN invalidation and prefix-isolated publishing.

CI:

  • Add a GitHub Actions workflow for validated, OIDC-authenticated publication of protocol artifacts and optional cross-repository microsite rebuilds.

Deployment:

  • Publish protocol artifacts to the shared site bucket using prefix-scoped AWS access and CDN invalidation.

Documentation:

  • Document the automated specification and schema publishing process, canonical schema identity, repository separation, and required production setup.
  • Amend the deployment-topology ADR to record the newly published schema and specification prefixes.

This repository has no website of its own, so what it publishes is artifacts:
schema/*.json and reference-vectors.ndjson under /schema, SPEC.md and docs/
under /spec, on the microsite's CDN. It then asks cgp-website to rebuild,
because the rendered documentation there quotes this specification and the two
would otherwise drift with nothing to say which is current.

validate-examples.py runs in this workflow rather than only in ci.yml. Reading
another workflow's result needs a workflow_run trigger, whose failure mode is
publishing anyway when the dependency is skipped.

The schema upload carries no --delete: a published schema URL is a contract
other implementations resolve, and removing one must not be something a rename
here does silently. The spec upload does, because those are documents.

Schemas' $id still resolves to raw.githubusercontent.com and the validator
still checks that copy byte-for-byte. What lands on the site is a mirror on a
branded host, not a new canonical location.

The dispatch step needs a token that can write to another repository, which
GITHUB_TOKEN cannot by design. Without SITE_DISPATCH_TOKEN it warns and the job
still succeeds, so a missing convenience cannot hide a successful publish.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds an automated GitHub Actions workflow that validates JSON schema examples and publishes schemas and spec artifacts to contextgraphprotocol.org on merges to main, documenting the behavior and required one‑time setup in PUBLISHING.md.

Sequence diagram for publishing validated artifacts

sequenceDiagram
    participant GitHub as GitHubActions
    participant Validator as validate-examples.py
    participant S3 as S3Bucket
    participant CloudFront
    participant Website as cgp-website

    GitHub->>Validator: python3 schema/validate-examples.py
    Validator-->>GitHub: validation succeeds
    GitHub->>S3: aws s3 sync schema/ to schema/ without --delete
    GitHub->>S3: aws s3 sync spec/ to spec/ with --delete
    GitHub->>CloudFront: aws cloudfront create-invalidation for /schema/* and /spec/*
    GitHub->>S3: curl published schema and jq -e .title
    GitHub->>Website: repository_dispatch rebuild request
    Website-->>GitHub: rebuild request accepted
Loading

File-Level Changes

Change Details Files
Introduce a dedicated GitHub Actions workflow that validates schema examples and, on non-PR events, publishes schemas and spec artifacts to the production S3 bucket and triggers downstream effects.
  • Define publish-spec workflow triggers for pull requests, pushes to main, and manual dispatch, scoped to schema, docs, SPEC.md, and the workflow itself.
  • Add a validate job that checks out the repo, installs Python and jsonschema, and runs schema/validate-examples.py to gate publishing.
  • Add a publish job that assumes an AWS role via OIDC with pinned action SHAs, restricted id-token permissions, and runs only outside pull_request events.
  • Sync JSON schema files to the S3 schema prefix without delete, with explicit content-type and cache-control, and copy the ndjson reference vectors with correct MIME type.
  • Sync spec docs and SPEC.md to the S3 spec prefix with delete, explicit Markdown content-type, and cache headers.
  • Invalidate CloudFront only for /schema/* and /spec/* paths and wait for completion.
  • Verify that a key schema URL is live by checking HTTP 200 and JSON body contents via jq, guarding against static-site 404s served as 200.
  • Request a rebuild of the microsite via repository_dispatch to macanderson/cgp-website when SITE_DISPATCH_TOKEN is present, otherwise warn and succeed.
.github/workflows/publish-spec.yml
Document the new spec and schema publishing workflow, its non-canonical mirror semantics, interaction with the microsite, and required setup.
  • Add a new section to PUBLISHING.md describing automatic publishing of schema/*.json, reference-vectors.ndjson, SPEC.md, and docs/** to contextgraphprotocol.org paths.
  • Clarify that schemas’ $id remain pointed at raw.githubusercontent.com and that the site is a branded mirror, not a new canonical location.
  • Explain the coordination between this repo and macanderson/cgp-website, including exclude and IAM prefix restrictions to prevent mutual overwrites.
  • Describe why schema retirement is manual (no delete on schema sync) while spec docs use delete for normal editing.
  • Document the need for a production environment wired to an AWS role via OIDC and the SITE_DISPATCH_TOKEN secret to trigger microsite rebuilds, with behavior when the secret is absent.
PUBLISHING.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@macanderson

Copy link
Copy Markdown
Owner Author

The mechanism is now proven end-to-end. macanderson/stella#4287 merged and
deployed stella.oxagen.sh through this exact path — same OIDC provider, same
constrained SSM document, same node-side script:

ssm command 8b6a0842-3126-4b0b-8e65-9c0e0135f5e1
fetching s3://oxagen-deploy-578673726240/_deploy/stella-standalone.tgz
starting stella from 20260822T201113Z (image node:22-alpine, port 3001, memory 512m)
healthy on 127.0.0.1:3001/
deployed stella 20260822T201113Z
stella.oxagen.sh -> 200

One thing had to be fixed to get there, and it affected this role too — it is
already applied, so nothing in this PR changes.
The first real deploy failed
twelve retries with Not authorized to perform sts:AssumeRoleWithWebIdentity.
The trust policy carried the subject every published example uses,
repo:<owner>/<name>:environment:<env>. CloudTrail's userIdentity.principalId
for the denied call carried what GitHub actually presents:

repo:macanderson@542881/stella@1297837446:environment:production

The immutable-id format — owner and repository each carrying their numeric
database id, so renaming a repository cannot hand its trust to whoever claims
the old name. All four roles now trust both spellings (still StringEquals over
literal strings, no wildcard). Verified on this PR's role directly with
aws iam get-role.

The same record retired a guess I had hedged against: the environment name in
the claim is verbatim from the workflow file. These repositories carry a
capital-P Production environment left over from Vercel's integration, the
workflow says environment: production, and the claim said production.

This PR is the event ADR 0008's guard anticipated in a comment: the repo
starts publishing artifacts to a host of its own, so URLs on that host stop
being 404s. The guard failed the PR that makes its premise obsolete.

Adding the host to SERVED_HOSTS would have been the wrong shape. This repo
publishes exactly two prefixes of the apex bucket (/schema/ and /spec/) and
owns nothing else there, so a bare host entry would re-bless
contextgraphprotocol.org/badges/conformant.svg — the precise 404 ADR 0008
was written about.

So SERVED_HOSTS becomes SERVED_PREFIXES, mapping each published URL prefix to
the repo directory whose bytes land there. That also strengthens the guard:
the "does this artifact exist" half was previously applied only to raw
URLs, and now covers every served prefix.

Verified against both directions: /badges/ on the apex still fails, and a
/schema/ URL on the apex naming a file not in schema/ fails the existence
half.
@macanderson
macanderson merged commit 2785462 into main Aug 22, 2026
21 checks passed
@macanderson
macanderson deleted the ci/publish-spec-to-aws branch August 22, 2026 23:47
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