Skip to content

Commit a8a98a1

Browse files
authored
Merge pull request #18 from MB3R-Lab/feat/mvp-bootstrap-contract
schema: publish via GitHub Pages and migrate URI to pages path
2 parents 373165f + 191ea67 commit a8a98a1

11 files changed

Lines changed: 239 additions & 16 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
name: Publish Schema
2+
3+
on:
4+
push:
5+
tags:
6+
- "schema-v*"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: "pages"
16+
cancel-in-progress: false
17+
18+
jobs:
19+
publish:
20+
runs-on: ubuntu-latest
21+
environment:
22+
name: github-pages
23+
url: ${{ steps.deployment.outputs.page_url }}
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
28+
- name: Resolve contract metadata
29+
id: contract
30+
shell: bash
31+
run: |
32+
set -euo pipefail
33+
34+
extract_const() {
35+
local key="$1"
36+
grep -E "^[[:space:]]*${key}[[:space:]]*=" internal/schema/constants.go | sed -E 's/.*"([^"]+)".*/\1/'
37+
}
38+
39+
SCHEMA_VERSION="$(extract_const ExpectedSchemaVersion)"
40+
SCHEMA_URI="$(extract_const ExpectedSchemaURI)"
41+
SCHEMA_DIGEST="$(extract_const ExpectedSchemaDigest)"
42+
43+
if [[ -z "${SCHEMA_VERSION}" || -z "${SCHEMA_URI}" || -z "${SCHEMA_DIGEST}" ]]; then
44+
echo "failed to resolve schema constants from internal/schema/constants.go"
45+
exit 1
46+
fi
47+
48+
if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
49+
if [[ "${GITHUB_REF_NAME}" != schema-v* ]]; then
50+
echo "unexpected tag: ${GITHUB_REF_NAME}; expected schema-v*"
51+
exit 1
52+
fi
53+
TAG_VERSION="${GITHUB_REF_NAME#schema-v}"
54+
if [[ "${TAG_VERSION}" != "${SCHEMA_VERSION}" ]]; then
55+
echo "tag version mismatch: tag=${TAG_VERSION} schema=${SCHEMA_VERSION}"
56+
exit 1
57+
fi
58+
else
59+
TAG_VERSION="${SCHEMA_VERSION}"
60+
fi
61+
62+
echo "schema_version=${SCHEMA_VERSION}" >> "${GITHUB_OUTPUT}"
63+
echo "schema_uri=${SCHEMA_URI}" >> "${GITHUB_OUTPUT}"
64+
echo "schema_digest=${SCHEMA_DIGEST}" >> "${GITHUB_OUTPUT}"
65+
echo "tag_version=${TAG_VERSION}" >> "${GITHUB_OUTPUT}"
66+
67+
- name: Validate schema JSON and $id binding
68+
shell: bash
69+
env:
70+
EXPECTED_URI: ${{ steps.contract.outputs.schema_uri }}
71+
EXPECTED_DIGEST: ${{ steps.contract.outputs.schema_digest }}
72+
run: |
73+
set -euo pipefail
74+
python - <<'PY'
75+
import hashlib
76+
import json
77+
import os
78+
from pathlib import Path
79+
80+
schema_path = Path("api/schema/model.schema.json")
81+
raw = schema_path.read_bytes()
82+
payload = json.loads(raw.decode("utf-8"))
83+
expected_uri = os.environ["EXPECTED_URI"]
84+
actual_uri = payload.get("$id")
85+
if actual_uri != expected_uri:
86+
raise SystemExit(f"schema $id mismatch: got={actual_uri!r} want={expected_uri!r}")
87+
88+
digest = "sha256:" + hashlib.sha256(raw).hexdigest()
89+
expected_digest = os.environ["EXPECTED_DIGEST"]
90+
if digest != expected_digest:
91+
raise SystemExit(f"schema digest mismatch: got={digest!r} want={expected_digest!r}")
92+
PY
93+
94+
- name: Build publish artifact
95+
shell: bash
96+
env:
97+
SCHEMA_VERSION: ${{ steps.contract.outputs.schema_version }}
98+
SCHEMA_URI: ${{ steps.contract.outputs.schema_uri }}
99+
SCHEMA_DIGEST: ${{ steps.contract.outputs.schema_digest }}
100+
run: |
101+
set -euo pipefail
102+
103+
ROOT="out"
104+
VERSION_DIR="${ROOT}/schema/model/v${SCHEMA_VERSION}"
105+
LATEST_DIR="${ROOT}/schema/model/latest"
106+
107+
mkdir -p "${VERSION_DIR}" "${LATEST_DIR}" "${ROOT}/schema"
108+
109+
cp api/schema/model.schema.json "${VERSION_DIR}/model.schema.json"
110+
cp api/schema/model.schema.json "${LATEST_DIR}/model.schema.json"
111+
112+
UPDATED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
113+
cat > "${ROOT}/schema/index.json" <<EOF
114+
{
115+
"name": "io.mb3r.bering.model",
116+
"version": "${SCHEMA_VERSION}",
117+
"uri": "${SCHEMA_URI}",
118+
"digest": "${SCHEMA_DIGEST}",
119+
"updated_at": "${UPDATED_AT}"
120+
}
121+
EOF
122+
123+
- name: Setup Pages
124+
uses: actions/configure-pages@v5
125+
126+
- name: Upload Pages artifact
127+
uses: actions/upload-pages-artifact@v3
128+
with:
129+
path: out
130+
131+
- name: Deploy to GitHub Pages
132+
id: deployment
133+
uses: actions/deploy-pages@v4

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Thanks for contributing to Bering.
99
3. Keep model contract compatibility intact:
1010
- `metadata.schema.name = io.mb3r.bering.model`
1111
- `metadata.schema.version = 1.0.0`
12-
- `metadata.schema.uri = https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json`
13-
- `metadata.schema.digest = sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7`
12+
- `metadata.schema.uri = https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json`
13+
- `metadata.schema.digest = sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7`
1414

