From a33a0b9863251157057b6a8165067a34440c7e6c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:00:09 +0000 Subject: [PATCH 1/9] feat(repl): repository commands %repo, %projects, %load and %publish over the SysML v2 API Split the standard SysML v2 API client (projects, branches, commits with change payloads, elements, roots, Link-paged collections, optional bearer token) into internal/translate/interop/sysmlapi; flexo keeps Layer 1 Turtle, organizations and ETag pushes over it. modelsync holds what sysml -sync and the REPL share: rooted cuts, provenance scope, notation, derived-property filtering and diff/apply over reposync. The REPL and the Jupyter kernel gain the OMG pilot kernel's four repository commands through a replext repository extension, with usage errors as *repl.UsageError, completion of flags and project names, and an httptest fake of the standard API exercising all four and a publish/load round trip. Co-Authored-By: jason.han --- .../repl-repository-commands.added.md | 2 + cmd/sysml/repository.go | 7 + docs/guide/12-jupyter.md | 41 ++ docs/reference/jupyter-kernel.md | 10 + docs/reference/repl-commands.md | 36 +- internal/frontend/jupyter/replengine.go | 4 + internal/frontend/jupyter/repository_test.go | 48 ++ internal/frontend/repl/complete.go | 5 + internal/frontend/repl/fakeapi_test.go | 323 ++++++++++ internal/frontend/repl/meta.go | 12 +- internal/frontend/repl/notebook.go | 6 +- internal/frontend/repl/notebook_test.go | 12 +- internal/frontend/repl/replext/all/all.go | 1 + internal/frontend/repl/replext/replext.go | 89 +++ .../repl/replext/repository/repository.go | 264 ++++++++ internal/frontend/repl/repository.go | 306 ++++++++++ internal/frontend/repl/repository_test.go | 359 +++++++++++ internal/frontend/repl/session.go | 7 + .../translate/interop/flexo/apply_measure.go | 6 +- internal/translate/interop/flexo/client.go | 311 ++-------- internal/translate/interop/flexo/measure.go | 5 +- .../translate/interop/flexo/repository.go | 209 +------ .../interop/flexo/repository_test.go | 137 ----- .../translate/interop/modelsync/modelsync.go | 320 ++++++++++ .../interop/modelsync/modelsync_test.go | 176 ++++++ internal/translate/interop/sysmlapi/client.go | 578 ++++++++++++++++++ internal/translate/interop/sysmlapi/commit.go | 116 ++++ .../translate/interop/sysmlapi/commit_test.go | 146 +++++ .../translate/interop/sysmlapi/repository.go | 137 +++++ .../{flexo => sysmlapi}/representation.go | 10 +- tests/hygiene/layering_test.go | 31 +- 31 files changed, 3107 insertions(+), 607 deletions(-) create mode 100644 changes/unreleased/repl-repository-commands.added.md create mode 100644 cmd/sysml/repository.go create mode 100644 internal/frontend/jupyter/repository_test.go create mode 100644 internal/frontend/repl/fakeapi_test.go create mode 100644 internal/frontend/repl/replext/repository/repository.go create mode 100644 internal/frontend/repl/repository.go create mode 100644 internal/frontend/repl/repository_test.go create mode 100644 internal/translate/interop/modelsync/modelsync.go create mode 100644 internal/translate/interop/modelsync/modelsync_test.go create mode 100644 internal/translate/interop/sysmlapi/client.go create mode 100644 internal/translate/interop/sysmlapi/commit.go create mode 100644 internal/translate/interop/sysmlapi/commit_test.go create mode 100644 internal/translate/interop/sysmlapi/repository.go rename internal/translate/interop/{flexo => sysmlapi}/representation.go (89%) diff --git a/changes/unreleased/repl-repository-commands.added.md b/changes/unreleased/repl-repository-commands.added.md new file mode 100644 index 0000000000..f609238d59 --- /dev/null +++ b/changes/unreleased/repl-repository-commands.added.md @@ -0,0 +1,2 @@ +- **The REPL and the Jupyter kernel read and write a SysML v2 API repository with the OMG pilot kernel's four commands.** `%repo []` shows or sets the server (seeded from `FLEXO_SYSMLV2_URL`, the token from `FLEXO_INTEROP_TOKEN`, never printed); `%projects` lists every project, paged to the end; `%load [--id=] [--name=] [--branch=] []` reads a project's model at a branch head into the session as notation and remembers where it came from; `%publish [-d] [--project=] [--branch=] ` writes the elements rooted in a session element as a new project or as a commit of what changed on the branch, reporting the commit id and the counts created, updated and deleted; `-d` includes derived properties. Any server speaking the standard API serves — the pilot's `SysML-v2-API-Services` without a token, Flexo MMS with one — and the kernel answers argument problems as `UsageError`. +- **The standard SysML v2 API client is its own package.** `internal/translate/interop/sysmlapi` drives projects, branches, commits with `change` payloads, elements and roots with optional bearer authentication and follows a server's `Link: rel="next"` paging to the end; `internal/translate/interop/flexo` keeps Flexo's extras — Layer 1 Turtle, organizations, ETag pushes — over it, and `internal/translate/interop/modelsync` holds what `sysml -sync` and the REPL share. diff --git a/cmd/sysml/repository.go b/cmd/sysml/repository.go new file mode 100644 index 0000000000..026155fb77 --- /dev/null +++ b/cmd/sysml/repository.go @@ -0,0 +1,7 @@ +//go:build !sysml_prod && !sysml_nosync + +package main + +import ( + _ "github.com/Open-MBEE/OpenSysML/internal/frontend/repl/replext/repository" // registers the repository REPL commands +) diff --git a/docs/guide/12-jupyter.md b/docs/guide/12-jupyter.md index 8b0217c61d..49a54778bc 100644 --- a/docs/guide/12-jupyter.md +++ b/docs/guide/12-jupyter.md @@ -249,6 +249,47 @@ The bounds the REPL takes from the environment (`OPENSYSML_MAX_STEPS`, kernel, read when it starts: set them in the environment of the notebook server. See [environment variables](../reference/environment.md). +## Working with a repository + +The kernel round-trips models with a SysML v2 API server through the same four commands the +OMG pilot's kernel has: `%repo` shows or sets the server, `%projects` lists its projects, +`%load` reads a project's model into the session and `%publish` writes elements back as a new +project or a commit. Any server speaking the standard API serves: the pilot's +`SysML-v2-API-Services` wants no token and the default URL is its `http://localhost:9000` +when `%repo` names it; Flexo MMS wants a bearer token and an organization. Set, in the +environment of the notebook server: + +| Variable | For the pilot API server | For Flexo MMS | +|---|---|---| +| `FLEXO_SYSMLV2_URL` | `http://localhost:9000` | the SysML v2 API endpoint, by default `http://localhost:8083` | +| `FLEXO_INTEROP_TOKEN` | unset | the bearer token; never put it in a cell | +| `FLEXO_SYSMLV2_ORG` | unset | the organization, by default `sysmlv2` | +| `FLEXO_ALLOW_PLAIN_HTTP` | unset on this machine | `1` to allow a plaintext `http://` server on another | + +Then, as the pilot's notebooks do: + +```text +%repo http://localhost:9000 +%projects +%load --name=Vehicles --branch=main +part def Truck :> Vehicles::Car; +%publish --project=Vehicles Truck +%publish -d --project="Trucks only" --branch=main Truck +``` + +`%load` takes the project by name, by `--id` or by `--name`, the branch by name or id or the +default when absent, and submits the model as a document, so the cells after it refer to the +loaded names; the session remembers which project, branch and commit, and a `%publish` of +what it loaded is a commit on that branch, not a second project. `%publish ` names the +project after the element's own name unless `--project` says otherwise; a project of that name +receives a commit of what changed against the branch head, reported as the commit id with the +counts created, updated and deleted, and no project of that name is created. `-d` sends every +derived property the exporter computes, as the pilot's `-d` does. An argument problem is a +`UsageError`; a server that cannot be reached, a missing project or branch, or a refused commit a +`CommandError` with the status and the server's message. A project name two projects share is +refused naming both ids: use `--id`. The [REPL commands reference](../reference/repl-commands.md#working-with-a-repository) +has each command's grammar. + ## Troubleshooting - **The kernel is not listed.** `jupyter kernelspec list` shows what the server sees; the diff --git a/docs/reference/jupyter-kernel.md b/docs/reference/jupyter-kernel.md index 300bb7b3f7..8073d19aab 100644 --- a/docs/reference/jupyter-kernel.md +++ b/docs/reference/jupyter-kernel.md @@ -144,6 +144,16 @@ kernel language is not `sysml` is refused. See A `%quit` in a cell is noted — the notebook's shutdown ends the kernel — and an unknown `%` command is an error (`UnknownCommand`), where the prompt would print guidance. +A `%` command given wrong arguments is a `UsageError` whose traceback is the command's usage; +a command that fails — a repository server that cannot be reached, a project that does not exist +— is a `CommandError` with the message the prompt would print, the HTTP status and the server's +message included. + +The repository commands `%repo`, `%projects`, `%load` and `%publish` address the SysML v2 API +server `FLEXO_SYSMLV2_URL` names, with the bearer token `FLEXO_INTEROP_TOKEN` when the server wants +one and Flexo's organization `FLEXO_SYSMLV2_ORG`, all read from the kernel's environment; a token +is never echoed and no state is written. See [Working with a repository](../guide/12-jupyter.md#working-with-a-repository). + ## Rich output Output with a richer form than plain text is a `display_data` carrying that form beside diff --git a/docs/reference/repl-commands.md b/docs/reference/repl-commands.md index 60af868689..3e2928d4ac 100644 --- a/docs/reference/repl-commands.md +++ b/docs/reference/repl-commands.md @@ -44,6 +44,7 @@ into the parts it holds (`car.fl.hub`, `#3.fl`, `car.wheels[2]`). | `%list` | List all declarations in current session | | `%clear` | Clear session (reset all declarations) | | `%load [--cells ] ...` | Submit the contents of files, directories or globs. A `.ipynb` notebook loads its code cells in order, each a source named ` cell `; the cells' `%` command lines and expressions are skipped and counted in the report, a cell tagged `skip-load` is skipped, and `--cells 1,3-5` or `--cells tag:` picks cells by position among the code cells or by tag ([Reusing another notebook](../guide/12-jupyter.md#reusing-another-notebook)) | +| `%load [--id=] [--name=] [--branch=] []` | Load a repository project's model into the session: the project named, or given by `--id` or `--name`, at the branch named by `--branch` (name or id) or its default branch. The elements at the branch head are read as SysML v2 API JSON and submitted as notation, so later input refers to the loaded names; the session remembers the project, branch and commit, and a `%publish` of the loaded element commits on that branch. A name two projects share is refused naming both ids. A lone name that is a path on disk — a separator, a `.sysml`/`.kerml`/`.ipynb` extension, a wildcard, or a file that exists — loads the files; a project whose name reads as one is loaded by `--name` ([Working with a repository](#working-with-a-repository)) | | `%print [name]` | Print the session model as SysML notation at the prompt, or only the named element and its body (`%print 'My Pkg'::Car`). Comments are kept, since the same writer `%save` writes notation with is used, and what is printed can be typed back in. Notation only: nothing about RDF is reported. Reading the model materializes nothing and leaves a debugging session running | | `%save ` | Write the session model to a file: `.sysml` notation (comments preserved), `.ttl` RDF or `.json` the API's element form, which are [experimental](rdf-mapping.md#status-experimental) and reported as such on each save | | `%import [as values] [map ] [format csv\|tsv\|json\|jsonl] [dry-run]` | Set the feature values a CSV, TSV, JSON or JSON Lines file assigns, one row per element: an `element` column names the element and each other column a feature, with a unit as `mass [kg]`. A declared value is replaced and an inherited one redefined; an empty cell leaves a value as it was. The file is checked whole first, and a row, value or unit the model refuses imports nothing. `map` names a JSON mapping file, `format` overrides the extension, and `dry-run` prints the values without changing the model. Same import as the CLI's [`-import`](cli.md#importing-data); see [Importing data into a model](../manual/importing-data.md) | @@ -118,7 +119,15 @@ into the parts it holds (`car.fl.hub`, `#3.fl`, `car.wheels[2]`). | `%advance