Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions .github/workflows/publish-schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: Publish Schema

on:
push:
tags:
- "schema-v*"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Resolve contract metadata
id: contract
shell: bash
run: |
set -euo pipefail

extract_const() {
local key="$1"
grep -E "^[[:space:]]*${key}[[:space:]]*=" internal/schema/constants.go | sed -E 's/.*"([^"]+)".*/\1/'
}

SCHEMA_VERSION="$(extract_const ExpectedSchemaVersion)"
SCHEMA_URI="$(extract_const ExpectedSchemaURI)"
SCHEMA_DIGEST="$(extract_const ExpectedSchemaDigest)"

if [[ -z "${SCHEMA_VERSION}" || -z "${SCHEMA_URI}" || -z "${SCHEMA_DIGEST}" ]]; then
echo "failed to resolve schema constants from internal/schema/constants.go"
exit 1
fi

if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
if [[ "${GITHUB_REF_NAME}" != schema-v* ]]; then
echo "unexpected tag: ${GITHUB_REF_NAME}; expected schema-v*"
exit 1
fi
TAG_VERSION="${GITHUB_REF_NAME#schema-v}"
if [[ "${TAG_VERSION}" != "${SCHEMA_VERSION}" ]]; then
echo "tag version mismatch: tag=${TAG_VERSION} schema=${SCHEMA_VERSION}"
exit 1
fi
else
TAG_VERSION="${SCHEMA_VERSION}"
fi

echo "schema_version=${SCHEMA_VERSION}" >> "${GITHUB_OUTPUT}"
echo "schema_uri=${SCHEMA_URI}" >> "${GITHUB_OUTPUT}"
echo "schema_digest=${SCHEMA_DIGEST}" >> "${GITHUB_OUTPUT}"
echo "tag_version=${TAG_VERSION}" >> "${GITHUB_OUTPUT}"

- name: Validate schema JSON and $id binding
shell: bash
env:
EXPECTED_URI: ${{ steps.contract.outputs.schema_uri }}
EXPECTED_DIGEST: ${{ steps.contract.outputs.schema_digest }}
run: |
set -euo pipefail
python - <<'PY'
import hashlib
import json
import os
from pathlib import Path

schema_path = Path("api/schema/model.schema.json")
raw = schema_path.read_bytes()
payload = json.loads(raw.decode("utf-8"))
expected_uri = os.environ["EXPECTED_URI"]
actual_uri = payload.get("$id")
if actual_uri != expected_uri:
raise SystemExit(f"schema $id mismatch: got={actual_uri!r} want={expected_uri!r}")

digest = "sha256:" + hashlib.sha256(raw).hexdigest()
expected_digest = os.environ["EXPECTED_DIGEST"]
if digest != expected_digest:
raise SystemExit(f"schema digest mismatch: got={digest!r} want={expected_digest!r}")
PY

- name: Build publish artifact
shell: bash
env:
SCHEMA_VERSION: ${{ steps.contract.outputs.schema_version }}
SCHEMA_URI: ${{ steps.contract.outputs.schema_uri }}
SCHEMA_DIGEST: ${{ steps.contract.outputs.schema_digest }}
run: |
set -euo pipefail

ROOT="out"
VERSION_DIR="${ROOT}/schema/model/v${SCHEMA_VERSION}"
LATEST_DIR="${ROOT}/schema/model/latest"

mkdir -p "${VERSION_DIR}" "${LATEST_DIR}" "${ROOT}/schema"

cp api/schema/model.schema.json "${VERSION_DIR}/model.schema.json"
cp api/schema/model.schema.json "${LATEST_DIR}/model.schema.json"

UPDATED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
cat > "${ROOT}/schema/index.json" <<EOF
{
"name": "io.mb3r.bering.model",
"version": "${SCHEMA_VERSION}",
"uri": "${SCHEMA_URI}",
"digest": "${SCHEMA_DIGEST}",
"updated_at": "${UPDATED_AT}"
}
EOF

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: out

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Thanks for contributing to Bering.
3. Keep model contract compatibility intact:
- `metadata.schema.name = io.mb3r.bering.model`
- `metadata.schema.version = 1.0.0`
- `metadata.schema.uri = https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json`
- `metadata.schema.digest = sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7`
- `metadata.schema.uri = https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json`
- `metadata.schema.digest = sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7`

## PR checklist

Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Bering pins `metadata.schema` exactly:

- `name`: `io.mb3r.bering.model`
- `version`: `1.0.0`
- `uri`: `https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json`
- `digest`: `sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7`
- `uri`: `https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json`
- `digest`: `sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7`

Any mismatch fails validation.

Expand Down Expand Up @@ -89,6 +89,19 @@ Bering output is deterministic for identical inputs and flags:

Details: [docs/trace-input-format.md](docs/trace-input-format.md)

## Schema publishing

Schema publishing is automated via GitHub Pages and release tags.

- Workflow: `.github/workflows/publish-schema.yml`
- Trigger: tags matching `schema-v*` (for example `schema-v1.0.0`)
- Published paths:
- `https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json`
- `https://mb3r-lab.github.io/Bering/schema/model/latest/model.schema.json`
- `https://mb3r-lab.github.io/Bering/schema/index.json`

Operational steps are documented in [docs/schema-publishing.md](docs/schema-publishing.md).

