feat(migrate): translate opaque JavaScript/English bodies, guards, swimlane names and clock reads to executable v2 - #382
Open
devin-ai-integration[bot] wants to merge 40 commits into
Conversation
…ames, read the clock Opaque JavaScript and English bodies are translated to v2 expressions by a bounded translator with typed refusals; names resolve against the partition's represented element before the activity and its owning block; the configured clock variable reads the local clock, which the runtime evaluates; profile stereotypes serialized under a schema namespace ending in .xmi are no longer skipped as XMI bookkeeping. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…behavior-migration
…formance, not behaviors of the occurrence A performance occurrence materialized for a typed action step ran the def's own perform members as classifier behaviors of the occurrence, in the wrong frame and before the step itself performed them. Typed constraint bodies and precedence-aware spelling of translated opaque expressions come along, with conformance fixtures for clock reads through localClock.currentTime. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…d error Co-Authored-By: jason.han <hanhuijun@gmail.com>
…document the opaque subset Co-Authored-By: jason.han <hanhuijun@gmail.com>
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…behavior-migration
…xt, bare clock assignment - a JavaScript const declared in a body is readable but a later assignment to it is refused; declared locals are typed ScalarValues::T so they resolve - a write to an in parameter is refused whether the direction is written or omitted (UML's default), through parameterDirection - a guard or default is one expression: text after its one optional ';' is refused instead of silently dropped - the runtime refuses a clock's own bare 'assign currentTime := ...' with ErrClockNotAssignable, as it already did through 'localClock.currentTime' - a one-event DurationObservation documents the UML semantics it applies (the node's own execution) and the acquisition fixture covers it Co-Authored-By: jason.han <hanhuijun@gmail.com>
…sional swimlanes, let features shadow the clock name A declaration of a name the body already has — a prior local, a member every action inherits, or a pin, parameter or property visible where the body lands — is refused instead of writing a second member. Pins are recorded as declared so bodies read them by their written name and cannot assign an input pin. A node held by partitions that do not nest resolves through the one naming an object, or through either when both name the same object; partitions naming different objects are a clash the node's and the partitions' report lines state, and names fall back to the activity and its block. A configured clock variable is recognized only where no feature of that name is visible; the report line names the configuration naming it, or counts the configurations when several do. A dotted name through an untyped pin is refused rather than dereferenced. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…behavior-migration
…alves as JavaScript does
Opaque string literals decode \b \f \xHH \uHHHH \u{...} and line
continuations; legacy octal escapes and characters the notation cannot
spell (NUL, vertical tab, other control characters, lone surrogates) are
typed refusals instead of silently losing the backslash. Math.round(x)
translates to RealFunctions::floor(x + 0.5), which rounds a half toward
positive infinity as JavaScript does, rather than RealFunctions::round.
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…rce's edge unresolved, divide Java whole numbers as Java does An opaque action whose body translates is no longer marked inert, so the flows leaving the output pins its body assigns are written; only an output pin the body never assigns, or an action whose body is kept as a comment, keeps its flows as comments, each saying which. An edge in no partition takes its source node's partitions whenever the source is in any, so a source held by two dimensions naming different objects refuses the edge's guard with that clash instead of reading it through the target's partition. Java is its own dialect: `/` on two whole operands lowers to `RealFunctions::floor((x - x % y) / y)`, the truncating quotient Java computes; when the types cannot tell that both operands are whole, the division is refused. JavaScript `/` stays real division. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…urrogate pairs A JavaScript whole number beyond 2^53 - 1, which the script's Number rounds, and any whole number beyond what an Integer holds are refused instead of being written as a different value. A feature typed by an enumeration or a block is known to hold no scalar: it is refused where a number or Boolean is wanted, when assigned a value of a type that neither is nor specializes its own, and when compared with or chosen beside a value sharing no type with it; a feature of unknown type is still trusted. A high and low surrogate escape pair in a string spells the one character it encodes; a lone surrogate stays refused. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… the clock's own scale The Time library's Clock reads a point on the clock's scale, in seconds since the run began, the same instant `accept at` waits for. Add a conformance case that stores the read into a TimeInstantValue, subtracts two reads into a DurationValue, waits at an instant derived from one and orders two instants, and a robustness case that comparing the instant with a point on Time::UTC is refused since the library binds the universal clock to no named scale. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… unary operand of ** A default in a language the translator reads is never re-read as v2 syntax once the translator has refused it for a construct, call, name or type; only a syntax or unsupported-language refusal falls back to the generic v2 parser. The value of a typed pin or result is translated against the pin's scalar type, so a Real expression on a Boolean pin is refused rather than written. JavaScript rejects -x ** y (a unary operand of ** must be parenthesized), so the translator refuses it; (-x) ** y, -(x ** y) and 2 ** -x still translate, and a Java body's ** is refused since Java has no exponentiation operator. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… `/` to an exact Integer quotient A dotted name that reads through a multi-valued feature or a swimlane representing a collection is a collection from that step on: arithmetic on it, a scalar assigned from it and any write through it are refused naming the carrier, and a call behavior action in such a lane runs in the caller's context, the report saying why. Only collection reductions (java.util.Collections.max/min) accept it. A Java body's `/` over two whole numbers is written OpenSysMLMathFunctions::quotient(x, y), a new function of the non-normative extension library: the exact quotient truncated toward zero, ErrDivisionByZero for y == 0 and ErrArithmeticOverflow for the least Integer by -1 instead of an overflow through a Real intermediate. Parenthesized groups keep their precedence so (i + 1) / 2 is written without redundant parentheses; (-x) ** y keeps its own. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ature's classifier and multiplicity A translated opaque default or typed pin/result value was checked only against the feature's scalar base, so a value of a block or enumeration type went unchecked: a conditional choosing a Gauge could be written as the default of a Needle. The wanted type now carries the feature's scalar, its non-scalar type with generalizations, and whether it holds one value; a value of another family or a collection where one value is held is refused with a typed report note. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…mp a flow final's start A DurationObservation whose event list names an element the document does not define was read as the one event that resolves, so a two-event observation with a dangling end became a single-node span. Unresolved event references are now checked before the resolved events are read, and the observation is refused with the dangling ids in the report. A FlowFinalNode is the point a token ends, so an observation may start there: the edges into it lead to the stamp (through a merge when there are several) and then to done, and a duration constraint on it is written as its wait. A flag asking for a flow final's end is still refused, as for an initial node. Fixture acquisition.xmi covers both, end to end at run time. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Java declares Math.floor(double) and Math.ceil(double) as returning a double, so a Java / after them is real division; the translator typed both Integer and lowered Math.floor(x) / 2 to OpenSysMLMathFunctions::quotient. Java Math.round stays Integer, as its long result is. The meter fixture runs both at the boundary and the docs and changelog state the rule. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Java's == and != on String operands compare identity, which a v2 value comparison does not reproduce; a Java body's equality with an operand known to be a string is now a construct refusal, while a.equals(b) and "x".equals(b) on strings translate to a == b, the comparison of content. JavaScript string equality is unchanged. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…behavior-migration Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # tests/migrate/testdata/xmi/acquisition.golden.report.txt # tests/migrate/testdata/xmi/acquisition.golden.sysml # tests/migrate/testdata/xmi/acquisition.xmi # tests/migrate/testdata/xmi/meter.golden.report.txt # tests/migrate/testdata/xmi/meter.golden.sysml # tests/migrate/testdata/xmi/meter.xmi # tests/migrate/testdata/xmi/plant.golden.report.txt # tests/migrate/testdata/xmi/plant.golden.sysml # tests/migrate/testdata/xmi/plant.xmi # tests/migrate/testdata/xmi/reactor.golden.report.txt # tests/migrate/testdata/xmi/reactor.golden.sysml # tests/migrate/testdata/xmi/reactor.xmi
…behavior-migration Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # internal/core/migrate/activity.go # internal/core/migrate/behavior.go # internal/core/migrate/migrate.go # internal/core/migrate/states.go # internal/core/migrate/values.go # internal/core/xmi/xmi.go # internal/core/xmi/xmi_test.go
…t; pin it by fixture A pin is bound as a script variable by the tool, so a body's bare name that matches a pin of the node is that pin, not a feature of the represented object; the reference now lists pins first in the lookup order, and acquisition.xmi has a node in the swimlane whose value pin Retries shadows TCS::Retries, asserted written as the pin. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…havior migration Re-homes the branch's runtime files under internal/exec/runtime and its migrate files under internal/translate/migrate, rewriting their imports to the new layer paths; no behavior changes. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…uments Java's Math.max and Math.min take exactly two arguments, so a Java body calling them with one or three is a compile error there and is now a call refusal; JavaScript's are variadic and keep folding pairwise. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…aque-behavior-migration Merge origin/feature/v1-state-interaction-fidelity (simulation configurations, results and probability; interactions, receptions, pseudostates, contexts and carriers) with the opaque-body, swimlane and clock-read migration. Both sides' semantics are kept: the migration struct carries the union of fields; a translated opaque state behavior stays Approximated with its "written as v2 assignments" note; the REPL's missing-observable test follows the "no completed run ... produced a value" diagnostic; the branch-side region.go is dropped in favor of the populatedRegions in states.go; the stdlib snapshot is regenerated over the merged libraries. Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration
Bot
changed the base branch from
develop
to
feature/v1-state-interaction-fidelity
September 19, 2026 21:24
…ble multiplicities An English body has no calls: `Math.sqrt(t) > 3` or `name.equals(other)` in an English guard is a construct refusal, never dispatched to the script function table. A script label is read only when every word of it names JavaScript, ECMAScript, JS, Rhino or Nashorn, plus at most one digit-led version; `JavaScript Expression Language`, `ECMAScript for XML`, `Rhino Shell` and `JSON` are languages the translator does not read, so their bodies stay comments. A part whose multiplicity is not written in numbers (`1..n`) may hold one object or several: a name read through it, a swimlane representing it and a swimlane whose object is reached through it are refused with the part named, instead of being read as one object. `meter.xmi` covers each path end to end. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…the argument JavaScript's Math.max(x) and Math.min(x) answer x itself, so the pairwise fold starts from the one argument after checking it is a number; only the no-argument forms, which answer an infinity the subset has no form for, are refused. A Java body still requires exactly two arguments. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…elity' into feature/v1-opaque-behavior-migration Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # docs/reference/sysml-v1-migration.md # internal/translate/migrate/activity.go
… edge leaves A decision, merge, fork or join with no outgoing edge ends the token as done does, but the stamp a DurationObservation (or the wait a DurationConstraint) placed on it was never written: the node had no entry, so every succession into it led straight to done and the observation's attribute stayed blank. Such a node now gets the lead-in a flow final has — the stamp, and a merge when several edges reach it — before done. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…elity' into feature/v1-opaque-behavior-migration Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # internal/translate/migrate/activity.go # internal/translate/migrate/behavior.go
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The v1 → v2 migrator wrote activities and state machines in executable v2 but kept every OpaqueAction, OpaqueExpression, OpaqueBehavior and guard body a Cameo author had written in JavaScript or "English" as a comment, unless it already happened to parse as v2 with visible names. That left retry loops unguarded and clock bookkeeping dead. This PR translates a bounded subset of those languages into executable v2, resolves the names they use through the swimlane (
ActivityPartition.represents) they sit in, givessimtimean executable form, and cleans up the report for the constructs this exposes.Opaque-language subset (
internal/translate/migrate/opaque.go, new): a lexer/parser/translator for JavaScript / ECMAScript / Java / empty-language bodies and English guards. A script label is read only when every word of it isJavaScript,ECMAScript,JS,RhinoorNashornplus at most one version (Javascript Rhino,JavaScript (Nashorn) 1.8,ECMAScript 2015); a Java label isJavaplus at most one version; any other word (JavaScript Expression Language,ECMAScript for XML,JavaCC,JSON) is a language the translator does not read.x = e,x += e(-=,*=,/=), sequences separated by;/newlines,var/let/const x = efor a declared local (aconstis never assigned again, nor is aninparameter).+ - * / %(a Java/on two whole operands isOpenSysMLMathFunctions::quotient(x, y), the exact quotient truncated toward zero; refused when the types cannot tell), comparisons,&& || !, parentheses, trivially mappable?:,Math.min/max/abs/floor/ceil/round/sqrt/pow→RealFunctions::…(Math.min/maxvariadic in JavaScript, folded pairwise — one argument is itself, none is refused as an infinity — and exactly two arguments in Java;Math.round(x)asfloor(x + 0.5), JavaScript's half-up;Math.ceil(x)asOpenSysMLMathFunctions::ceiling(x), exact at the least Integer where-floor(-x)overflows), string escapes decoded as JavaScript reads them (legacy octal escapes and characters the notation cannot spell are refused),java.util.Collections.max/min(s)→max/minon a collection. A Java body'sa.equals(b)on strings isa == b(the comparison of content), and its==/!=with a string operand is refused, since Java compares strings there by identity; JavaScript string==is a value comparison and translates as it stands.TRUE/FALSE/true/false, a property name — spaces and all: a run of words between operators is one name (not Guide Star Lost and i < Retries), refused whole when nothing visible is called that —not X,X and Y,X or Y, comparisons written with=/==, parentheses. English has no calls:Math.sqrt(t) > 3orname.equals(other)is a construct refusal, never dispatched to the script function table.refusedLanguage/Syntax/Construct/Call/Name/Type/Context): loops, calls outside the table,new, string methods, regexes, non-simple declarations,this.in unknown context, type-incompatible operands. A refusal of a recognized language is final — the body is never re-read as v2 syntax. A translation is emitted only when every name resolves, the types agree where known, and the result parses with the v2 parser.Swimlane context (
swimlane.go, new): partitions are indexed throughnodeandinPartition;representsis resolved as a property (this.tcs, nestedthis.a.b) or classifier (this). Names in a body or guard resolve against the represented object first, then the activity's parameters/locals, then the owning block. Unset, dangling, absent, untyped and unwritable representations and nodes outside any partition each get a defined behavior and a note; a partition is reported mapped only when a name was actually resolved through it. A partition representing a collection (cells : Gauge[2]), or a dotted name through a multi-valued feature, is a collection from that step on: arithmetic on it, a scalar assigned from it and a write through it are refused naming the carrier, and a call in such a lane runs in the caller's context with the report saying why; onlyjava.util.Collections.max/minreductions accept it.Clock (
translate.go,internal/exec/runtime/clock_read.go): theSimulationConfig.timeVariableName(defaultsimtime) is read aslocalClock.currentTime; the runtime evaluatescurrentTimeon the standardClocks::Clockagainst the sharedruntime.Clock, so-observe this.Time_Acq_Totaland%runsreport the migrated attribute. AssigningcurrentTimeis refused withErrClockNotAssignable; a redefinition of it (attribute now :>> currentTime;) is recognised by the redefinition chain, not the name, so it reads the clock and refuses the write alike, bare or through a part. Acalc defresult expression is checked against the single return/output parameter's type; a behavior with several outputs is refused as a result expression. ADurationObservationwhose two events are nodes of the activity (an initial node counts as a start, stamped right afterstart, before the activity's wait) becomes a pair of[0..1]attributes with no default, the start instant and the elapsed clock, assigned only when a run reaches each event, so an unreached observation is blank in%runs/-observerather than0.0(observation.go, new); every other shape — including a flag asking for the end of an initial, final or control node — says why it is not.Where it lands: opaque/value/structural-feature actions →
assignstatements (multi-statement bodies as a sequence); guards →ifon the succession; OpaqueExpression defaults → attribute defaults; JavaScript constraints →constraint defexpressions over the ConstraintBlock's parameters; JavaScript OpaqueBehaviors →calc def/ action bodies. Symbolic durations (ditSetup s) resolve through the same lookup.Report hygiene: a
CallBehaviorActionwith no behavior, no pins and only a DurationConstraint is a leaf step and is mapped ("a step with a duration and no further behavior"); calls with pins or an unresolved behavior stay unmapped with a pin-oriented reason. A UML region with no vertex is not written (nothing could enter it, and the runtime correctly refuses a region with no initial state) and is reported approximated — this is the change tostates.go, kept to the seven lines inregions()with the helper inregion.go.XMI: stereotypes whose profile schema namespace ends in
.xmi(SimulationProfile.xmi) were mistaken for XMI bookkeeping and dropped. The shared reader ondevelopnow recognizes only the metamodel namespaces (IsXMINamespace/IsUMLNamespace); this PR pins that withTestStereotypeUnderSchemaNamespaceininternal/core/xmi/sysmlv1. It surfaces«Refines»,«TMT_Requirement», … in the report (see the Dependency row below).Runtime:
OpenSysMLMathFunctions::quotient(x, y)joins the non-normative extension library (integerQuotient: exact Integer quotient truncated toward zero,ErrDivisionByZerofory == 0,ErrArithmeticOverflowfor the least Integer by-1rather than a wrapped result; conformancecalc_integer_quotient,TestRuntimeRobustnessIntegerQuotient, snapshot regenerated).performmembers of an action def performed as a classifier behavior run as steps of the performance; a non-behavior named as an action fails withErrNotABehavior(this moved two refused rows ofdocs/project/analysis-library-census.json).TMT proof (
TMT-2024x.mdzip)Header (this PR's base,
feature/v1-state-interaction-fidelity, → this PR, sameTMT-2024x.mdzip):28113 mapped, 10367 approximated, 5656 unmapped→28625 mapped, 10319 approximated, 5192 unmapped(1713 skipped as profile/library/notation content and 764 unreferenced elements in both); both outputs validate with 0 errors.Every other row is identical on the base and here: ValueSpecificationAction (approximated for structural reasons, 4 now carry translated values), OpaqueBehavior (translated bodies are written as state entry/do bodies and stay approximated; toolkit calls outside the table and empty bodies keep their comments), DurationObservation (all 380 observe no event; the report says so), Region and Dependency (the vertex-less-region and XMI-namespace fixes are on the base).
Execution:
bin/sysml tmt.sysml -action "'Acquire Target with IRIS and NFIRAOS - Logical' 'Acquire Target with IRIS and NFIRAOS'" -runs 100 -seed 1 -observe clock -observe this.Time_Acq_Total— on the base output all 100 runs complete butthis.Time_Acq_Totalstays at its0.0default (theTime_Acq_Total = simtime - Time_Acq_Totalbody is a comment); herethis.Time_Acq_Total=clockin every run: min 19.4 s, mean 141.5 s, p50 116.8 s, max 455.5 s. (These differ from the figures over adevelopbase because the base writes the model's own«Probability»values —p = ProbabilityBTOOP,1.0 - ProbabilityBTOOP, … — where even splits were written before.) The retry loop is exercised on the seeds whose draws reach it:-runs 1 -seed 6 -traceshowsthis.TCS.i := 1, guardi >= Retries(Retries -> 2) false, retry,this.TCS.i := i + 1 -> 2, guard true, exit.Lamp::'Lamp Ohm Law'(V = R * i) is written asconstraint def 'Lamp Ohm Law' { in attribute V, i, R : ScalarValues::Real; V == R * i }and, with-seed 1(the base's draw policy refuses an unseededuniform), evaluatestruefor(12.0, 6.0, 2.0),falsefori = 5.0, and%validateof an instantiatedLampreports the constrainttrue/falsethe same way.Cameo's stored snapshots for the same activity: Group 0 mean 96.6 s (6 runs, 80–119 s), Group 1 157 s, Groups 3/6 ≈ 210 s. The OpenSysML numbers differ and are not tuned: which per-group SimulationConfig applies (probabilities and durations) is selected by the simulation-configuration work this PR is stacked on.
Known limitations
min(a,b)/max(a,b)(noMath.) and other toolkit calls (ALH.*) are refused, as are UMLExpressiontrees and bodies that are not expressions.DurationObservationwith no events (all 380 in TMT) cannot be timed; the referring DurationConstraint keeps its own value.-observereports features of the run's performer; features of nested parts (this.TCS.i) are read through-traceor the REPL.SimulationConfigdoes not say which bodies itsexecutionTargetreaches, so any visible feature of that name shadows it, but a name configured for one activity is also recognized in another.banks : Bank[1..n]) may hold one object or several: a name read through it, a partition representing it, and a partition whose object is reached through it are refused with the part named, never read as one object (the written v2 feature holds one value, as the base migrator writes it, so a default of that feature is checked as one value).-9223372036854775808is refused because the magnitude is read before its sign.Integer: where a JavaScriptNumberwould round a result beyond 2⁵³ or a Javaint/longwould wrap, the translation does not reproduce that; the modeler'sInteger-typed feature keeps the exact value. Only literals are refused for range (above 2⁵³ − 1 in JavaScript, beyondint64anywhere).Math.floor/ceil/roundreturn the library'sInteger: exact through the whole Integer range, and a whole Real at or beyond 2⁶³ or below −2⁶³ is a typed arithmetic-overflow error at run time, never a wrapped Integer.DurationObservationwith nofirstEventflags spans both nodes whole (first's start to second's end); UML 2.5.1 givesfirstEvent([0..2]) no default. Explicit flags are honored per event.translate(internal/translate/migrate) orexec(internal/exec/runtime) package, so the layering gate'spackageLayer,permittedandtoleratedtables are unchanged, and no ratcheted baseline moved.Specification basis
Kernel Semantic Library
Clocks.kerml(Clock::currentTime,Clocks::universalClock) andOccurrences.kerml(Occurrence::localClock) for the clock read; SysML v2 textual notation forassign, succession guards (if),constraint defandcalc defbodies for the translated output; UML 2.5.1ActivityPartition::represents,OpaqueAction/OpaqueExpression/OpaqueBehaviorbodies andDurationObservation::eventfor the source. Adds theClock::currentTimeand action-def-performance rows todocs/project/spec-compliance.md.How it was verified
gofmt -l .(empty),go vet ./...,make lint,go build ./...,go test ./...(81 packages ok).OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 OPENSYSML_REQUIRE_PILOT_LIBRARY_XMI=1 go test -count=1 ./tests/corpus ./tests/identity -run 'TestTrainingExamples|TestPilotCorpora|TestPilotLibraryXMI|TestCorpusRoundTrip'ok.make docs-check(0 broken links),python3 scripts/changelog.py check,python3 scripts/check-doc-ids.py,go run -C tools ./cmd/doc-counts -check.opaque_test.go(translator table + refusals),opaque_migration_test.goand fixturesacquisition.xmi,plant.xmi,reactor.xmi(partitions incl. nested/unset/dangling/absent/untyped/unwritable/no-partition, JS assignments, English guards, clock reads, non-defaulttimeVariableName, DurationObservation shapes, constraint bodies, calc bodies, refusals),empty_region_test.go(undertests/migrate),internal/translate/xmi/sysmlv1/xmi_test.go(TestStereotypeUnderSchemaNamespace), runtime conformanceclock_read_local_clock_elapsed,clock_read_redefined_current_timeandperformed_action_def_perform_members_are_steps, robustnessrobustness_clock_read_test.go(TestRuntimeRobustnessClockRead).Checklist
make testandmake lintpass locallychanges/unreleased/<slug>.<section>.md, not as an edit toCHANGELOG.mdmake docs-countsrun if a gate count moved (compliance rows need nothing: the census is counted at docs build)F4,K5) in the body, docs, or changelog