From aba63209d3b283c5fb2fdfe2880f6fd11f590836 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:58:57 +0000 Subject: [PATCH 1/5] refactor: remove the deprecated names and flags ahead of 0.12.0 Remove the surfaces deprecated in releases up to 0.10.0 whose replacements are documented: the sysml CLI's -to and -pdf-title-page/-pdf-toc/ -pdf-number-sections, the Python client's opensysml.eval and RuntimeError aliases, the Node client's strict parse option, and the Go client's EditResult.Content. ApplyEdits and ApplyDocumentEdits now require the edit_documents capability, which every service since 0.9.0 advertises. The tests that only exercised the deprecated names go with them; the others read the replacements. Docs, the manual page and the changelog fragments follow. Co-Authored-By: jason.han --- .agents/skills/testing-doc-pdf/SKILL.md | 2 +- .../cli-former-flag-names.removed.md | 1 + .../go-edit-result-content.removed.md | 1 + .../unreleased/node-strict-alias.removed.md | 1 + .../python-deprecated-aliases.removed.md | 1 + client/node/conformance/runner.ts | 2 +- client/node/src/core/connection.ts | 4 +- client/node/src/core/model.ts | 25 +------ client/node/test/sources.test.ts | 15 ---- client/opensysml/README.md | 27 ++++---- client/opensysml/authoring.go | 39 ++--------- client/opensysml/surface_test.go | 68 +++++++++---------- client/python/opensysml/__init__.py | 23 ------- client/python/opensysml/errors.py | 24 ------- client/python/tests/test_api.py | 46 +------------ client/python/tests/test_errors.py | 10 --- cmd/sysml/convert.go | 8 --- cmd/sysml/convert_test.go | 1 - cmd/sysml/features.go | 3 +- cmd/sysml/mcp_catalog_test.go | 10 +-- cmd/sysml/render_document_pdf_test.go | 4 +- cmd/sysml/render_pdf.go | 3 - cmd/sysml/usage.go | 10 --- docs/clients/python/errors.md | 12 ++-- docs/guide/07-saving-and-rdf.md | 2 +- docs/internals/design/api-surface-parity.md | 2 +- docs/manual/outputs.md | 3 +- docs/project/demo.md | 4 +- docs/project/html-document-backend.md | 7 +- docs/project/spec-compliance.md | 2 +- docs/reference/api.md | 11 ++- docs/reference/cli.md | 4 -- docs/reference/node-api.md | 5 +- internal/frontend/usage/usage.go | 2 +- packaging/man/man1/sysml.1 | 13 ---- tools/cmd/conformance/pkgclient.go | 9 +-- 36 files changed, 97 insertions(+), 307 deletions(-) create mode 100644 changes/unreleased/cli-former-flag-names.removed.md create mode 100644 changes/unreleased/go-edit-result-content.removed.md create mode 100644 changes/unreleased/node-strict-alias.removed.md create mode 100644 changes/unreleased/python-deprecated-aliases.removed.md diff --git a/.agents/skills/testing-doc-pdf/SKILL.md b/.agents/skills/testing-doc-pdf/SKILL.md index 062f483eff..4c5c409bf2 100644 --- a/.agents/skills/testing-doc-pdf/SKILL.md +++ b/.agents/skills/testing-doc-pdf/SKILL.md @@ -20,7 +20,7 @@ description: How to end-to-end test the sysml PDF document backend (internal/doc - Diagram forms: render the worked example with `-diagram-form dot`, `-diagram-form plantuml` and `-diagram-form d2` too; the PDF must contain no "did not draw" notice (pypdf `extract_text`) and the page's XObjects grow by one per diagram. With `OPENSYSML_DOT=/nonexistent` the render still succeeds and the notice names `OPENSYSML_DOT`; the same with `OPENSYSML_D2`. A `// layout: neato -n` block (the DOT writer emits one for a positioned diagram) must run neato: `dot -Kneato` failing with "no layout engine support" means the GTS library is missing. ## Rendering -- Worked example: `bin/sysml docs/manual/examples/observatory.sysml -render-document Observatory::MassReport -doc-form pdf -pdf-title-page -pdf-toc -pdf-number-sections -o /tmp/observatory.pdf`. It exercises emphasis, code span, external Link, Ref (`#breakdown`), a standalone `` anchor line, grouped table (`**zone: ...**` headings), numbered list and two mermaid diagrams. +- Worked example: `bin/sysml docs/manual/examples/observatory.sysml -render-document Observatory::MassReport -doc-form pdf -doc-title-page -doc-toc -doc-number-sections -o /tmp/observatory.pdf`. It exercises emphasis, code span, external Link, Ref (`#breakdown`), a standalone `` anchor line, grouped table (`**zone: ...**` headings), numbered list and two mermaid diagrams. - Richest input: `internal/doc/docrender/testdata/telescope_report.sysml` → `Observatory::MassReport` covers every inline construct including escaped `\*`/`\|`/`` \` `` prose and reference links; `internal/doc/docpdf/fixture_test.go` evaluates it into a `*docir.Document`, which is what `docpdf.Render(document, "weasyprint", docpdf.Options{...})` takes (not Markdown). - Markdown regression: `-o foo.md` must diff-equal `docs/manual/examples/observatory.md`. Note `-doc-form` takes `markdown`, `html` or `pdf` (not `md`). - WeasyPrint and Prince read the HTML backend's page (`docrender.HTML`, the same markup as `-doc-form html`) under `internal/doc/docpdf/print.css`, so `-html-theme`, `-html-css` and `-html-no-default-css` change the PDF; pandoc reads the Markdown and refuses the theme flags with a typed error. Inspect the prepared input with `docpdf.Render`'s fake-tool tests (`TestRenderHTMLIsTheBackendsPage`) or a fake `OPENSYSML_WEASYPRINT` script that copies its input file aside. diff --git a/changes/unreleased/cli-former-flag-names.removed.md b/changes/unreleased/cli-former-flag-names.removed.md new file mode 100644 index 0000000000..55cd6b08fc --- /dev/null +++ b/changes/unreleased/cli-former-flag-names.removed.md @@ -0,0 +1 @@ +- The `sysml` CLI's former flag names: `-to`, replaced by `-convert` in 0.0.7, and `-pdf-title-page`, `-pdf-toc` and `-pdf-number-sections`, replaced by `-doc-title-page`, `-doc-toc` and `-doc-number-sections` in 0.9.0. Only the replacements are declared. diff --git a/changes/unreleased/go-edit-result-content.removed.md b/changes/unreleased/go-edit-result-content.removed.md new file mode 100644 index 0000000000..e8f74f580f --- /dev/null +++ b/changes/unreleased/go-edit-result-content.removed.md @@ -0,0 +1 @@ +- The Go client's `EditResult.Content`, deprecated in 0.9.0 in favour of `EditResult.Documents`, which carries the edited notation of a model of one document as well as of several. `ApplyEdits` and `ApplyDocumentEdits` now require the `edit_documents` capability every service since 0.9.0 advertises, and are refused with `CodeUnimplemented` by one without it. diff --git a/changes/unreleased/node-strict-alias.removed.md b/changes/unreleased/node-strict-alias.removed.md new file mode 100644 index 0000000000..3ea6359dca --- /dev/null +++ b/changes/unreleased/node-strict-alias.removed.md @@ -0,0 +1 @@ +- The Node client's deprecated `strict` parse option, an alias of `strictConformance` since 0.10.0; pass `strictConformance`. diff --git a/changes/unreleased/python-deprecated-aliases.removed.md b/changes/unreleased/python-deprecated-aliases.removed.md new file mode 100644 index 0000000000..2d3711b366 --- /dev/null +++ b/changes/unreleased/python-deprecated-aliases.removed.md @@ -0,0 +1 @@ +- The Python client's deprecated aliases `opensysml.eval`, `opensysml.RuntimeError` and `opensysml.errors.RuntimeError`, which warned and answered `opensysml.evaluate` and `opensysml.ExecutionError` since 0.9.0; write the replacements. diff --git a/client/node/conformance/runner.ts b/client/node/conformance/runner.ts index fbe72c6615..f51bee3bac 100644 --- a/client/node/conformance/runner.ts +++ b/client/node/conformance/runner.ts @@ -577,7 +577,7 @@ export class Runner { model.fixtures === undefined ? await this.connection.loads(this.fixture(model.fixture ?? ""), { ...(model.language === undefined ? {} : { language: model.language }), - ...(model.strict_conformance === undefined ? {} : { strict: model.strict_conformance }), + ...(model.strict_conformance === undefined ? {} : { strictConformance: model.strict_conformance }), }) : await this.connection.parseSources( model.fixtures.map((name) => SourceDocument.inline(name, this.fixture(name))), diff --git a/client/node/src/core/connection.ts b/client/node/src/core/connection.ts index 9a54e5db7e..e29a70264f 100644 --- a/client/node/src/core/connection.ts +++ b/client/node/src/core/connection.ts @@ -103,7 +103,7 @@ import { } from "../generated/sysml_pb.js"; import { callRpc, fromHandshakeError, fromRpcError } from "./status.js"; import { requireString } from "./arguments.js"; -import { decodeDiagnostic, Instance, Model, strictConformanceOf } from "./model.js"; +import { decodeDiagnostic, Instance, Model } from "./model.js"; import type { ModelDiagnostic } from "./errors.js"; import type { ParseOptions } from "./model.js"; import { sourceDocuments, type Source } from "./sources.js"; @@ -375,7 +375,7 @@ export class Connection { ); capabilities.push(CAPABILITY_INLINE_LANGUAGE); } - const strictConformance = strictConformanceOf(options); + const { strictConformance } = options; if (strictConformance === true) { this.requireStrictConformance(); capabilities.push(CAPABILITY_STRICT_CONFORMANCE); diff --git a/client/node/src/core/model.ts b/client/node/src/core/model.ts index c7ac43d0bb..8235d48625 100644 --- a/client/node/src/core/model.ts +++ b/client/node/src/core/model.ts @@ -82,29 +82,6 @@ export interface ParseOptions { language?: string; /** Reject the OpenSysML notation extensions; requires `strict_conformance`. */ strictConformance?: boolean; - /** - * @deprecated Use `strictConformance`; `strict` is its alias. Passing both - * with different values is refused. - */ - strict?: boolean; -} - -/** Resolves `strictConformance` and its `strict` alias; differing values refuse. */ -export function strictConformanceOf(options: { - strict?: boolean; - strictConformance?: boolean; -}): boolean | undefined { - if ( - options.strict !== undefined && - options.strictConformance !== undefined && - options.strict !== options.strictConformance - ) { - throw new RangeError( - `strict (${options.strict}) and strictConformance (${options.strictConformance}) disagree; ` + - "pass only strictConformance", - ); - } - return options.strictConformance ?? options.strict; } /** Where an expression is evaluated. */ @@ -280,7 +257,7 @@ export class Model { requireLanguage(options.language); requireCapability(connection.info, CAPABILITY_INLINE_LANGUAGE, upgradeRemedy(CAPABILITY_INLINE_LANGUAGE)); } - const strictConformance = strictConformanceOf(options); + const { strictConformance } = options; if (strictConformance === true) { requireCapability( connection.info, diff --git a/client/node/test/sources.test.ts b/client/node/test/sources.test.ts index 5f9f507d28..79c035efba 100644 --- a/client/node/test/sources.test.ts +++ b/client/node/test/sources.test.ts @@ -6,7 +6,6 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { before, test } from "node:test"; import { - ParseError, SourceDocument, SymbolNotFoundError, connect, @@ -84,20 +83,6 @@ test("a broken document reports diagnostics naming it", async () => { assert.ok(model.diagnostics.every((diagnostic) => diagnostic.file === "broken")); }); -test("strict is a deprecated alias of strictConformance; disagreeing values refuse", async () => { - await using connection = await connect(); - await assert.rejects( - () => connection.parseSources([["top", TOP]], { strict: true, strictConformance: false }), - RangeError, - ); - await assert.rejects( - () => connection.loads(TOP, { strict: false, strictConformance: true }), - RangeError, - ); - const model = await connection.parseSources([["broken", BROKEN]], { strict: true }); - assert.throws(() => model.raiseForErrors(), ParseError); -}); - test("a document whose name another takes is refused before it is sent", async () => { await using connection = await connect(); await assert.rejects( diff --git a/client/opensysml/README.md b/client/opensysml/README.md index 868bab5cfb..0b0c4451db 100644 --- a/client/opensysml/README.md +++ b/client/opensysml/README.md @@ -316,10 +316,8 @@ a new or an existing declaration and require `authoring` and `authoring` and `comment_authoring`. The edited source is `result.Documents`, one `EditedDocument` per document the -batch reached, under the name the model was parsed with; `result.Content` is the -same notation for a model of one document and empty for a model of several, kept -for callers of the sole-document contract. Each `AppliedEdit` names the -`Document` its bytes belong to. +batch reached, under the name the model was parsed with — for a model of one +document, that one. Each `AppliedEdit` names the `Document` its bytes belong to. ## What a model is here @@ -359,16 +357,17 @@ another, and a name that is not one of the model's is `CodeInvalidArgument`. A rename or a cascade delete follows its references into the other documents, every document touched is re-parsed and re-analysed together, and `result.Documents` lists exactly the documents rewritten — so a batch that reaches one document of -three answers one `EditedDocument`, and `result.Content` is empty. The client marks -every request as accepting documents; the service refuses a request that does not on a -model of several, as it did before, so a program reading `Content` alone through an -earlier client is never handed an empty one. A reference from -a document the edit cannot rewrite, such as a bundled library file, refuses the -edit as `EditFailureReferencedElsewhere`, naming it in `Referrers`. All of this is the -`CapabilityEditDocuments` capability: a service without it edits a model of one -document alone, answering `Content` with `Documents` empty, refuses a model of several -with `CodeFailedPrecondition`, and refuses `ApplyDocumentEdits` with `CodeUnimplemented` -— so a program reading `Documents` checks `ServerInfo` for the capability first. +three answers one `EditedDocument`. The client marks every request as accepting +documents; the service refuses a request that does not on a model of several, as +it did before, so a program reading the sole-document `content` through an earlier +client is never handed an empty one. A reference from a document the edit cannot +rewrite, such as a bundled library file, refuses the edit as +`EditFailureReferencedElsewhere`, naming it in `Referrers`. All of this is the +`CapabilityEditDocuments` capability, which every service since 0.9.0 advertises: +`ApplyEdits` and `ApplyDocumentEdits` require it and are refused with +`CodeUnimplemented` by a service without it, whose answer carried the notation in +the sole-document `content` alone — the `EditResult.Content` field that read it, +deprecated in 0.9.0, was removed in 0.12.0. ## Concurrency, contexts and lifetime diff --git a/client/opensysml/authoring.go b/client/opensysml/authoring.go index 4a81b0b76d..b9b5b6bd4f 100644 --- a/client/opensysml/authoring.go +++ b/client/opensysml/authoring.go @@ -730,14 +730,6 @@ func (f EditFailure) String() string { // EditResult is a model's source with every edit applied. type EditResult struct { - // Content is the edited notation of a single-document model, byte-identical - // to the source outside the edited spans. Empty for a model of several - // documents, whose notation is in Documents alone. - // - // Deprecated: read Documents, which carries the same notation for a model of - // one document and every rewritten document of a model of several, from a - // service advertising the edit_documents capability. - Content string // Documents is the edited notation of every document the edits rewrote: the // edited document first when it is among them, then the others in name order. // A single-document model's one document is always listed; a document of @@ -790,25 +782,6 @@ func (c *client) ApplyEdits(ctx context.Context, model *Model, edits ...Edit) (* return c.ApplyDocumentEdits(ctx, model, "", edits...) } -// requireEditDocuments refuses to send a document name to a service without -// the edit_documents capability, which would ignore it and edit its sole document. -func (c *client) requireEditDocuments(ctx context.Context, document string) error { - if document == "" { - return nil - } - info, err := c.serverInfo(ctx) - if err != nil { - return err - } - if !info.Has(CapabilityEditDocuments) { - return &StatusError{ - Code: CodeUnimplemented, - Message: fmt.Sprintf("capability %q is unavailable", CapabilityEditDocuments), - } - } - return nil -} - func (c *client) requireCapabilities(ctx context.Context, capabilities ...string) error { info, err := c.serverInfo(ctx) if err != nil { @@ -830,15 +803,11 @@ func (c *client) ApplyDocumentEdits(ctx context.Context, model *Model, document if err != nil { return nil, err } - if err := c.requireEditDocuments(ctx, document); err != nil { + // The notation is read from Documents, which only a service with the + // edit_documents capability answers (one without it ignores a document name). + if err := c.requireCapabilities(ctx, append(requiredCapabilities(edits), CapabilityEditDocuments)...); err != nil { return nil, err } - if names := requiredCapabilities(edits); len(names) > 0 { - if err := c.requireCapabilities(ctx, names...); err != nil { - return nil, err - } - } - // This client reads Documents, so a model of several documents may be edited. req := &pb.ApplyEditsRequest{ModelHash: hash, Document: document, AcceptDocuments: true} for _, edit := range edits { operation, err := editToProto(edit) @@ -860,7 +829,7 @@ func (c *client) ApplyDocumentEdits(ctx context.Context, model *Model, document Referrers: referrersFromProto(resp.Referrers), } } - result := &EditResult{Content: resp.Content, Diagnostics: diagnostics} + result := &EditResult{Diagnostics: diagnostics} for _, doc := range resp.Documents { result.Documents = append(result.Documents, EditedDocument{Name: doc.Name, Content: doc.Content}) } diff --git a/client/opensysml/surface_test.go b/client/opensysml/surface_test.go index 97b5e2c71d..8d224c5caf 100644 --- a/client/opensysml/surface_test.go +++ b/client/opensysml/surface_test.go @@ -1160,8 +1160,8 @@ func TestApplyEditsAnswersTheEditedSource(t *testing.T) { if err != nil { t.Fatalf("ApplyEdits: %v", err) } - if !strings.Contains(result.Content, "1050.0") { - t.Errorf("edited source does not carry the new value:\n%s", result.Content) + if len(result.Documents) != 1 || !strings.Contains(result.Documents[0].Content, "1050.0") { + t.Errorf("edited source does not carry the new value:\n%+v", result.Documents) } if len(result.Applied) != 1 { t.Fatalf("applied = %d edits, want 1", len(result.Applied)) @@ -1186,8 +1186,8 @@ func TestApplyEditsListsTheOneDocumentUnderTheParsesName(t *testing.T) { if len(result.Documents) != 1 || result.Documents[0].Name != "demo.sysml" { t.Fatalf("documents = %+v, want the one document named demo.sysml", result.Documents) } - if result.Documents[0].Content != result.Content || result.Content == "" { - t.Errorf("content = %q, documents[0].content = %q, want the same notation", result.Content, result.Documents[0].Content) + if !strings.Contains(result.Documents[0].Content, "1050.0") { + t.Errorf("documents[0].content = %q, want the edited notation", result.Documents[0].Content) } if len(result.Applied) != 1 || result.Applied[0].Document != "demo.sysml" { t.Errorf("applied = %+v, want one edit in demo.sysml", result.Applied) @@ -1219,9 +1219,6 @@ func TestApplyEditsRenamesAcrossDocumentsAndLeavesContentEmpty(t *testing.T) { if err != nil { t.Fatalf("ApplyEdits: %v", err) } - if result.Content != "" { - t.Errorf("content = %q, want empty for a model of two documents", result.Content) - } if len(result.Documents) != 2 || result.Documents[0].Name != "lib.sysml" || result.Documents[1].Name != "car.sysml" { t.Fatalf("documents = %+v, want lib.sysml then car.sysml", result.Documents) } @@ -1247,9 +1244,6 @@ func TestApplyDocumentEditsTargetsTheNamedDocument(t *testing.T) { if len(result.Documents) != 1 || result.Documents[0].Name != "car.sysml" { t.Fatalf("documents = %+v, want car.sysml alone", result.Documents) } - if result.Content != "" { - t.Errorf("content = %q, want empty for a model of two documents", result.Content) - } _, err = client.ApplyDocumentEdits(context.Background(), model, "nope.sysml", opensysml.Rename{Target: "Car::wheel", NewName: "tyre"}) if !errors.Is(err, opensysml.CodeInvalidArgument) { @@ -1281,7 +1275,7 @@ func TestApplyEditsRefusalNamesReferrersByDocument(t *testing.T) { // A service without edit_documents edits one document alone and answers Content // alone, so a client checks the capability before reading Documents, before // editing a model of several documents, and before naming a document. -func TestApplyEditsWithoutEditDocumentsAnswersContentAlone(t *testing.T) { +func TestApplyEditsRequiresEditDocuments(t *testing.T) { svc, err := sysmlgrpc.NewServiceWithUnavailableCapabilitiesForTesting(16, "test", []string{opensysml.CapabilityEditDocuments}) if err != nil { t.Fatalf("NewServiceWithUnavailableCapabilitiesForTesting: %v", err) @@ -1302,23 +1296,14 @@ func TestApplyEditsWithoutEditDocumentsAnswersContentAlone(t *testing.T) { t.Fatalf("capabilities = %v, want apply_edits without edit_documents", info.Capabilities) } + // Such a service answers the sole-document `content` alone, which this + // client no longer reads, so the edit is refused before it is sent. one := parse(t, client, editableSource) - result, err := client.ApplyEdits(ctx, one, opensysml.SetValue{Target: "Demo::SC::unitMass", Value: "1050.0"}) - if err != nil { - t.Fatalf("ApplyEdits: %v", err) - } - if !strings.Contains(result.Content, "1050.0") || len(result.Documents) != 0 { - t.Errorf("content=%q documents=%+v, want the notation in Content alone", result.Content, result.Documents) - } - if len(result.Applied) != 1 || result.Applied[0].Document != "" { - t.Errorf("applied = %+v, want one edit naming no document", result.Applied) + _, err = client.ApplyEdits(ctx, one, opensysml.SetValue{Target: "Demo::SC::unitMass", Value: "1050.0"}) + if !errors.Is(err, opensysml.CodeUnimplemented) || !strings.Contains(err.Error(), opensysml.CapabilityEditDocuments) { + t.Errorf("a model of one: err = %v, want CodeUnimplemented naming edit_documents", err) } - two := parseTwo(t, client) - _, err = client.ApplyEdits(ctx, two, opensysml.Rename{Target: "Lib::Engine", NewName: "Motor"}) - if !errors.Is(err, opensysml.CodeFailedPrecondition) || !strings.Contains(err.Error(), opensysml.CapabilityEditDocuments) { - t.Errorf("a model of two: err = %v, want CodeFailedPrecondition naming edit_documents", err) - } _, err = client.ApplyDocumentEdits(ctx, two, "car.sysml", opensysml.Rename{Target: "Car::wheel", NewName: "tyre"}) if !errors.Is(err, opensysml.CodeUnimplemented) || !strings.Contains(err.Error(), opensysml.CapabilityEditDocuments) { t.Errorf("naming a document: err = %v, want CodeUnimplemented naming edit_documents", err) @@ -1348,11 +1333,15 @@ func TestApplyEditsRenamesAndRewritesTheReferences(t *testing.T) { if err != nil { t.Fatalf("ApplyEdits: %v", err) } - if strings.Contains(result.Content, ": SC") { - t.Errorf("the reference to the renamed element still reads SC:\n%s", result.Content) + if len(result.Documents) != 1 { + t.Fatalf("documents = %+v, want the one edited document", result.Documents) } - if !strings.Contains(result.Content, "part sc : Spacecraft") { - t.Errorf("the reference was not rewritten:\n%s", result.Content) + edited := result.Documents[0].Content + if strings.Contains(edited, ": SC") { + t.Errorf("the reference to the renamed element still reads SC:\n%s", edited) + } + if !strings.Contains(edited, "part sc : Spacecraft") { + t.Errorf("the reference was not rewritten:\n%s", edited) } } @@ -1373,6 +1362,15 @@ func TestApplyEditsRefusesDeletingAReferencedElement(t *testing.T) { } } +// editedNotation is the edited notation of a model of one document. +func editedNotation(t *testing.T, result *opensysml.EditResult) string { + t.Helper() + if len(result.Documents) != 1 { + t.Fatalf("documents = %+v, want the one edited document", result.Documents) + } + return result.Documents[0].Content +} + func TestApplyEditsAddsAMember(t *testing.T) { client := newClient(t) model := parse(t, client, editableSource) @@ -1381,8 +1379,8 @@ func TestApplyEditsAddsAMember(t *testing.T) { if err != nil { t.Fatalf("ApplyEdits: %v", err) } - if !strings.Contains(result.Content, "attribute margin") { - t.Errorf("the member was not added:\n%s", result.Content) + if edited := editedNotation(t, result); !strings.Contains(edited, "attribute margin") { + t.Errorf("the member was not added:\n%s", edited) } } @@ -1396,8 +1394,8 @@ func TestApplyEditsAddsAConnection(t *testing.T) { if err != nil { t.Fatalf("ApplyEdits: %v", err) } - if !strings.Contains(result.Content, "allocation alloc1 allocate a to b;") { - t.Errorf("the connection was not added:\n%s", result.Content) + if edited := editedNotation(t, result); !strings.Contains(edited, "allocation alloc1 allocate a to b;") { + t.Errorf("the connection was not added:\n%s", edited) } } @@ -1422,8 +1420,8 @@ func TestApplyEditsAddsATypedConnection(t *testing.T) { if err != nil { t.Fatalf("ApplyEdits: %v", err) } - if !strings.Contains(result.Content, "connection cable : Cable connect left.p to right.p;") { - t.Errorf("the typed connection was not added:\n%s", result.Content) + if edited := editedNotation(t, result); !strings.Contains(edited, "connection cable : Cable connect left.p to right.p;") { + t.Errorf("the typed connection was not added:\n%s", edited) } } diff --git a/client/python/opensysml/__init__.py b/client/python/opensysml/__init__.py index 848f9cb338..d47ad9bc3f 100644 --- a/client/python/opensysml/__init__.py +++ b/client/python/opensysml/__init__.py @@ -1,6 +1,5 @@ """opensysml - Python client library for OpenSysML SysML v2 parser.""" -import warnings from opensysml._version import VERSION as _declared_version from opensysml.connection import ( @@ -178,7 +177,6 @@ "UnsupportedOperationError", "UnsupportedValueError", "WrongKindError", "load", "loads", "parse_sources", "connect", "convert", "migrate", "read_json", - # "eval" is deprecated in favour of "evaluate", so it is not exported. "evaluate", "instantiate", "DEFAULT_PORT", "split_target", "__version__" @@ -551,24 +549,3 @@ def instantiate(symbol_id, file_path=None, model_hash=None, host='localhost', return conn.instantiate(symbol_id, model_hash) - -#: Names that shadowed a built-in, and what each is called now. Served through -#: __getattr__ so a star-import no longer binds over the built-in. -_RENAMED_NAMES = {"eval": "evaluate", "RuntimeError": "ExecutionError"} - - -def __getattr__(name): - """Serve a renamed name with the object it became, warning about its use. - - ``opensysml.eval`` is :func:`evaluate` and ``opensysml.RuntimeError`` is - :class:`~opensysml.errors.ExecutionError`, so existing snippets keep working. - """ - replacement = _RENAMED_NAMES.get(name) - if replacement is None: - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - warnings.warn( - f"opensysml.{name} is deprecated; use opensysml.{replacement} instead", - DeprecationWarning, - stacklevel=2, - ) - return globals()[replacement] diff --git a/client/python/opensysml/errors.py b/client/python/opensysml/errors.py index 3fed0a4f84..9533a820a0 100644 --- a/client/python/opensysml/errors.py +++ b/client/python/opensysml/errors.py @@ -8,7 +8,6 @@ """ import builtins -import warnings from contextlib import contextmanager from typing import TYPE_CHECKING @@ -642,29 +641,6 @@ def translate_rpc_errors(not_found=ModelNotFoundError, unimplemented=None): ) from exc -#: Names kept for callers of older versions, and what they are now. -_DEPRECATED_NAMES = {"RuntimeError": ExecutionError} - - -def __getattr__(name): - """Serve the deprecated ``RuntimeError`` alias, warning about its use. - - It is the same class as :class:`ExecutionError`, so ``except - opensysml.errors.RuntimeError`` keeps working; it is gone from ``__all__`` so - that a star-import no longer shadows the built-in with it. - """ - replacement = _DEPRECATED_NAMES.get(name) - if replacement is None: - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - warnings.warn( - f"opensysml.errors.{name} is deprecated; use " - f"opensysml.errors.{replacement.__name__} instead", - DeprecationWarning, - stacklevel=2, - ) - return replacement - - __all__ = [ "OpenSysMLError", "ChecksumMismatchError", diff --git a/client/python/tests/test_api.py b/client/python/tests/test_api.py index 1d1252d0c8..6af106e8a3 100644 --- a/client/python/tests/test_api.py +++ b/client/python/tests/test_api.py @@ -1,6 +1,5 @@ """Tests for opensysml module-level convenience API.""" -import warnings import pytest from unittest.mock import patch, MagicMock, Mock @@ -204,22 +203,6 @@ def test_opensysml_evaluate_with_file(self): "6 * 7", "model-abc", context_symbol_id=None, subject_symbol_id=None ) - @pytest.mark.usefixtures("no_default_connection") - def test_opensysml_eval_still_works_and_warns(self): - """The deprecated name evaluates the same way, warning about itself.""" - with patch('opensysml.Connection') as MockConnection: - mock_conn = Mock() - MockConnection.return_value = mock_conn - mock_conn.eval.return_value = 42 - - with pytest.warns(DeprecationWarning, match="opensysml.evaluate"): - result = opensysml.eval("6 * 7", model_hash="model-abc") - - assert result == 42 - # The name to write is exported; the deprecated one is not. - assert "evaluate" in opensysml.__all__ - assert "eval" not in opensysml.__all__ - @pytest.mark.usefixtures("no_default_connection") def test_opensysml_evaluate_takes_the_address_positionally(self): """subject comes last, so a positional call still binds host and port.""" @@ -335,34 +318,7 @@ def test_opensysml_instantiate_with_both_params(self): class TestRenamedNames: - """The two names that shadowed a built-in, and the aliases left behind.""" - - def test_the_old_names_resolve_to_the_new_objects(self): - """`opensysml.eval` is `evaluate` and `opensysml.RuntimeError` is `ExecutionError`.""" - for old, new in (("eval", opensysml.evaluate), ("RuntimeError", opensysml.ExecutionError)): - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("always") - alias = getattr(opensysml, old) - assert alias is new - assert len(caught) == 1 - assert issubclass(caught[0].category, DeprecationWarning) - assert new.__name__ in str(caught[0].message) - - @pytest.mark.usefixtures("no_default_connection") - def test_an_old_snippet_still_runs_through_the_alias(self): - """A script written against `opensysml.eval` keeps working, warning included.""" - with patch('opensysml.Connection') as MockConnection: - mock_conn = Mock() - MockConnection.return_value = mock_conn - mock_conn.eval.return_value = 4 - - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - assert opensysml.eval("2 + 2", model_hash="h") == 4 - - mock_conn.eval.assert_called_once_with( - "2 + 2", "h", context_symbol_id=None, subject_symbol_id=None - ) + """The two names that shadowed a built-in are gone, not aliased.""" def test_the_old_names_no_longer_shadow_a_builtin_on_star_import(self): """Which is the point of the rename: `import *` binds neither name.""" diff --git a/client/python/tests/test_errors.py b/client/python/tests/test_errors.py index 7929af8974..5f11a57e85 100644 --- a/client/python/tests/test_errors.py +++ b/client/python/tests/test_errors.py @@ -1,7 +1,6 @@ """Tests for translating gRPC failures into the opensysml exception hierarchy.""" import builtins -import warnings import grpc import pytest @@ -176,15 +175,6 @@ def test_it_carries_the_diagnostics_behind_the_failure(self): assert exc.message == "evaluation failed" assert exc.diagnostics == ["d"] - def test_the_old_name_is_a_deprecated_alias(self): - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("always") - alias = errors.RuntimeError - assert alias is ExecutionError - assert len(caught) == 1 - assert issubclass(caught[0].category, DeprecationWarning) - assert "ExecutionError" in str(caught[0].message) - def test_the_old_name_is_gone_from_the_star_import_surface(self): # Which is the point: `from opensysml.errors import *` no longer shadows # the built-in RuntimeError. diff --git a/cmd/sysml/convert.go b/cmd/sysml/convert.go index b76d4c7ab3..644a84afbf 100644 --- a/cmd/sysml/convert.go +++ b/cmd/sysml/convert.go @@ -16,14 +16,6 @@ import ( "github.com/Open-MBEE/OpenSysML/internal/workspace/project" ) -// deprecatedFlag rejects a flag that has been replaced, so the old spelling -// reports what to write instead of "flag provided but not defined". -type deprecatedFlag struct{ instead string } - -func (f *deprecatedFlag) String() string { return "" } - -func (f *deprecatedFlag) Set(string) error { return errors.New(f.instead) } - // runConvert converts the model named on the command line to the format // -convert asks for, writing to -o or to stdout. // diff --git a/cmd/sysml/convert_test.go b/cmd/sysml/convert_test.go index 84ffd20dd4..25f05b67f5 100644 --- a/cmd/sysml/convert_test.go +++ b/cmd/sysml/convert_test.go @@ -265,7 +265,6 @@ func TestConvertErrors(t *testing.T) { "file as format": {[]string{"-convert", model}, "-convert names the format"}, "several to notation": {[]string{model, model, "-convert", "sysml"}, "converts to ttl or api-json"}, "several to a branch": {[]string{model, model, "-convert", "ttl", "-o", "flexo://p/main"}, "is written to a file"}, - "replaced -to flag": {[]string{model, "-convert", "ttl", "-to", "sysml"}, "-to has been replaced by -convert"}, "forgotten value": {[]string{model, "-convert", "ttl", "-o"}, "flag needs an argument: -o"}, "syntax error": {[]string{broken, "-convert", "ttl"}, "syntax error"}, "unsupported rdf": {[]string{badTurtle, "-convert", "sysml"}, "blank node"}, diff --git a/cmd/sysml/features.go b/cmd/sysml/features.go index 0b7335bc89..f4efbaf596 100644 --- a/cmd/sysml/features.go +++ b/cmd/sysml/features.go @@ -74,8 +74,7 @@ var ( name: "docpdf", flags: append(flags("pdf-engine", "html-theme", "html-css", "html-mermaid", "html-math"), boolFlags("doc-title-page", "doc-toc", "doc-number-sections", - "html-no-default-css", "html-fragment", "html-default-css", - "pdf-title-page", "pdf-toc", "pdf-number-sections")...), + "html-no-default-css", "html-fragment", "html-default-css")...), values: map[string][]string{"doc-form": {"html", "pdf"}}, markers: []string{"-doc-form html", "-doc-form pdf"}, } diff --git a/cmd/sysml/mcp_catalog_test.go b/cmd/sysml/mcp_catalog_test.go index ab0cc49813..1471ab8b51 100644 --- a/cmd/sysml/mcp_catalog_test.go +++ b/cmd/sysml/mcp_catalog_test.go @@ -9,9 +9,9 @@ import ( // Drift gate: every sysml flag that is an operation or one of its arguments // is classified exactly once in the sysml-mcp tool catalog, as the tool that -// performs it, as unserved by the service, or as CLI-only. The General and -// Deprecated option groups are the process's own and are not operations; -// an alias is classified under its long spelling. +// performs it, as unserved by the service, or as CLI-only. The General option +// group is the process's own and is not an operation; an alias is classified +// under its long spelling. func TestEverySysmlFlagIsClassifiedForMCP(t *testing.T) { process := map[string]bool{} for _, group := range doc().Options { @@ -20,13 +20,13 @@ func TestEverySysmlFlagIsClassifiedForMCP(t *testing.T) { for _, alias := range opt.Aliases { process[alias] = true } - if group.Title == "General" || group.Title == "Deprecated" { + if group.Title == "General" { process[opt.Name] = true } } } if len(process) == 0 { - t.Fatal("the General and Deprecated option groups were not found") + t.Fatal("the General option group was not found") } var classified int docFlags().VisitAll(func(f *flag.Flag) { diff --git a/cmd/sysml/render_document_pdf_test.go b/cmd/sysml/render_document_pdf_test.go index 6acba7d4e5..783358b304 100644 --- a/cmd/sysml/render_document_pdf_test.go +++ b/cmd/sysml/render_document_pdf_test.go @@ -44,7 +44,7 @@ printf '' > "$out" out := filepath.Join(dir, "report.pdf") cmd := exec.Command(binary, fixture, "-render-document", "Observatory::MassReport", - "-doc-form", "pdf", "-pdf-title-page", "-pdf-toc", "-pdf-number-sections", "-o", out) + "-doc-form", "pdf", "-doc-title-page", "-doc-toc", "-doc-number-sections", "-o", out) cmd.Env = append(os.Environ(), docpdf.WeasyPrintEnv+"="+weasyprint, docpdf.MermaidEnv+"="+mmdc) if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("render: %v\n%s", err, output) @@ -393,7 +393,7 @@ func TestRenderDocumentPDFFlagConflicts(t *testing.T) { 2, "name the file to write with -o") wantReport(t, check(t, binary, documentModel, "-render-document", "Reports::MassReport", "-doc-form", "latex"), 2, "unknown document form", "markdown, asciidoc, html or pdf") - wantReport(t, check(t, binary, documentModel, "-render-document", "Reports::MassReport", "-pdf-toc"), + wantReport(t, check(t, binary, documentModel, "-render-document", "Reports::MassReport", "-doc-toc"), 2, "-doc-form pdf") wantReport(t, check(t, binary, documentModel, "-render-document", "Reports::MassReport", "-doc-form", "pdf", "-o", "x.pdf", "-pdf-engine", "latex"), 2, "unknown PDF engine", "weasyprint, pandoc, prince") diff --git a/cmd/sysml/render_pdf.go b/cmd/sysml/render_pdf.go index bf6e3e294f..585da758af 100644 --- a/cmd/sysml/render_pdf.go +++ b/cmd/sysml/render_pdf.go @@ -24,9 +24,6 @@ func init() { fs.BoolVar(&htmlShowCSS, "html-default-css", false, "Write the default document stylesheet, or with -html-theme that theme's whole sheet, and exit") fs.StringVar(&htmlMermaid, "html-mermaid", "", "Have the page load Mermaid to draw its diagrams: cdn loads a pinned release from jsDelivr, a URL the script it names") fs.StringVar(&htmlMath, "html-math", "", "Have the page load MathJax to typeset its formulas: cdn loads a pinned release from jsDelivr, a URL the script it names") - fs.BoolVar(&pdfTitlePage, "pdf-title-page", false, "Former name of -doc-title-page, which also shapes HTML") - fs.BoolVar(&pdfTOC, "pdf-toc", false, "Former name of -doc-toc, which also shapes HTML") - fs.BoolVar(&pdfNumbering, "pdf-number-sections", false, "Former name of -doc-number-sections, which also shapes HTML") }) } diff --git a/cmd/sysml/usage.go b/cmd/sysml/usage.go index eed94ab8d8..795835dac8 100644 --- a/cmd/sysml/usage.go +++ b/cmd/sysml/usage.go @@ -770,8 +770,6 @@ func registerFlags(fs *flag.FlagSet) { fs.Var(&renderRuns, "render-run", "Render the trace of a run as timeline or sequence into path (`-` is stdout); form comes from -render-form or the path extension") fs.BoolVar(&memStats, "memstats", false, "Report on stderr what the run cost: wall time, memory allocated, memory taken from the OS") - fs.Var(&deprecatedFlag{instead: "-to has been replaced by -convert, as `sysml model.sysml -convert ttl`"}, "to", "Replaced by -convert, which names the output format") - declareFeatureFlags(fs) } @@ -962,14 +960,6 @@ func optionGroups() []usage.OptionGroup { usage.Opt("memprofile", fileArg), usage.Opt("memstats", ""), }, - }, { - Title: "Deprecated", - Options: []usage.Option{ - usage.Opt("to", formatArg), - usage.Opt("pdf-title-page", ""), - usage.Opt("pdf-toc", ""), - usage.Opt("pdf-number-sections", ""), - }, }} } diff --git a/docs/clients/python/errors.md b/docs/clients/python/errors.md index 82b513d7ae..1a7416d192 100644 --- a/docs/clients/python/errors.md +++ b/docs/clients/python/errors.md @@ -42,15 +42,15 @@ or a condition that could not be evaluated is represented separately; see ## Names that shadowed builtins -Neither historical builtin-like name is part of the public API any more: -the module-level evaluation function is `opensysml.evaluate`, and the -execution exception is `opensysml.ExecutionError`. Deprecated aliases remain -available and warn when accessed, but they are excluded from `__all__`. +Neither historical builtin-like name is part of the public API: the +module-level evaluation function is `opensysml.evaluate`, and the execution +exception is `opensysml.ExecutionError`. The deprecated aliases that warned +and answered the new object were removed in 0.12.0. -| Deprecated name | Use instead | +| Removed name | Use instead | | --- | --- | | `opensysml.eval` | `opensysml.evaluate` | -| `opensysml.errors.RuntimeError` | `opensysml.ExecutionError` | +| `opensysml.RuntimeError`, `opensysml.errors.RuntimeError` | `opensysml.ExecutionError` | Import the package rather than star-importing names that might shadow builtins. The `Model.eval` and `Connection.eval` methods keep their name; object diff --git a/docs/guide/07-saving-and-rdf.md b/docs/guide/07-saving-and-rdf.md index d1d9658064..affccbca76 100644 --- a/docs/guide/07-saving-and-rdf.md +++ b/docs/guide/07-saving-and-rdf.md @@ -135,7 +135,7 @@ outside the mapping. It never writes a partial model. The same conversion is available as the service method `Convert`, which `GetServerInfo` reports as the `convert` capability. It accepts either a `file_path` that the service opens or `content` -passed inline, takes the same format names as `-from` and `-to`, and returns the written text +passed inline, takes the same format names as `-from` and `-convert`, and returns the written text with its formats, or an `error` together with the diagnostics that explain it. `tolerate_syntax_errors` writes notation despite syntax errors; it is rejected for any direction that builds a graph, where an unparsed declaration would be silently dropped. diff --git a/docs/internals/design/api-surface-parity.md b/docs/internals/design/api-surface-parity.md index a8ccc08a39..b90f305c1f 100644 --- a/docs/internals/design/api-surface-parity.md +++ b/docs/internals/design/api-surface-parity.md @@ -63,7 +63,7 @@ surface reimplements the assembly around the core call. | Engine listing and selection | `%engines`, `%engine`, `-engines`, `-engine` | — | `ListEngines`, the `engine` field | `ListEngines`, the option | yes — `engines.Default` | | Model query | `%query`, `-query` (OSLC); `%run-query`, `-run-query` (document query) | — | `Query`, `RunDocumentQuery` | `Query`, `QueryOSLC`, `RunDocumentQuery` | yes — `query`, `queryplan`, `queryexec` | | Document rendering | `%render-document`, `-render-document(s)`, `-doc-form` | `opensysml/renderDocument` | `RenderDocument` | `RenderDocument` | yes — `docir`, `docplan`, `docrender` | -| Convert (notation, RDF, element JSON) | `%save` (`.ttl`), `-convert`, `-from`, `-to` | — | `Convert` | `Convert`, `ConvertFile`, `ConvertSource` | yes — `export` | +| Convert (notation, RDF, element JSON) | `%save` (`.ttl`), `-convert`, `-from` | — | `Convert` | `Convert`, `ConvertFile`, `ConvertSource` | yes — `export` | | Model edits | — | `opensysml/applyModelEdit`, rename, code actions | `ApplyEdits` | `ApplyEdits` | yes — `edit`; the editor's rename and quick fixes are their own assembly over it | | Strict conformance | `%strict`, `-strict` | editor setting | `strict_conformance` request field | option | yes — `conformance` | | Server / build identity | `-version` | `initialize` result | `GetServerInfo` | `ServerInfo` | yes | diff --git a/docs/manual/outputs.md b/docs/manual/outputs.md index 1135db7099..8f9c8a981d 100644 --- a/docs/manual/outputs.md +++ b/docs/manual/outputs.md @@ -412,8 +412,7 @@ own. | `-doc-toc` | A table of contents built from the section headings | | `-doc-number-sections` | Hierarchical section numbers (1, 1.1, ...) | -All three are off by default and shape HTML and PDF alike; `-pdf-title-page`, -`-pdf-toc` and `-pdf-number-sections` are aliases of them. +All three are off by default and shape HTML and PDF alike. ### Wide tables diff --git a/docs/project/demo.md b/docs/project/demo.md index 6588af16aa..9110da9d98 100644 --- a/docs/project/demo.md +++ b/docs/project/demo.md @@ -524,7 +524,7 @@ And back — this direction is a real print of the parsed tree, so it is the can model that was loaded: ```console -$ sysml -convert structure.ttl -to sysml +$ sysml structure.ttl -convert sysml package Structure { private import ScalarValues::*; part def Wheel { @@ -587,7 +587,7 @@ echoes it, `%clear` resets it, and `%verbosity quiet|normal|debug` sets how much namespace they came from drops them, with a note naming the submission that ended them. - **`clear` is not a REPL command.** At the `sysml> ` prompt it is parsed as SysML and leaves an unresolved session error that is then reported under later commands. `%clear` is the command. -- **`-convert … -to ttl` covers structure and the behavior a body states.** A model with a +- **`-convert ttl` covers structure and the behavior a body states.** A model with a construct the notation could not be rebuilt from — a name declared twice in one body, a `perform` with neither a name nor an action to refer to — is rejected (`cannot convert the … at …`) rather than silently exported, so use a model that converts for the RDF demo, as in §10. diff --git a/docs/project/html-document-backend.md b/docs/project/html-document-backend.md index 4b0eaa6355..1e2e04a9c2 100644 --- a/docs/project/html-document-backend.md +++ b/docs/project/html-document-backend.md @@ -363,10 +363,11 @@ fallback, not an error. PDF form accepts too since its converters read the same HTML. - **The shared deliverable options.** A title page, a contents list and section numbering are not PDF-specific — they are exactly what an HTML deliverable wants too — but they are - spelled `-pdf-title-page`, `-pdf-toc` and `-pdf-number-sections` today. The proposal is to + were spelled `-pdf-title-page`, `-pdf-toc` and `-pdf-number-sections`. The proposal was to accept `-doc-title-page`, `-doc-toc` and `-doc-number-sections` for both forms and keep the - `-pdf-*` spellings working as documented aliases, so no existing script breaks. **Open - decision:** whether to keep the aliases indefinitely or deprecate them for 1.0. + `-pdf-*` spellings working as documented aliases, so no existing script breaks. **Decided:** + the aliases were listed as deprecated from 0.9.0 and removed in 0.12.0; only the `-doc-*` + spellings remain. - **REPL and service.** `%render-document` keeps printing Markdown; a terminal has no use for markup. The gRPC `RenderDocument` request grows a form field defaulting to Markdown, so the Python client can ask for HTML, and the LSP's document preview keeps its Markdown, which is diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 59ff924833..85cb98c8b7 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -3219,7 +3219,7 @@ the `@type` mapping and the comparison choices. client/python/tests/test_lifecycle.py, test_stale_service.py) - an `instance_id` outside an `Instantiate` response (an `Evaluate` result, say) is still a bare int64: those responses carry no instance graph to resolve it -- `opensysml.eval` and `opensysml.RuntimeError` are deprecated aliases of `evaluate` and `ExecutionError`, served by a module `__getattr__` with a warning +- `opensysml.eval` and `opensysml.RuntimeError`, the deprecated aliases of `evaluate` and `ExecutionError`, were removed in 0.12.0; neither name is an attribute of the package - a downloaded binary is verified against the digest the shipped `release-digests.json` (`binary.py:PINNED_SHA256`) pins for its release, independent of the origin that served it; a version with no pin fails rather than diff --git a/docs/reference/api.md b/docs/reference/api.md index fda1509f2d..4ff39ee49b 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -112,10 +112,9 @@ fmt.Println(exploration.Status()) // complete (6 runs) ``` `ApplyEdits` rewrites a model's source and answers an `EditResult`: `Documents` is the edited -notation of every document the batch reached, each under the name the parse gave it, and -`Content` is the same notation for a model of exactly one document — the field the sole-document -contract answered first, kept so a caller written against it is unchanged; it is empty for a model -of several, even when the batch rewrote only one of them. The operations name elements declared +notation of every document the batch reached, each under the name the parse gave it — for a +model of one document, that one. (`EditResult.Content`, the sole-document notation the contract +answered first, was deprecated in 0.9.0 and removed in 0.12.0.) The operations name elements declared in the model's first document; `ApplyDocumentEdits` names another. A rename or cascade delete follows its references into the model's other documents, every touched document is re-parsed and re-analysed together, and either all of them are answered or the refusal — an `*EditError` @@ -124,8 +123,8 @@ request's `accept_documents`, which is what lets a model of several documents be request without it, as every earlier client sends, is refused on such a model with `FAILED_PRECONDITION` as before, so a caller reading `content` alone is never handed an empty one. A service advertising `apply_edits` without `edit_documents` (`CapabilityEditDocuments`) predates -`Documents`: it edits a model of one document and answers `Content` alone, so a caller checks the -capability before reading `Documents`, `Referrers` or an applied edit's `Document`. +`Documents` and answers `content` alone, which this client no longer reads: `ApplyEdits` and +`ApplyDocumentEdits` require the capability and are refused with `CodeUnimplemented` without it. `add_member` accepts the existing `specializes`, type, multiplicity and value fields, plus `body_expression`, `is_abstract`, `redefines`, `is_default` and `direction`. `body_expression` diff --git a/docs/reference/cli.md b/docs/reference/cli.md index f4c789719f..275c74617c 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -292,9 +292,6 @@ the same member-path parser as `Project` and `OrderBy`. | `-html-mermaid ` | | Have the HTML page load Mermaid to draw its diagrams: `cdn` loads a pinned release from jsDelivr, a URL loads the script it names (default: diagrams stay Mermaid source) | | `-html-math ` | | Have the HTML page load MathJax to typeset its formulas: `cdn` loads a pinned release from jsDelivr, a URL loads the script it names (default: formulas stay LaTeX source) | | `-pdf-engine ` | | Converter `-doc-form pdf` drives: `weasyprint` (default), `pandoc` or `prince` | -| `-pdf-title-page` | | Former name of `-doc-title-page` | -| `-pdf-toc` | | Former name of `-doc-toc` | -| `-pdf-number-sections` | | Former name of `-doc-number-sections` | | `-output ` | `-o` | Write the conversion, the rendering, the rendered document or a `-validate` report in a `-format` other than text to a file instead of stdout, or name what `-bundle` and `-unbundle` write; with `-convert ttl` a branch URL (the same two forms) replaces the branch's model graph with the converted Turtle — see [Reading and pushing a repository branch](#reading-and-pushing-a-repository-branch) | | `-version` | `-v` | Show version information | | `-help` | `-h` | Show usage information | @@ -367,7 +364,6 @@ Other modes, each described in full by `sysml -help` and the manual page: | `-sync-apply ` | Applies the change set to the model's project branch at the SysML v2 API endpoint, refusing one the dry run would have flagged, then records the commit in the sync state (`-sync-state `, default `.sync.json` beside the model). The token comes from `FLEXO_INTEROP_TOKEN` | | `-sync-state ` | The sync state file a live sync or a `-convert` over a branch URL reads and records the last-seen commit in; the defaults are `.sync.json` on `-sync-apply` and a push, and `.sync.json` on a branch read written to `-o`. A stdout branch read records nothing unless `-sync-state` names a file | | `-memstats`, `-cpuprofile `, `-memprofile ` | Report on stderr what the run cost — wall time, memory allocated, memory taken from the OS — or write a CPU or heap profile for `go tool pprof` | -| `-to ` | Replaced by `-convert`, which names the output format | **Arguments:** - `[file...]` - SysML files to load (loaded in order) diff --git a/docs/reference/node-api.md b/docs/reference/node-api.md index 961b83128f..4c5d9de983 100644 --- a/docs/reference/node-api.md +++ b/docs/reference/node-api.md @@ -93,9 +93,8 @@ directly, reporting a miss with no suggestions. `ParseOptions` are `language` (`"sysml"` or `"kerml"`, for inline content) and `strictConformance`; both are capability-gated, and the client checks before it -calls. `strict` remains as a deprecated alias of `strictConformance`; passing -both with different values is refused. To raise on parse errors, call -`model.raiseForErrors()` on the returned model. +calls. The `strict` alias of `strictConformance` was removed in 0.12.0. To raise +on parse errors, call `model.raiseForErrors()` on the returned model. ## Values are discriminated unions diff --git a/internal/frontend/usage/usage.go b/internal/frontend/usage/usage.go index fa18269b8e..7bed66853d 100644 --- a/internal/frontend/usage/usage.go +++ b/internal/frontend/usage/usage.go @@ -35,7 +35,7 @@ type Doc struct { // OptionGroup is one titled block of the option list. type OptionGroup struct { - // Title heads the block: "Checking a model", "Deprecated". + // Title heads the block: "Checking a model", "General". Title string Options []Option } diff --git a/packaging/man/man1/sysml.1 b/packaging/man/man1/sysml.1 index 62fd6bb96f..5e0d26579a 100644 --- a/packaging/man/man1/sysml.1 +++ b/packaging/man/man1/sysml.1 @@ -596,19 +596,6 @@ Write a heap profile of the run to this file, for go tool pprof .B \-memstats Report on stderr what the run cost: wall time, memory allocated, memory taken from the OS -.SS Deprecated -.TP -.BR \-to " \fIformat\fP" -Replaced by \-convert, which names the output format -.TP -.B \-pdf\-title\-page -Former name of \-doc\-title\-page, which also shapes HTML -.TP -.B \-pdf\-toc -Former name of \-doc\-toc, which also shapes HTML -.TP -.B \-pdf\-number\-sections -Former name of \-doc\-number\-sections, which also shapes HTML .SH EXAMPLES .RS 2 .nf diff --git a/tools/cmd/conformance/pkgclient.go b/tools/cmd/conformance/pkgclient.go index ca23279d60..d8dec6e48e 100644 --- a/tools/cmd/conformance/pkgclient.go +++ b/tools/cmd/conformance/pkgclient.go @@ -1447,10 +1447,11 @@ func (c *pkgClient) applyEdits(ctx context.Context, request protoreflect.Message if err != nil { return nil, apiError(err) } - response := &pb.ApplyEditsResponse{ - //lint:ignore SA1019 the scenarios pin the deprecated field's compatibility - Content: result.Content, - Diagnostics: diagnosticsToProto(result.Diagnostics), + response := &pb.ApplyEditsResponse{Diagnostics: diagnosticsToProto(result.Diagnostics)} + // The wire fills the sole-document content for a model of one document, + // which the public API carries in Documents alone. + if len(model.Roots) == 1 && len(result.Documents) == 1 { + response.Content = result.Documents[0].Content } for _, document := range result.Documents { response.Documents = append(response.Documents, &pb.EditedDocument{ From f97ec4f897e16673131c900087c99485ec7b55b8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:03:13 +0000 Subject: [PATCH 2/5] docs(project): fix the sentence recording the -pdf-* alias decision Co-Authored-By: jason.han --- docs/project/html-document-backend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/project/html-document-backend.md b/docs/project/html-document-backend.md index 1e2e04a9c2..8bfb4b9dc7 100644 --- a/docs/project/html-document-backend.md +++ b/docs/project/html-document-backend.md @@ -362,7 +362,7 @@ fallback, not an error. for the other forms the way the `-pdf-*` flags already are, except `-html-css`, which the PDF form accepts too since its converters read the same HTML. - **The shared deliverable options.** A title page, a contents list and section numbering are - not PDF-specific — they are exactly what an HTML deliverable wants too — but they are + not PDF-specific — they are exactly what an HTML deliverable wants too — but they were spelled `-pdf-title-page`, `-pdf-toc` and `-pdf-number-sections`. The proposal was to accept `-doc-title-page`, `-doc-toc` and `-doc-number-sections` for both forms and keep the `-pdf-*` spellings working as documented aliases, so no existing script breaks. **Decided:** From a0b55f1aaa3f967e406c402bfbf750418d5d56fc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:09:36 +0000 Subject: [PATCH 3/5] docs(python): the ExecutionError docstring no longer calls the removed alias current Co-Authored-By: jason.han --- client/python/opensysml/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/opensysml/errors.py b/client/python/opensysml/errors.py index 9533a820a0..1cb8053148 100644 --- a/client/python/opensysml/errors.py +++ b/client/python/opensysml/errors.py @@ -236,7 +236,7 @@ class ExecutionError(OpenSysMLError, builtins.RuntimeError): Also a built-in :class:`RuntimeError`, so ``except RuntimeError`` catches it — which is what the traceback's ``opensysml.errors.RuntimeError`` used to - promise and not deliver. That old name is a deprecated alias of this class. + promise and not deliver; that old name was an alias of this class until 0.12.0. Attributes: message (str): Error description From 520684685b02bb4c59c51cc073a7a5f4f4624b28 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:22:09 +0000 Subject: [PATCH 4/5] fix(node): the conformance runner forwards strict_conformance as strictConformance The runner still mapped the wire field to the removed strict option, so a strict ParseFile scenario was sent without strict conformance. Two parse scenarios now tell the modes apart: an OpenSysML-only notation is one error under strict conformance and one warning by default, so a runner that drops the field fails the suite. Co-Authored-By: jason.han --- client/node/conformance/runner.ts | 2 +- .../fixtures/nonstandard_notation.sysml | 10 ++++ conformance/scenarios/02-parse.json | 54 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 conformance/fixtures/nonstandard_notation.sysml diff --git a/client/node/conformance/runner.ts b/client/node/conformance/runner.ts index f51bee3bac..bf2579e675 100644 --- a/client/node/conformance/runner.ts +++ b/client/node/conformance/runner.ts @@ -513,7 +513,7 @@ export class Runner { private async parseFile(request: Record): Promise { const options = { ...stringOption(request, "language", "language"), - ...flagOption(request, "strict_conformance", "strict"), + ...flagOption(request, "strict_conformance", "strictConformance"), }; const content = request["content"]; if (typeof content === "string") { diff --git a/conformance/fixtures/nonstandard_notation.sysml b/conformance/fixtures/nonstandard_notation.sysml new file mode 100644 index 0000000000..d28e1cce24 --- /dev/null +++ b/conformance/fixtures/nonstandard_notation.sysml @@ -0,0 +1,10 @@ +package P { + state def S { + entry; then a; + state a; + state b; + choice c; + transition a_c first a then c; + transition c_b first c then b; + } +} diff --git a/conformance/scenarios/02-parse.json b/conformance/scenarios/02-parse.json index 1bbd85582a..2e58b3ac48 100644 --- a/conformance/scenarios/02-parse.json +++ b/conformance/scenarios/02-parse.json @@ -88,6 +88,60 @@ "status_message_contains": "strict_conformance" } }, + { + "id": "parse/strict_conformance_refuses_an_extension", + "description": "Under strict conformance an OpenSysML-only notation is an error, where the default parse reports it as a warning; a runner that drops the field answers the warning and fails here.", + "rpc": "ParseFile", + "requires_capabilities": [ + "strict_conformance" + ], + "request": { + "content": "${fixture:nonstandard_notation.sysml}", + "strict_conformance": true + }, + "expect": { + "non_empty": [ + "model_hash" + ], + "counts": { + "diagnostics": 1 + }, + "response": { + "diagnostics": [ + { + "severity": "error" + } + ] + } + }, + "expect_without_capability": { + "status": "UNIMPLEMENTED", + "status_message_contains": "strict_conformance" + } + }, + { + "id": "parse/an_extension_is_a_warning_by_default", + "description": "The same OpenSysML-only notation parses with one warning when strict conformance is not asked for.", + "rpc": "ParseFile", + "request": { + "content": "${fixture:nonstandard_notation.sysml}" + }, + "expect": { + "non_empty": [ + "model_hash" + ], + "counts": { + "diagnostics": 1 + }, + "response": { + "diagnostics": [ + { + "severity": "warning" + } + ] + } + } + }, { "id": "parse_sources/several_documents_are_one_model", "description": "Two documents parse as one model, so the import between them resolves and each document answers with its own root.", From 4facc415c43985a3f9460f256a30c300d74f72d5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:23:39 +0000 Subject: [PATCH 5/5] docs(go): the edit contract no longer describes the removed Content fallback Co-Authored-By: jason.han --- client/opensysml/authoring.go | 3 +-- client/opensysml/client.go | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/client/opensysml/authoring.go b/client/opensysml/authoring.go index b9b5b6bd4f..b735f88d8d 100644 --- a/client/opensysml/authoring.go +++ b/client/opensysml/authoring.go @@ -733,8 +733,7 @@ type EditResult struct { // Documents is the edited notation of every document the edits rewrote: the // edited document first when it is among them, then the others in name order. // A single-document model's one document is always listed; a document of - // several the edits left as parsed is not. Empty, with Content filled, from a - // service without the edit_documents capability, which edits one document alone. + // several the edits left as parsed is not. Documents []EditedDocument // Applied says what each edit changed, grouped by document in the order // Documents lists them and in request order within a document. diff --git a/client/opensysml/client.go b/client/opensysml/client.go index a4b996adfa..7c91ff6896 100644 --- a/client/opensysml/client.go +++ b/client/opensysml/client.go @@ -232,9 +232,8 @@ type Client interface { // them all with an EditError. The edits target the model's first document — // its only one when parsed from a file or source — and a rename or cascade // delete follows references into every other document of the model, whose - // rewritten notation the result lists. Requires the apply_edits capability; - // a model of several documents, and a result listing Documents, the - // edit_documents capability too. + // rewritten notation the result lists. Requires the apply_edits and + // edit_documents capabilities. ApplyEdits(ctx context.Context, model *Model, edits ...Edit) (*EditResult, error) // ApplyDocumentEdits is ApplyEdits with the edits targeting the document