## CI and local checks

```bash
Expand Down
2 changes: 1 addition & 1 deletion api/schema/model.schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
"$id": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
"title": "BeringResilienceModel",
"type": "object",
"required": [
Expand Down
43 changes: 43 additions & 0 deletions docs/schema-publishing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Schema Publishing (GitHub Pages)

This repository publishes the public model schema through GitHub Pages.

## One-time repository setup

1. Open `Settings -> Pages`.
2. Set source to `GitHub Actions`.
3. Ensure `Settings -> Actions -> General` allows workflows to deploy Pages.

After first deployment, GitHub creates the `github-pages` environment automatically.

## Publish workflow

- File: `.github/workflows/publish-schema.yml`
- Trigger: `push` tags matching `schema-v*`
- Optional emergency path: `workflow_dispatch`

The workflow:

1. Reads `ExpectedSchemaVersion`, `ExpectedSchemaURI`, and `ExpectedSchemaDigest` from `internal/schema/constants.go`.
2. Validates tag/version binding (`schema-vX.Y.Z` must match `ExpectedSchemaVersion`).
3. Validates schema JSON and `$id` binding.
4. Builds a Pages artifact with:
- `schema/model/v<version>/model.schema.json`
- `schema/model/latest/model.schema.json`
- `schema/index.json`
5. Deploys to GitHub Pages.

## Release operation model

1. Merge schema changes into `main`.
2. Create and push tag `schema-v<version>` (for example, `schema-v1.0.0`).
3. Wait for workflow completion.
4. Verify:
- schema URL returns `200`
- downloaded schema digest matches `ExpectedSchemaDigest`

## Notes

- This stage updates Bering only.
- Sheaft currently pins strict URI and digest independently and must be migrated in a separate coordinated change.

4 changes: 2 additions & 2 deletions examples/outputs/bering-model.normalized.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
"confidence": 0.94,
"discovered_at": "2026-03-03T00:00:00Z",
"schema": {
"digest": "sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7",
"digest": "sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7",
"name": "io.mb3r.bering.model",
"uri": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
"uri": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
"version": "1.0.0"
},
"source_ref": "bering://discover?input=examples%2Ftraces%2Fnormalized.sample.json",
Expand Down
4 changes: 2 additions & 2 deletions examples/outputs/bering-model.otel.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
"confidence": 0.94,
"discovered_at": "2026-03-03T00:00:00Z",
"schema": {
"digest": "sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7",
"digest": "sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7",
"name": "io.mb3r.bering.model",
"uri": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
"uri": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
"version": "1.0.0"
},
"source_ref": "bering://discover?input=examples%2Ftraces%2Fotel.sample.json",
Expand Down
4 changes: 2 additions & 2 deletions internal/schema/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package schema
const (
ExpectedSchemaName = "io.mb3r.bering.model"
ExpectedSchemaVersion = "1.0.0"
ExpectedSchemaURI = "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json"
ExpectedSchemaDigest = "sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7"
ExpectedSchemaURI = "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json"
ExpectedSchemaDigest = "sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7"
)

type SchemaRef struct {
Expand Down
36 changes: 35 additions & 1 deletion internal/schema/contract_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package schema

import "testing"
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"testing"
)

func TestEmbeddedSchemaDigestMatchesPinned(t *testing.T) {
t.Parallel()
Expand All @@ -17,3 +23,31 @@ func TestValidateStrict(t *testing.T) {
t.Fatalf("expected strict validation to pass, got error: %v", err)
}
}

func TestEmbeddedSchemaIDMatchesExpectedURI(t *testing.T) {
t.Parallel()

var payload map[string]any
if err := json.Unmarshal(EmbeddedSchema(), &payload); err != nil {
t.Fatalf("decode embedded schema: %v", err)
}

id, _ := payload["$id"].(string)
if id != ExpectedSchemaURI {
t.Fatalf("schema $id mismatch: got=%q want=%q", id, ExpectedSchemaURI)
}
}

func TestExpectedSchemaURIVersionPathMatchesConstant(t *testing.T) {
t.Parallel()

parsed, err := url.Parse(ExpectedSchemaURI)
if err != nil {
t.Fatalf("parse ExpectedSchemaURI: %v", err)
}

wantSegment := fmt.Sprintf("/v%s/", ExpectedSchemaVersion)
if !strings.Contains(parsed.Path, wantSegment) {
t.Fatalf("ExpectedSchemaURI path %q must contain %q", parsed.Path, wantSegment)
}
}
2 changes: 1 addition & 1 deletion internal/schema/schema/model.schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
"$id": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
"title": "BeringResilienceModel",
"type": "object",
"required": [
Expand Down
6 changes: 3 additions & 3 deletions internal/schema/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func TestValidateJSON_Success(t *testing.T) {
"schema":{
"name":"io.mb3r.bering.model",
"version":"1.0.0",
"uri":"https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
"digest":"sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7"
"uri":"https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
"digest":"sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7"
}
}
}`)
Expand All @@ -45,7 +45,7 @@ func TestValidateJSON_StrictDigestFail(t *testing.T) {
"schema":{
"name":"io.mb3r.bering.model",
"version":"1.0.0",
"uri":"https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
"uri":"https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
"digest":"sha256:deadbeef"
}
}
Expand Down