Skip to content

Commit e26beaf

Browse files
Textual notation improvement (#368)
* [Add] role-based element location: cursor primitives + pinned and structural role predicates * [Add] role-based element location: cursor primitives, role predicates, implied-element reconciliation * [Add] hard-error unclaimed body elements in generated and hand-coded dispatch * [Update] dedupe duplicate clauses within a structural-signature alternative * Update of nameresolutioncache and improved textual notation builders * missing file * [Add] textual-notation baselines for six Simple Tests models * missing binding layer * [Add] textual-notation baselines for nine more Simple Tests models * [Add] textual-notation baselines for ViewTest and DecisionTest * [Add] textual-notation baselines for six more Simple Tests models * [Fix] stop redefinedFeature scope walk at the first general that binds the name * [Fix] gate the redefinedFeature scope walk on a proper-type resolution * [Add] textual-notation baselines for three more Simple Tests models * [Fix] name a MembershipImport through the alias Membership, not its member element * [Fix] keep Features out of the global Namespace so bare names re-resolve * [Add] textual-notation baselines for AnalysisTest and StateTest * [Update] record confirmed KEBNF divergence 11 and correct ledger rows * [Add] textual-notation baseline for RequirementTest * flag TextualNotationValidationTestFixture.cs as integrationtest * Fix SQ issues * [Fix] record each resource's root namespace instead of re-deriving it * [Update] build each scope's name-resolution index on first probe * doc guideline * remove integration flag
1 parent 80b25ca commit e26beaf

166 files changed

Lines changed: 14671 additions & 3969 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/agents/textual-notation-reviewer.md‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ You are a master of the SysML2.NET textual notation pipeline. Your job is to rev
1212
Any code change touching any of these paths is in your remit:
1313

1414
- **`SysML2.NET.Serializer.TextualNotation/Writers/`** — every `.cs` file under `Writers/`, both hand-coded partial classes (`*.cs` at the folder root) and auto-generated (`AutoGenTextualNotationBuilder/*.cs`). This includes `TextualNotationValidationExtensions.cs`, `MembershipValidationExtensions.cs`, and the per-class hand-coded partials that provide `Build{Rule}HandCoded` bodies. The namespace for all of these is `SysML2.NET.Serializer.TextualNotation.Writers`.
15+
- **`SysML2.NET.Serializer.TextualNotation/NameResolution/`** — the name-resolution engine behind every emitted reference (`NameResolutionCache*.cs`, `NamespaceBindingIndex*.cs`, `LocalScopeResolver.cs`, `ImportExpansion.cs`, `SegmentNaming.cs`, `ContainmentPaths.cs`, `ResolutionGraph.cs`). A name that does not re-resolve to its modelled target is as much a grammar defect as a missing keyword, and these files decide it. Ground verdicts here in KerML §8.2.3.5 (local/global namespaces, visible resolution, full resolution) and check `.team-notes/name-resolution-conformance-ledger.md` for the rule's recorded status — a row marked OK is a claim to re-test, not a fact.
1516
- **`SysML2.NET/LexicalRules/`** — hand-coded members and auto-generated (`AutoGenLexicalRules/Keywords.cs`, `SymbolicKeywordKind.cs`, `SymbolicKeywordKindExtensions.cs`).
1617
- **`SysML2.NET.CodeGenerator/HandleBarHelpers/RulesHelper.cs`** — the central code-gen logic.
1718
- **`SysML2.NET.CodeGenerator/Templates/Uml/*.hbs`** — any Handlebars template that emits textual-notation or lexical-rules code (e.g. `core-textual-notation-builder-template.hbs`, `core-textual-notation-shared-builder-template.hbs`, `core-lexical-*.hbs`).
@@ -26,7 +27,17 @@ Before reviewing anything, re-read these to refresh your understanding:
2627
- **`Resources/SysML-textual-bnf.kebnf`** and **`Resources/KerML-textual-bnf.kebnf`** — the grammar source of truth. When a rule appears in both, SysML overrides KerML.
2728
- The `<para>{…}</para>` XML doc on the generated public `Build{Rule}` method — the authoritative grammar fragment for that specific method. For a hand-coded partial (`Build{Rule}HandCoded`), the grammar context is the same rule — the generated sibling delegates to the hand-coded method because the generator can't produce the full body automatically.
2829

29-
### Hypha grounding (if installed)
30+
### Hypha grounding — REQUIRED for any spec-dependent verdict
31+
32+
**Whenever a verdict turns on what the specification requires, or on metamodel structure, ground it
33+
with Hypha and cite the clause. Do not issue such a verdict from memory, from a sibling rule, or from
34+
the shape of the surrounding code.** A confident-but-ungrounded reading is the failure mode this
35+
review exists to catch — it has produced real defects here, in both directions: a rule "confirmed"
36+
against a plausible prior that the clause contradicts, and a correct implementation reported as a
37+
defect because the reviewer inferred the rule rather than reading it.
38+
39+
State the clause you relied on in the finding. If a verdict rests on a defined term (`parameter`,
40+
`namingFeature`, root `Namespace`, `redefinedFeature`), quote the definition rather than assuming it.
3041

3142
When the Hypha plugin's skills are available in your session, use them instead of reasoning from memory:
3243

‎CLAUDE.md‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,61 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
66

77
SysML2.NET is a .NET C# SDK implementing the OMG SysML v2 specification (based on Beta 4 pilot implementation). It provides metaclass DTOs/POCOs, serializers (JSON, XMI, MessagePack), a REST client, a DAL layer, and a Blazor WebAssembly viewer application. Current version: 0.19.0.
88

9+
## Project goal: conformance to the SPECIFICATIONS
10+
11+
**The target for EVERY part of this SDK is conformance to the OMG specifications — not parity with
12+
any reference implementation.** This is the standing intent across the whole solution, not a
13+
textual-notation concern. Treat it as governing whenever the two conflict.
14+
15+
The specifications in scope, and what each governs here:
16+
17+
| Specification | Governs |
18+
|---|---|
19+
| **KerML 1.0** | abstract syntax (metaclasses, properties, multiplicities, ordering, redefinitions), derived properties and operations (OCL), invariants, model-level semantics including implied relationships (§8.4.2) |
20+
| **SysML v2.0** | the systems layer on top of KerML — same three concerns — plus the textual concrete syntax |
21+
| **Systems Modeling API & Services 1.0** | the PIM types and services, and their REST/HTTP binding (`SysML2.NET.REST`, `PIM/`, `SysML2.NET.Serializer.Dictionary`) |
22+
| **Model interchange** | the project/archive interchange format (`SysML2.NET.Kpar`) |
23+
24+
This applies per layer, with the same standard everywhere:
25+
26+
- **Abstract syntax** (`Core/AutoGen*`) — structure comes from `Resources/*_only_xmi.uml`, the single
27+
source of truth. Multiplicity, `ordered`, redefinition/subsetting are contractual, not incidental.
28+
- **Derived properties and operations** (`Extend/`) — the OCL in the XMI is the contract. Translate
29+
it faithfully AND to its intent; where the OCL is terse or leans on a defined term, ground the
30+
intent (`hypha:spec-citation`) rather than guessing from a sibling.
31+
- **Constraints/invariants** — a validation rule is conformant only if it implements the stated
32+
invariant, not an approximation that happens to pass the corpus.
33+
- **Concrete syntax** — the KEBNF and the clauses it comes from; deviations recorded, never silently
34+
encoded.
35+
- **Semantics** — implied relationships and library specializations follow the clause, not what makes
36+
a particular model render nicely.
37+
- **API/PIM** — service behaviour and payload shape follow the API specification, not the behaviour
38+
of a particular server.
39+
40+
Rules that hold at every layer:
41+
42+
- **The specification decides.** Where a reference implementation and the specification disagree, the
43+
specification wins and the divergence is recorded (`GRAMMAR.md` "Known KEBNF divergences", the
44+
deviation ledger, or `GrammarErrata.cs`). Never implement a behaviour whose only justification is
45+
"the reference implementation does it".
46+
- **A reference implementation or its corpus is a test oracle, not the definition of correct.** The
47+
pilot's `.sysml` files, a reference API server's responses, a sample model — all catch regressions
48+
cheaply, and agreement is usually evidence of correctness. A diff is a question, not a verdict.
49+
Note specifically that the pilot's serializer replays the author's original source text wherever
50+
the abstract syntax records no choice (optional keywords, name spellings, formatting), so on those
51+
points it is not an authority at all — which is what the ledger's ACCEPT categories encode.
52+
- **Where the spec is genuinely ambiguous**, pick a reading, cite the clause, and say in one line
53+
that the clause admits more than one — do not silently encode one reading as fact.
54+
- **Where the spec offers several valid forms and ranks none**, the choice is ours: make it a setting
55+
(see issue #359) or state it as a house convention. Do not present it as a requirement.
56+
57+
**Not yet achieved anywhere — do not claim conformance for any layer.** Confidence today rests
58+
largely on end-to-end comparison against sample data, which measures non-regression rather than
59+
conformance. What each layer needs before conformance can be asserted: **round-trip tests** (write →
60+
re-read → assert the same model; this exists for no serializer today, including the textual writer)
61+
and **per-invariant unit tests** so an individual rule — a derivation, a constraint, a resolution
62+
rule, a service contract — is falsifiable in isolation rather than only via an end-to-end diff.
63+
964
## Build & Test Commands
1065

1166
```bash
@@ -224,6 +279,57 @@ Direct pushes to `development` or `master` are forbidden. All work lives on a fe
224279

225280
**Why this split**: the user is the reviewer of record. The commit is the review and the push is the delivery — both are the user's calls. The agent's git involvement is bounded to: (a) create the branch locally + push the empty ref (so the user's push later is frictionless), and (b) leave the rest alone. This was tightened after two failures: first the agent auto-pushed branches to `development` directly, then over-corrected by auto-committing on the user's behalf.
226281

282+
## Comments: write as few as possible
283+
284+
Comments break readability. Default to **none**; every comment kept needs a justification. This
285+
applies to production code, tests, and the generator alike.
286+
287+
- **Delete rather than write.** Prefer a better name or an extracted method over an explanation.
288+
- **Never narrate the what** — a comment paraphrasing the line below it is noise.
289+
- **No history, no benchmarks, no "this used to…"/"previously"/"an earlier approach"/"was reverted".**
290+
Source control holds it. Never put timing measurements in code.
291+
- **No worked examples, no specific case names.** Never justify a rule with a particular model, a
292+
validation-corpus file (`13a-Model Containment`, `ISQ::mass`, `first start;`), or an issue number.
293+
State the rule; if it needs authority, cite the clause (`KerML §8.2.3.5.3`) and nothing more.
294+
- **Never reference the OMG pilot implementation** — or any other tool — as the reason for behaviour.
295+
The writer implements the **specification**, not another implementation's choices. Where the spec
296+
is genuinely ambiguous, say so in one sentence with the clause; where the KEBNF is defective, that
297+
belongs in `GrammarErrata.cs` with its rationale, not scattered through the writers.
298+
- **No notes to future editors** ("keep in step with X", "do not remove"). Encode it in a guard or a
299+
test.
300+
- **XML docs still required on every type and member** (`DEVELOPMENT_STANDARDS.md` §5.1), but held to
301+
one sentence per tag, two as the ceiling — no `<para>` elaborations, no essays.
302+
303+
### The bright line — the ONLY test for keeping an inline comment
304+
305+
Earlier wording said to keep "a non-obvious *why*". That is not testable and gets self-served. The
306+
rule is:
307+
308+
> **A comment may ONLY state a constraint that would cause a reader to break something if they did
309+
> not know it. A comment may NEVER explain why the change was made.**
310+
311+
Apply it as a question with a yes/no answer: *if a reader deleted or rewrote this code without the
312+
comment, would they introduce a defect?* No → delete the comment. "It helps the reviewer understand
313+
my change" is not a yes; that belongs in the commit message.
314+
315+
**Budget: at most 2 added comment lines per change.** Over that, delete until it fits or ask first.
316+
317+
**Signature words that mean you are writing a commit message, not a comment.** If an added comment
318+
line contains any of `now`, `previously`, `rather than`, `instead of`, `used to`, `was `, `no
319+
longer`, `we `, `I `, or restates a `<remarks>` already on the same member — delete it. A
320+
`PreToolUse` hook rejects these on `Edit`/`Write`, so it fails loudly rather than reaching review.
321+
322+
Worked example of the failure, from this repo:
323+
324+
```csharp
325+
// The supplier now records each resource's root as it is read rather than re-deriving it, so
326+
// interior elements no longer reach this list and no metaclass filter is needed to keep them out.
327+
var otherRootNamespaces = globalNamespaces?.Where(c => c != null && !ReferenceEquals(c, rootNamespace))
328+
```
329+
330+
Two banned signatures (`now`, `rather than`), it annotates a self-evident `Where`, and deleting it
331+
costs a reader nothing. The commit message was the right home for all of it.
332+
227333
## Quality rules
228334

229335
- **OCL index base is 1-based; translate positional access accordingly and NEVER mix the two forms.** OCL collections are 1-based (`->at(1)` is the first element; `->first()` ≡ `->at(1)`). Two correct C# forms, applied by target:

‎SysML2.NET.CodeGenerator/Extensions/GrammarErrata.cs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,22 @@ public static class GrammarErrata
9090
"parameter path emits 'out verdict', which re-parses as a plain FeatureMembership with " +
9191
"direction out and so loses the metaclass. CalculationBodyItem is already declared in the " +
9292
"same file, so the replacement resolves without any further correction."),
93+
new("OccurrenceUsagePrefix",
94+
"OccurrenceUsagePrefix : OccurrenceUsage =\n BasicUsagePrefix",
95+
"OccurrenceUsagePrefix : OccurrenceUsage =\n UnextendedUsagePrefix",
96+
"SysML 8.2.2.9.2 builds OccurrenceUsagePrefix on 'BasicUsagePrefix', which contains no " +
97+
"'EndUsagePrefix', so 'isEnd' is UNREACHABLE for every occurrence usage — 'end port p1: P;' " +
98+
"and 'end item a;' cannot be written at all. This is issue #124 item 7, already recorded as " +
99+
"an accepted divergence in SysML2.NET.CodeGenerator/GRAMMAR.md. Three sources say the " +
100+
"notation is real: (1) the pilot's own grammar reaches the end prefix here (org.omg.sysml.xtext " +
101+
"SysML.xtext, OccurrenceUsagePrefix); (2) the corpus writes it — 'end port p1: P;' in the " +
102+
"Simple Tests ConjugationTest and 'end [1] item a : A' in ConnectionTest; (3) the metamodel " +
103+
"sets isEnd on exactly those elements. There is no admissible alternative spelling: " +
104+
"'BasicUsagePrefix' reaches 'RefPrefix', whose 'isConstant ?= constant' then renders the " +
105+
"pilot's transform-set isConstant as 'constant port p1: P' — which drops isEnd AND asserts a " +
106+
"constant the source never wrote. 'UnextendedUsagePrefix = EndUsagePrefix | BasicUsagePrefix' " +
107+
"is declared in the same file and dispatches on isEnd, so a non-end usage keeps the exact " +
108+
"BasicUsagePrefix behaviour and only an end usage changes."),
93109
new("DefinitionElement",
94110
" | InterfaceDefinition\n | PortDefinition",
95111
" | InterfaceDefinition\n | AllocationDefinition\n | PortDefinition",

‎SysML2.NET.CodeGenerator/GRAMMAR.md‎

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,40 @@ re-diagnosed:
231231
| #3 | `MetadataUsage` not wired into any dispatch point | 14-Language Extensions |
232232
| #9 | `SatisfyRequirementUsage` requires `assert` | 08-Requirements |
233233
| #10 | `CaseBodyItem` admits no `ReturnParameterMember` | 10-Analysis and Trades |
234-
| #11 | `EnumeratedValue` cannot carry prefix metadata (`#Security enum secret`) | 13-Model Containment, 14-Language Extensions |
234+
| #11 | `EnumeratedValue` cannot carry prefix metadata (`#Security enum secret`) | **CONFIRMED** — see below | 13-Model Containment, 14-Language Extensions, `Simple Tests/MetadataTest` |
235235

236236
Items #2, #4, #5, #6 concern productions with no corpus coverage.
237237

238+
### #124 item 11 — confirmed, and it silently DROPS a FeatureTyping
239+
240+
```
241+
EnumerationUsageMember : VariantMembership = MemberPrefix ownedRelatedElement += EnumeratedValue
242+
EnumeratedValue : EnumerationUsage = 'enum'? Usage ← no prefix slot
243+
EnumerationUsage : EnumerationUsage = UsagePrefix 'enum' Usage
244+
```
245+
246+
`EnumeratedValue` has no `UsagePrefix`, so — unlike `EnumerationUsage` — nothing consumes a
247+
`PrefixMetadataMember` from the cursor. The consequence is worse than an unwritable keyword: the
248+
annotation is the FIRST entry in the value's `ownedRelationship`, so the cursor is still parked on it
249+
when `BuildUsage` reaches the positional `FeatureSpecializationPart` guard
250+
(`cursor.Current is IFeatureTyping || …`). That guard fails and **the typing is never emitted**.
251+
252+
`Simple Tests/MetadataTest` shows it exactly — all three values carry a `FeatureTyping` to
253+
`ClassificationLevel`, but only the annotated one loses it:
254+
255+
```
256+
enum uncl: ClassificationLevel = 0; ← [FeatureTyping, FeatureValue]
257+
enum conf: ClassificationLevel = 1; ← [FeatureTyping, FeatureValue]
258+
enum secret = 2 { @ Security; } ← [OwningMembership(MetadataUsage), FeatureTyping, FeatureValue]
259+
```
260+
261+
**Fix when this is taken up:** emit the annotation as a prefix and advance the cursor past it before
262+
delegating to `Usage`, which yields the pilot's `#Security enum secret : ClassificationLevel = 2;`.
263+
That is the deviation this item already licenses, and it restores the typing as a side effect.
264+
`BuildEnumeratedValue` is generated, so the change belongs in the generator — as a HandCoded fallback
265+
for this rule, the way `EntryTransitionMember` (item 8) is handled. `MetadataTest` stays out of
266+
validation until then.
267+
238268
## Model ↔ notation reconciliations (NOT divergences)
239269

240270
Cases where the grammar offers two conformant productions for one model, so the writer must choose.
@@ -305,3 +335,40 @@ Each condition alone is refuted by one of the three; the conjunction fits all of
305335

306336
`IsValidForDefaultReferenceUsage` still encodes the one spec-mandated case (`!IsEnd &&
307337
Direction.HasValue`): a directed usage is always referential, so the keyword is redundant there.
338+
339+
### `DefaultInterfaceEnd` vs `PortUsage` — the optional `port` keyword on an interface end
340+
341+
```
342+
InterfaceOccurrenceUsageElement : Usage = DefaultInterfaceEnd | StructureUsageElement | BehaviorUsageElement
343+
DefaultInterfaceEnd : PortUsage = isEnd ?= 'end' Usage ← end p1: P;
344+
PortUsage = OccurrenceUsagePrefix 'port' Usage ← end port p1: P;
345+
```
346+
347+
Both alternatives of `InterfaceOccurrenceUsageElement` reach `PortUsage`, so `end p1: P;` and
348+
`end port p1: P;` round-trip to the same metaclass and nothing records which the author wrote.
349+
350+
The spec settles the OPTIONALITY and one hard condition on it. SysML 2.0 §7.14.2 Interface
351+
Definitions and Usages (p. 109, normative): "All the end features of an interface definition or
352+
usage must be port usages, so the use of the `port` keyword is optional on such end features if no
353+
owned cross feature is declared on the end."
354+
355+
`DefaultInterfaceEnd` has no notation for a cross feature — only `EndUsagePrefix` carries the
356+
`( ownedRelationship += OwnedCrossFeatureMember )?` slot — so an end that owns one MUST take the
357+
`port` form. `IsValidForDefaultInterfaceEnd` implements exactly that condition
358+
(`IsEnd && OwnedCrossFeature() == null`); where both forms are open the writer takes the
359+
keyword-less one, the production's first alternative. That choice is ours, not a requirement.
360+
361+
### `BinaryConnectorPart` vs `NaryConnectorPart` — a two-end connector
362+
363+
```
364+
ConnectorPart : ConnectionUsage = BinaryConnectorPart | NaryConnectorPart
365+
BinaryConnectorPart = ownedRelationship += ConnectorEndMember 'to' ownedRelationship += ConnectorEndMember
366+
NaryConnectorPart = '(' ownedRelationship += ConnectorEndMember ',' ownedRelationship += ConnectorEndMember
367+
( ',' ownedRelationship += ConnectorEndMember )* ')'
368+
```
369+
370+
`NaryConnectorPart` admits exactly two ends, so for a two-end connector `connect a to b` and
371+
`connect (a, b)` are both conformant and produce the same model. Three or more ends leave only the
372+
n-ary form. The writer prefers the binary form at exactly two ends — a house convention, not a
373+
requirement. `IsValidForBinaryConnectorPart`, `IsValidForBinaryConnectorDeclaration` and
374+
`IsValidForBinaryInterfacePart` each count `EndFeatureMembership` children for it.

‎SysML2.NET.CodeGenerator/HandleBarHelpers/RuleGenerationContext.cs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ public TextualNotationRule FindRule(string ruleName)
125125
/// </summary>
126126
public int LoopProgressCheckCounter { get; set; }
127127

128+
/// <summary>
129+
/// Monotonically-incrementing counter used to produce unique role-based-consumption variable
130+
/// names (e.g. <c>elementAsOwningMembership0</c>) across the emission of a single rule body.
131+
/// Required because a <c>TryTake</c> out-variable shares the enclosing scope, so two role-based
132+
/// consumptions of the same target type in one generated method would collide (CS0136).
133+
/// Incremented by <c>RuleProcessor.TryEmitPinnedRuleConsumption</c>.
134+
/// </summary>
135+
public int TakenElementCounter { get; set; }
136+
128137
/// <summary>
129138
/// Determines whether the next sibling element is a terminal that uses <c>AppendLine</c>
130139
/// (e.g., <c>{</c>, <c>}</c>, <c>;</c>), in which case a trailing space would be unnecessary.

0 commit comments

Comments
 (0)