1515
## PR checklist
1616

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Bering pins `metadata.schema` exactly:
88

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

1414
Any mismatch fails validation.
1515

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

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

92+
## Schema publishing
93+
94+
Schema publishing is automated via GitHub Pages and release tags.
95+
96+
- Workflow: `.github/workflows/publish-schema.yml`
97+
- Trigger: tags matching `schema-v*` (for example `schema-v1.0.0`)
98+
- Published paths:
99+
- `https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json`
100+
- `https://mb3r-lab.github.io/Bering/schema/model/latest/model.schema.json`
101+
- `https://mb3r-lab.github.io/Bering/schema/index.json`
102+
103+
Operational steps are documented in [docs/schema-publishing.md](docs/schema-publishing.md).
104+
92105
## CI and local checks
93106

94107
```bash

api/schema/model.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3-
"$id": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
3+
"$id": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
44
"title": "BeringResilienceModel",
55
"type": "object",
66
"required": [

docs/schema-publishing.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Schema Publishing (GitHub Pages)
2+
3+
This repository publishes the public model schema through GitHub Pages.
4+
5+
## One-time repository setup
6+
7+
1. Open `Settings -> Pages`.
8+
2. Set source to `GitHub Actions`.
9+
3. Ensure `Settings -> Actions -> General` allows workflows to deploy Pages.
10+
11+
After first deployment, GitHub creates the `github-pages` environment automatically.
12+
13+
## Publish workflow
14+
15+
- File: `.github/workflows/publish-schema.yml`
16+
- Trigger: `push` tags matching `schema-v*`
17+
- Optional emergency path: `workflow_dispatch`
18+
19+
The workflow:
20+
21+
1. Reads `ExpectedSchemaVersion`, `ExpectedSchemaURI`, and `ExpectedSchemaDigest` from `internal/schema/constants.go`.
22+
2. Validates tag/version binding (`schema-vX.Y.Z` must match `ExpectedSchemaVersion`).
23+
3. Validates schema JSON and `$id` binding.
24+
4. Builds a Pages artifact with:
25+
- `schema/model/v<version>/model.schema.json`
26+
- `schema/model/latest/model.schema.json`
27+
- `schema/index.json`
28+
5. Deploys to GitHub Pages.
29+
30+
## Release operation model
31+
32+
1. Merge schema changes into `main`.
33+
2. Create and push tag `schema-v<version>` (for example, `schema-v1.0.0`).
34+
3. Wait for workflow completion.
35+
4. Verify:
36+
- schema URL returns `200`
37+
- downloaded schema digest matches `ExpectedSchemaDigest`
38+
39+
## Notes
40+
41+
- This stage updates Bering only.
42+
- Sheaft currently pins strict URI and digest independently and must be migrated in a separate coordinated change.
43+

examples/outputs/bering-model.normalized.sample.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
"confidence": 0.94,
4141
"discovered_at": "2026-03-03T00:00:00Z",
4242
"schema": {
43-
"digest": "sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7",
43+
"digest": "sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7",
4444
"name": "io.mb3r.bering.model",
45-
"uri": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
45+
"uri": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
4646
"version": "1.0.0"
4747
},
4848
"source_ref": "bering://discover?input=examples%2Ftraces%2Fnormalized.sample.json",

examples/outputs/bering-model.otel.sample.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
"confidence": 0.94,
4141
"discovered_at": "2026-03-03T00:00:00Z",
4242
"schema": {
43-
"digest": "sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7",
43+
"digest": "sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7",
4444
"name": "io.mb3r.bering.model",
45-
"uri": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
45+
"uri": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
4646
"version": "1.0.0"
4747
},
4848
"source_ref": "bering://discover?input=examples%2Ftraces%2Fotel.sample.json",

internal/schema/constants.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package schema
33
const (
44
ExpectedSchemaName = "io.mb3r.bering.model"
55
ExpectedSchemaVersion = "1.0.0"
6-
ExpectedSchemaURI = "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json"
7-
ExpectedSchemaDigest = "sha256:7dc733936a9d3f94ab92f46a30d4c8d0f5c05d60670c4247786c59a3fe7630f7"
6+
ExpectedSchemaURI = "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json"
7+
ExpectedSchemaDigest = "sha256:272277c093f37580adcd2dded225bd37c86539d642d7910baad7e4228227d1a7"
88
)
99

1010
type SchemaRef struct {

internal/schema/contract_test.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package schema
22

3-
import "testing"
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
"strings"
8+
"testing"
9+
)
410

511
func TestEmbeddedSchemaDigestMatchesPinned(t *testing.T) {
612
t.Parallel()
@@ -17,3 +23,31 @@ func TestValidateStrict(t *testing.T) {
1723
t.Fatalf("expected strict validation to pass, got error: %v", err)
1824
}
1925
}
26+
27+
func TestEmbeddedSchemaIDMatchesExpectedURI(t *testing.T) {
28+
t.Parallel()
29+
30+
var payload map[string]any
31+
if err := json.Unmarshal(EmbeddedSchema(), &payload); err != nil {
32+
t.Fatalf("decode embedded schema: %v", err)
33+
}
34+
35+
id, _ := payload["$id"].(string)
36+
if id != ExpectedSchemaURI {
37+
t.Fatalf("schema $id mismatch: got=%q want=%q", id, ExpectedSchemaURI)
38+
}
39+
}
40+
41+
func TestExpectedSchemaURIVersionPathMatchesConstant(t *testing.T) {
42+
t.Parallel()
43+
44+
parsed, err := url.Parse(ExpectedSchemaURI)
45+
if err != nil {
46+
t.Fatalf("parse ExpectedSchemaURI: %v", err)
47+
}
48+
49+
wantSegment := fmt.Sprintf("/v%s/", ExpectedSchemaVersion)
50+
if !strings.Contains(parsed.Path, wantSegment) {
51+
t.Fatalf("ExpectedSchemaURI path %q must contain %q", parsed.Path, wantSegment)
52+
}
53+
}

internal/schema/schema/model.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3-
"$id": "https://schemas.mb3r.dev/bering/model/v1.0.0/model.schema.json",
3+
"$id": "https://mb3r-lab.github.io/Bering/schema/model/v1.0.0/model.schema.json",
44
"title": "BeringResilienceModel",
55
"type": "object",
66
"required": [

0 commit comments

Comments
 (0)