Schema Submission #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Schema Submission | |
| on: | |
| issues: | |
| types: [opened] | |
| jobs: | |
| ingest: | |
| if: startsWith(github.event.issue.title, 'schema-submission:') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: pip install -r requirements.txt | |
| - name: Parse issue metadata | |
| id: meta | |
| run: | | |
| TITLE="${{ github.event.issue.title }}" | |
| SCHEMA_NAME="${TITLE#schema-submission:}" | |
| SCHEMA_NAME=$(echo "$SCHEMA_NAME" | xargs | tr ' ' '-' | tr '[:upper:]' '[:lower:]') | |
| echo "schema_name=$SCHEMA_NAME" >> $GITHUB_OUTPUT | |
| # Determine bump type: | |
| # label "breaking" → major, default → minor | |
| LABELS='${{ toJson(github.event.issue.labels.*.name) }}' | |
| if echo "$LABELS" | grep -q "breaking"; then | |
| echo "bump=major" >> $GITHUB_OUTPUT | |
| else | |
| echo "bump=minor" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Extract and convert schema from issue body | |
| env: | |
| ISSUE_BODY: ${{ github.event.issue.body }} | |
| SCHEMA_NAME: ${{ steps.meta.outputs.schema_name }} | |
| run: | | |
| python3 - << 'PYEOF' | |
| import os, re, sys, json | |
| import yaml | |
| body = os.environ["ISSUE_BODY"] | |
| name = os.environ["SCHEMA_NAME"] | |
| # Detect format from the code-fence language tag: | |
| # ```linkml / ```yaml / ```yml → already LinkML | |
| # ```json / ```json-schema → JSON Schema, needs conversion | |
| fenced = re.search(r'```([\w-]*)\s*\n(.*?)```', body, re.DOTALL) | |
| if fenced: | |
| lang = (fenced.group(1) or "yaml").lower() | |
| content = fenced.group(2).strip() | |
| else: | |
| lang = "yaml" | |
| content = body.strip() | |
| json_schema_langs = {"json", "json-schema", "jsonschema"} | |
| linkml_langs = {"yaml", "yml", "linkml", ""} | |
| os.makedirs("schemas", exist_ok=True) | |
| out_path = f"schemas/{name}.yml" | |
| if lang in json_schema_langs: | |
| print(f"Detected format: JSON Schema (lang={lang!r}) — converting to LinkML…") | |
| sys.path.insert(0, ".") | |
| from neuro_ghost.converters.from_jsonschema import convert as js2lm | |
| try: | |
| data = json.loads(content) | |
| except json.JSONDecodeError: | |
| data = yaml.safe_load(content) | |
| linkml_dict = js2lm(data, name) | |
| with open(out_path, "w") as f: | |
| yaml.dump(linkml_dict, f, default_flow_style=False, | |
| allow_unicode=True, sort_keys=False) | |
| print(f"Converted → {out_path} " | |
| f"({len(linkml_dict['classes'])} classes, " | |
| f"{len(linkml_dict['slots'])} slots)") | |
| elif lang in linkml_langs or lang not in json_schema_langs: | |
| print(f"Detected format: LinkML (lang={lang!r})") | |
| with open(out_path, "w") as f: | |
| f.write(content) | |
| print(f"Wrote {out_path}") | |
| else: | |
| print(f"ERROR: Unsupported schema format '{lang}'. " | |
| "Use ```yaml (LinkML) or ```json (JSON Schema).") | |
| sys.exit(1) | |
| PYEOF | |
| - name: Validate LinkML schema | |
| run: | | |
| python neuro_ghost/validate_schema.py schemas/${{ steps.meta.outputs.schema_name }}.yml | |
| - name: Seed schema.org (if DB not cached) | |
| run: | | |
| python neuro_ghost/seed.py \ | |
| --db registry.lbug \ | |
| --registry-version 1.0.0 | |
| - name: Ingest schema | |
| run: | | |
| python neuro_ghost/ingest_linkml.py \ | |
| --file schemas/${{ steps.meta.outputs.schema_name }}.yml \ | |
| --db registry.lbug \ | |
| --issue ${{ github.event.issue.number }} \ | |
| --agent "${{ github.event.issue.user.login }}" | |
| - name: Run alignment | |
| run: | | |
| python neuro_ghost/align.py \ | |
| --source ${{ steps.meta.outputs.schema_name }} \ | |
| --db registry.lbug | |
| - name: Export + version registry | |
| run: | | |
| python neuro_ghost/export_json.py \ | |
| --db registry.lbug \ | |
| --bump ${{ steps.meta.outputs.bump }} \ | |
| --issue ${{ github.event.issue.number }} \ | |
| --agent "${{ github.event.issue.user.login }}" \ | |
| --schema ${{ steps.meta.outputs.schema_name }} | |
| - name: Commit all artifacts | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add \ | |
| schemas/${{ steps.meta.outputs.schema_name }}.yml \ | |
| data/registry.json \ | |
| data/versions/ \ | |
| data/provenance.json | |
| git diff --cached --quiet || git commit -m \ | |
| "feat(registry): ingest ${{ steps.meta.outputs.schema_name }} from issue #${{ github.event.issue.number }}" | |
| git push | |
| - name: Comment success | |
| if: success() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const name = "${{ steps.meta.outputs.schema_name }}"; | |
| const pagesUrl = `https://${context.repo.owner}.github.io/${context.repo.repo}`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: [ | |
| `✅ **\`${name}\` ingested successfully.**`, | |
| ``, | |
| `- Classes, properties, and alignments committed.`, | |
| `- Provenance logged to \`data/provenance.json\`.`, | |
| `- Archived snapshot in \`data/versions/\`.`, | |
| ``, | |
| `Browse: ${pagesUrl}`, | |
| ].join("\n"), | |
| }); | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| issue_number: context.issue.number, state: "closed", | |
| }); | |
| - name: Comment failure | |
| if: failure() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: [ | |
| `❌ **Ingestion failed.**`, | |
| ``, | |
| `Check the run log for the specific LinkML validation error (e.g. an undefined slot, ` + | |
| `\`is_a\` parent, or range reference).`, | |
| ``, | |
| `[View run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`, | |
| ].join("\n"), | |
| }); |