Files
Socrates/docs/sysml-modeling.md
dtoro f1c4566576 Initial commit — design docs + Phase 0 validation harness
Sets up the Socrata project repo with:

docs/ — strategy and design documents
  - idea.md: full product vision
  - implementation-plan.md: Phase 0 + Phase 1 MVP plan
  - phase-0-validation.md: 2-week validation experiment strategy
  - phase-0-plan.md: concrete Phase 0 build plan
  - phase-0-results.md: Phase 0 gate outcome — GO for MVP
  - sysml-modeling.md: metamodel + SE discipline + validation rules
  - socrates.md: agent character, surfaces, modes, prompts, lifecycle
  - sync.md: bidirectional text↔diagram sync engineering
  - design-source/: HTML/CSS/JS handoff bundle from Claude Design

phase-0/ — validated harness (CLI, no UI, no DB)
  - LM Studio (local OpenAI-compatible) generation + detection + judge
  - PlantUML rendering for SysML model visualization
  - 10-seed corpus (8 working + 2 holdouts)
  - 5 corpus runs with iteration history in reports/
  - Final gate: 10/10 pass, mean 4.32/5, holdouts validated

Phase 1 MVP scope and milestones documented in implementation-plan.md.
2026-04-28 22:07:38 +02:00

38 KiB
Raw Permalink Blame History

SysML & Systems-Engineering Modeling in Socrata

This document defines what subset of SysML and systems-engineering practice Socrata adopts, why, and how it lands in code. It is the bridge between the vision ("SysML is the engine") and the implementation plan ("here are the milestones").


1. What SysML is, and what we're borrowing

SysML is OMG's UML profile for systems engineering — nine diagram types, hundreds of metaclasses, ~600 pages of spec. It was designed for engineers modeling physical/software systems with formal rigor: aircraft, satellites, control loops.

Socrata uses a deliberate subset of SysML's structural and traceability concepts, plus the discipline of systems thinking (decomposition, boundary definition, traceability, constraint formalism). It does not aim for OMG spec compliance, XMI interop, or feature parity with Cameo / MagicDraw.

Why a subset?

PMs are not systems engineers. They want the thinking discipline — separating entities from behaviors, surfacing constraints, tracing claims to elements — not OMG spec compliance. Asking a PM to choose between aggregation and composition loses the user. Asking them whether Aristotle is part of Course or merely associated with it is exactly the question we want them to face.

SysML 1.x vs. SysML 2.0

SysML 2.0 (released 2024, KerML-based, text-first) is the OMG future. Its tooling is still maturing and its formalism is academic. Socrata models a SysML 1.x-shaped subset internally. If 2.0 wins the industry we can map our model to KerML serializations later — the metamodel concepts (Block, Property, Association, Constraint, Requirement) carry across.


2. The Socrata metamodel — what we keep

Concept SysML term Socrata role
Block Block The fundamental noun — an entity in the system. Sub-kinds: block, actor, constraint, system.
Property value property A typed attribute on a block (self_efficacy, refusal_policy).
Association Association Generic labeled relationship between two blocks (consults, scoped_to).
Composition composite association Whole-part relationship — diamond marker (Course contains Assignment).
Generalization Generalization Is-a hierarchy — hollow triangle (Undergrad is a Student).
Constraint Block Constraint Block A named rule applied to one or more blocks (FERPA boundary). Body is plain text in MVP, parsed expressions in Phase 2.
Requirement Requirement Tagged statement (REQ-001…) with satisfy and derive relationships to other elements.
Stereotype «stereotype» Visual + semantic kind tag («block», «actor», «constraint», «system»).

Property typing

MVP supports four property types: string, number, boolean, enum<…>. Multiplicity is 0..1 (single-valued, default) or 0..* / 1..* (multi-valued). Types are advisory in MVP — surfaced as soft warnings when violated, not hard-enforced at edit time. Hard enforcement arrives with Phase 2 parametric expressions.

Requirement relationships

SysML defines six requirement relationships: derive, satisfy, verify, refine, trace, copy. MVP supports two:

  • satisfy — a block fulfills a requirement. Drives the rail's traced/untraced dot.
  • derive — a requirement is derived from a higher-level requirement.

verify is introduced in M8 with experiment tracking (an experiment verifies a requirement). refine, trace, copy are deferred — they're useful for large requirement specs, not for product ideation.

Stereotype mechanism

Stereotypes are how SysML extends the metamodel without changing it. In MVP, four stereotypes are baked in: «block», «actor», «constraint», «system». In Phase 3 ("domain profiles"), stereotypes become user-definable per project — a "Marketplace" profile might add «seller», «listing», «transaction» stereotypes with their own validation rules.


3. What we're not using from SysML — and why

SysML 1.x is a large language. This section enumerates every major SysML concept we are deliberately not adopting, grouped by SysML category, with the reasoning. The intent is that anyone evaluating Socrata's modeling rigor can see the boundary clearly.

The pattern across every "no" below tracks one of four reasons:

  1. Engineering ceremony. Useful for hardware/control-systems engineers; not load-bearing for PM thinking.
  2. Behavior modeling. Out of scope for "structure first; behavior later only if users pull."
  3. Replaced by a Socrata-native concept. SysML's mechanism exists, but Socrata captures the same intent through the changelog, Socrates threads, or research findings.
  4. Premature formalism. Could be added when proven necessary; would slow MVP if added now.

3.1 Diagram types

SysML 1.x defines nine diagram types. Socrata renders only one combined view.

Diagram type Status Reason
Block Definition Diagram (BDD) Partial We render the structure (blocks + relationships) but not the BDD/IBD split. The split is a 1990s tooling artifact: BDDs show the type catalog, IBDs show internal wiring. We collapse both into one diagram because PM-scale models rarely justify the separation.
Internal Block Diagram (IBD) Not used Same as above. If a block needs internal structure, it gets composition relationships in the single diagram.
Requirement Diagram Replaced Surfaced as a rail listing, not a separate diagram. The diagram form adds visual noise and PMs read requirements better as a list.
Package Diagram Not used (#4) One project = one namespace in MVP. Packages become useful only when models exceed ~50 blocks.
Parametric Diagram Not used (#1) Mathematical equations between properties (latency_p50 = inference_time + network_rtt). Engineering analysis, not PM thinking. Phase 2 may add a constraint-expression DSL but never a parametric diagram.
Activity Diagram Not used (#2) Process flows, decisions, fork/join. Behavior modeling, deferred.
Sequence Diagram Not used (#2) Object-to-object message ordering over lifelines. Behavior modeling, deferred.
State Machine Diagram Not used (#2) States, transitions, events. Re-evaluate in Phase 2 if PMs need feature/user state modeling.
Use Case Diagram Replaced Actors are kept (as block kind); use cases themselves are replaced by Requirements + the narrative document. UC diagrams add a third surface that overlaps both.

3.2 Block-internal concepts

Of the dozen+ concepts SysML defines for what a block contains, we keep two: properties and stereotypes.

Concept Status Reason
Value Property Kept (as Property) The typed attribute. Core.
Part Property Replaced Composition associations express the part-of relationship instead. SysML's Part Property is technically "the slot inside a block that holds a part"; we model it as a relationship on the graph for queryability.
Reference Property Not used (#1) A typed reference to another block from inside this one. Same intent as a non-composition association; we use the association uniformly.
Constraint Property Not used (#1) An instance of a Constraint Block bound to specific properties. Only meaningful with parametrics.
Operation (method) Not used (#2) Behavior on a block (e.g., Aristotle.askQuestion()). PMs don't model methods; they model what the system is and contains.
Reception Not used (#2) Signal handlers — "block reacts to event X". Behavior modeling, deferred.
Port (Standard / Flow / Full / Proxy) Not used (#1) Interaction points on a block boundary. Powerful for hardware/electronics; ceremony for ideas. The four sub-kinds (Standard, Flow, Full, Proxy) are SysML's most-criticized accidental complexity.
Interface Block Not used (#1) Defines the type of a Port. Without ports, no interface blocks.
Distributed Property Not used (#4) Property whose value distribution is described statistically. Engineering analysis.

3.3 Connection concepts

Concept Status Reason
Association Kept Generic relationship. Core.
Composition Kept Whole-part. Core.
Generalization Kept Is-a hierarchy. Core.
Aggregation Data only Shared part-of. Data shape supports it; UI hides it in MVP. PMs find aggregation-vs-composition confusing; we default everything to composition unless explicitly told otherwise.
Connector Replaced An Association is a type of relationship; a Connector is an instance of one between two ports. Without ports we just have associations.
Binding Connector Not used (#1) Connects equal values between properties. Parametric-only.
Item Flow Not used (#1) What flows along a connection (data, energy, material). Useful for pipeline / control-system modeling, premature for ideas.
Dependency Replaced Generic UML "X depends on Y." We capture dependency through the explicit edges in the dep graph (Section 7), not as a first-class metamodel relationship.

3.4 Behavior modeling — entirely deferred

All behavior modeling is out of scope through Phase 2. Re-evaluate after MVP feedback.

Concept Status Reason
Activity Not used (#2) The container for a behavior flow.
Action (CallBehaviorAction, CallOperationAction, SendSignalAction…) Not used (#2) Steps within an activity. SysML defines ~15 action subtypes — heavy ceremony.
Object Flow / Control Flow Not used (#2) Edges between actions.
State / Transition / Event / Trigger / Effect / Guard Not used (#2) The state-machine vocabulary.
Lifeline / Message / CombinedFragment Not used (#2) The sequence-diagram vocabulary.
Use Case / Include / Extend Replaced The intent — "what users do with the system" — is captured by the narrative document and Requirements. UC diagrams duplicate that surface.
Actor Kept (as block kind) The participant. Useful as a structural concept.

3.5 Parametrics — deferred

Concept Status Reason
Constraint Block Kept (as Constraint) The named rule. Body is plain text in MVP.
Constraint Property Not used (#1) Bind a Constraint Block to specific block properties. Phase 2 may add this with a constraint DSL.
Value Type Partial We have four built-in types (string/number/boolean/enum). SysML's Value Type system extends to user-defined types; deferred to Phase 2.
Unit Not used (#1) "meter", "second", "USD". Phase 2 may add for KPI properties; not for ideas.
Quantity Kind Not used (#1) The dimensional category of a unit (length, time, currency). Same as Unit.
Distribution / Real / Complex / EnumerationValue subtypes Not used (#4) The richer Value Type hierarchy.

3.6 Requirement relations

SysML defines six requirement relationships. MVP uses two; one more arrives in M8.

Relation Status Reason
satisfy Kept A block satisfies a requirement. Drives traceability.
derive (deriveReqt) Kept Requirement-from-requirement. Useful for hierarchical req decomposition.
verify M8 A test case verifies a requirement. Socrata uses Experiments instead of SysML Test Cases — verify becomes experiment.verifies(requirement) in M8.
refine Not used (#3) A model element refines a requirement (clarifies its meaning without changing intent). The narrative document does this in prose; formalizing it as a relationship adds no value.
trace Replaced Generic "this is related to that." Replaced by the narrative chip mechanism — every chip is a trace.
copy Not used (#4) A requirement that is a literal copy of another with potential text override. Useful for cross-project library management; meaningless in single-project MVP.
Test Case Replaced SysML's formal test concept. Replaced by Socrata's Experiment (more general — covers user research, data analysis, A/B tests, not just functional verification).

3.7 Allocation — entirely deferred

Concept Status Reason
Allocate Not used (#2) Maps behavior to structure (allocate an Activity to a Block). Requires behavior modeling first.
AllocateActivityPartition Not used (#2) Partitions an Activity by allocated structure. Same reason.

3.8 Model organization & cross-cutting

Concept Status Reason
Package Not used (#4) Namespacing for large models. One project = one namespace in MVP. Add packages when a single model crosses ~50 blocks.
Profile Phase 3 Domain-specific stereotype + validation bundles ("Marketplace profile", "SaaS profile"). The mechanism is reserved in the metamodel; profiles ship in Phase 3 per the spec.
Stereotype Kept (hardcoded) Four stereotypes hardcoded in MVP («block» «actor» «constraint» «system»). User-defined stereotypes arrive with profiles.
View / Viewpoint Replaced "What does this stakeholder see?" SysML's mechanism for filtered views. Replaced by the dual-canvas + rail UI — every PM sees the same view; filtering is done by selection and search rather than formal viewpoints.
Rationale Replaced A note explaining why a model element is the way it is. Replaced by the changelog reasoning field — every applied proposal stamps its reasoning into the audit trail. Stronger than Rationale because it's tied to the moment of change.
Problem Replaced A flagged issue on a model element. Replaced by Risks and ValidationIssues — both Socrata-native and richer (Risks have severity and mitigation experiments).
ConformsTo Not used (#4) "This package conforms to that profile." Meaningful only when profiles ship.
Comment (UML built-in) Replaced Free-text annotation. Replaced by the prose surrounding chips in the narrative document — every block can have arbitrary explanatory text adjacent to its chip.

3.9 SysML 2.0 / KerML additions

SysML 2.0 (released 2024) is a complete redesign on top of KerML. Tooling is immature. Socrata watches but does not yet adopt these.

Concept Status Reason
Text-first surface syntax Watching SysML 2.0's headline feature. Our markup-augmented narrative is conceptually similar but PM-tuned, not KerML-conformant.
Calculation Not used (#1) Replaces parametric constraints with computable expressions. Phase 2 candidate.
Action / Item / Occurrence Not used (#2) The 2.0 behavior-modeling vocabulary. Same deferral as 1.x activities.
Variant Modeling Phase 2 Modeling product-family variants. Maps onto Phase 2 branching/Opportunity Solution Tree.
Snapshot / Time Not used (#4) Modeling state at points in time. Premature.

3.10 The principle

The pattern: structure before behavior, semantics before syntax, enforcement before formalism.

Socrata enforces the SE thinking discipline — define the system, decompose, surface boundaries, trace claims — without the OMG spec compliance tax. Every concept above can be reconsidered post-MVP if real users hit a wall. The metamodel's event-sourced storage means we can extend types and re-project history rather than migrate destructively.


4. Two contested replacements, examined

Two of the "replacements" in §3 deserve scrutiny because they cut a load-bearing SysML concept and claim a Socrata-native concept covers it. One holds; one was overstated and needs a fix.

4.1 Use Cases → narrative + Requirements

What a SysML Use Case actually provides. A Use Case is more than the stick-figure-on-an-oval diagram. It's a structured artifact with:

  • A goal an Actor wants to achieve via the system
  • Pre-conditions and post-conditions that bound the interaction
  • A main flow — numbered, ordered steps of actor↔system exchange
  • Alternative flows — branches off the main flow for edge cases
  • Exception flows — failure paths
  • «include» — reusable sub-flows always invoked
  • «extend» — optional flows that extend the base
  • Generalization — specialized UCs inheriting from a base

The diagram is decoration. The textual Use Case Description is the load-bearing part.

What we lose by dropping it. Three things, none cheaply replaced by free-form prose:

  1. Edge-case discoverability. A UC's "alternative flows" section is a prompt to enumerate — it forces the PM to ask "what if the user does X instead?" Prose lets you skip that. A PM writing "Sarah opens Aristotle, types her question, gets a Socratic prompt" never gets pushed to think about "what if Sarah's question is itself a request for the answer?"
  2. Sprint-sliceability. Use Cases map cleanly to user stories and sprints. A narrative paragraph doesn't.
  3. Step-to-element traceability. UCs let you trace step 3 of "Borrow a book" to the LibraryCard.validate() operation on a specific block. Our chip mechanism traces blocks/properties referenced anywhere in prose, but loses the per-step granularity.

What we don't lose. The Actor (kept as a block kind), the goal (capturable as a Requirement), the constraints (Constraint blocks), the involved system elements (chips). The static content of a UC is fine.

What replaces the lost capability in MVP.

  • Edge-case discoverability is partially recovered by Socrates: when narrative prose describes a happy path, Socrates is prompted (in M6) to surface "what about the failure mode where…?" as a margin note. This is weaker than the UC alt-flow ritual because Socrates depends on inference, not on a forcing function.
  • Sprint-sliceability is genuinely lost in MVP. PMs who run sprints will need to externalize that elsewhere (Linear, Jira). We don't try to be a sprint planner.
  • Step-to-element traceability is lost. We accept that.

Where the need re-enters: Opportunities in Phase 2. Teresa Torres's Opportunity Solution Tree (cited as a reference framework in the spec) is — mechanically — a tree of user-goal nodes that decompose into solution-experiment branches. An "Opportunity" is structurally equivalent to a Use Case's goal layer; a "Solution branch" is structurally equivalent to a Use Case flow. So Phase 2 brings back UC-shaped thinking through the OST door, not the SysML door.

This means: if a beta PM says "I want to model my user flows," the answer is not "we'll add SysML UC support" — it's "that's the Opportunity feature, and we should pull it forward into Phase 1.5 if you need it now." The Opportunity model can be tighter than SysML UC because we know the audience: PM-facing, story-shaped, not engineering-shaped.

Phase 1.5 escape hatch (if MVP feedback demands it). Add a Scenario element type — lighter than full SysML UC:

interface Scenario {
  id: string;
  title: string;
  primaryActorBlockId: string;
  goal: string;                         // free text
  preconditions: string[];
  steps: ScenarioStep[];                // ordered
  alternativeSteps?: ScenarioStep[][];  // each is an alt branch
  derivesFromRequirementIds: string[];
}
interface ScenarioStep {
  actorAction: string;
  systemResponse: string;
  involvedElementIds: string[];         // blocks/properties touched
}

This gives us 80% of UC value (alt flows, step traceability) at 20% of the metamodel ceremony. We don't add this in MVP because (a) Opportunities will subsume it in Phase 2, and (b) a Scenario type is itself a forcing function — adopting it changes the seed-screen interview substantially.

Verdict. The replacement is defensible for MVP. Beta PMs will likely miss UC-style thinking; the right answer is to listen for that signal and either pull Opportunity work forward or ship the lightweight Scenario type — not to bolt SysML UCs onto the metamodel.


4.2 Rationale → changelog reasoning field

My original claim: the changelog reasoning field replaces SysML Rationale, and is "stronger because tied to the moment of change."

Why that was overstated. Rationale and changelog reasoning answer two different questions:

Question Wants
"Why does this constraint exist?" (current state) Rationale
"Why did we add this constraint last Tuesday?" (history) Changelog reasoning

The changelog gives moment-of-change reasoning, which is excellent for audit but the wrong default for "I just clicked this element, tell me why it's here." The most recent changelog entry that touched a block might be a position-move ("dragged left") with reasoning "cleanup" — which is true but useless to a reader trying to understand the element.

So changelog reasoning is necessary but not sufficient to replace Rationale. The "stronger" framing missed that the reader usually wants synthesis, not history.

What SysML Rationale actually provides that I cut. A persistent, queryable, element-attached "why" — readable inline when you hover or select an element. Static. Authored once at element creation, edited rarely.

Three options, examined:

  • Option A: Defend the original claim. Keep changelog reasoning, no Rationale field. Reader synthesizes by scrolling. Rejected. Brittle: latest entry isn't necessarily the explanatory one. Bad UX for the most-clicked question in the product ("why is this here?").

  • Option B: Add a writable rationale: string field to every model element. PMs maintain it manually. Rejected. PMs won't maintain it. The field rots within weeks. We end up with a parallel-truth problem where the rationale contradicts the model.

  • Option C: Synthesized Rationale view, computed from the changelog. When the user requests "why does this exist?" on an element, we run an LLM summary over the subset of ChangelogEntry rows that touched it, plus the SocratesThread messages associated with those changes. Output is a 13 sentence "current rationale" rendered inline. Adopted.

Why Option C is genuinely better than SysML Rationale.

  1. No maintenance burden. Users don't author or update it. It is always current with respect to the changelog.
  2. Synthesizes across edits. The constraint "FERPA boundary" might have been added in Week 1 with one reasoning, scoped down in Week 4 with another, re-applied to a new block in Week 7. SysML Rationale typically captures only the first; the synthesis captures the trajectory.
  3. Tied to evidence. Each sentence in the synthesis can cite the changelog entry that produced it (clickable in the UI), so users can drill into the moment.
  4. Cheap by default. Cached at the element level, invalidated only when a new changelog entry touches that element. Most reads are free.

The cost. An LLM call on cache miss. The "Why this exists?" query may be a heavily-clicked feature; we need a cache strategy day-one. Approach:

  • Cache key: (elementId, lastChangelogTsTouchingElement)
  • Cache value: synthesized 13 sentence rationale + per-sentence changelog citations
  • Invalidation: any new changelog entry whose payload.affectedElementIds includes elementId
  • Storage: a RationaleCache table or Redis if we add Redis later
  • Backfill: on first open of a project, walk the dep graph and pre-warm rationales for every element in the model snapshot — one batch Haiku call, ~50ms per element parallelized

What this means for the doc. Add this to the metamodel implementation:

// lib/sysml/rationale.ts
async function synthesizeRationale(elementId: string, projectId: string): Promise<{
  summary: string;
  citations: Array<{ changelogEntryId: string; text: string }>;
}>

Edge case: an element that has never been touched since creation. Its rationale is the seed-conversation context that produced it — already captured in the initial changelog entry. Synthesis returns that single entry's reasoning verbatim.

Edge case: a deleted element. The changelog still has its history. We can synthesize a "tombstone rationale" — useful for branching (Phase 2: "this opportunity was rejected because…").

Verdict. The original claim was wrong as stated. The corrected claim — "the changelog plus an LLM-synthesized Rationale view replaces SysML Rationale and is genuinely stronger" — holds. The fix adds one new module (lib/sysml/rationale.ts), one cache table, and a cache-warming pass on project open. None of this changes the build sequence; it slots into M6 (when the LLM gateway lands) as a small additional capability.



5. The systems-engineering discipline Socrata enforces

This is the load-bearing part. The metamodel is plumbing — what makes Socrata useful is the thinking discipline it imposes. Socrates is the agent that enforces it.

4.1 Define the system of interest

Every project has exactly one block stereotyped «system» — the System of Interest (SoI). It is the thing being designed. Every other block is one of:

  • Inside the SoI — a subsystem (composition relationship to SoI)
  • Outside the SoI — an actor or external system
  • Applied to the SoI — a constraint

In the Aristotle example: Aristotle is the SoI; Student, Instructor, Course, Assignment are outside actors/systems; FERPA boundary is a constraint applied to it.

The seed-screen interview elicits the SoI as the very first modeling decision. The validator enforces "exactly one system" (rule M3).

4.2 Decompose top-down, just-in-time

Subsystems decompose by composition. Socrates encourages decomposition only when it surfaces a real decision the PM is facing. No premature decomposition — a Student block does not need a Brain sub-block until something depends on the brain being modeled.

4.3 Surface boundaries explicitly

A block is either inside the SoI or outside. Crossing the boundary requires an explicit association — no implicit reach into externals. In Phase 2 the boundary may render as a visible enclosing region in the diagram.

4.4 Requirements before realization

Requirements are first-class and rail-visible. Each must have at least one satisfying block. Untraced requirements are warnings (rule T1). Unused blocks — no satisfaction, no association, no requirement reference — are soft warnings (rule T2): they may be context, or may be dead.

4.5 Constraints capture non-negotiables

A constraint differs from a requirement in modality:

  • Requirement — what the system should do. Choice. ("REQ-002: response latency under 1.2s P50.")
  • Constraint — what the system must not violate. Invariant. ("FERPA boundary: coursework never leaves institutional tenancy.")

You don't "satisfy" FERPA — you obey it or you don't ship. The metamodel separates them so Socrates can reason about each correctly.

4.6 Trace every claim

Every assumption, risk, experiment, and research finding links back to one or more model elements. The traceability graph is what powers impact analysis: change a block, see all dependents — including the assumptions and risks that ride on it.


6. Implementation: types

// lib/sysml/model.ts

export type BlockKind = 'block' | 'actor' | 'constraint' | 'system';

export type PropertyType =
  | { kind: 'string' }
  | { kind: 'number' }
  | { kind: 'boolean' }
  | { kind: 'enum'; values: string[] };

export type Multiplicity = '0..1' | '1' | '0..*' | '1..*';

export interface Property {
  id: string;
  name: string;
  type: PropertyType;
  multiplicity: Multiplicity;
  defaultValue?: string | number | boolean | string[];
  description?: string;
}

export interface Block {
  id: string;
  label: string;
  kind: BlockKind;
  stereotypes: string[];           // e.g. ['system'] or ['actor', 'role:student']
  properties: Property[];
  position: { x: number; y: number; w: number; h: number };
  description?: string;
}

export type AssociationKind =
  | 'association'      // generic →
  | 'composition'      // ◆—  whole-part
  | 'aggregation'      // ◇—  shared part (data-shape supported, UI deferred)
  | 'generalization'   // —▷  is-a
  | 'constraintApplies'; // - - dashed

export interface Association {
  id: string;
  fromBlockId: string;
  toBlockId: string;
  label: string;
  kind: AssociationKind;
  multiplicity?: { from: Multiplicity; to: Multiplicity };
}

export interface Constraint {
  id: string;
  label: string;
  expression: string;              // free-text in MVP, AST in Phase 2
  appliesTo: string[];             // block ids
}

export type RequirementRelation =
  | { kind: 'satisfy'; blockId: string }
  | { kind: 'derive'; fromReqId: string }
  | { kind: 'verify'; experimentId: string };  // wired in M8

export interface Requirement {
  id: string;
  tag: string;                     // REQ-001
  text: string;
  relations: RequirementRelation[];
}

export interface SysMLModel {
  systemOfInterestId?: string;     // the «system» block
  blocks: Block[];
  associations: Association[];
  constraints: Constraint[];
  requirements: Requirement[];
}

These types are the canonical model. Everything else — narrative chips, diagram nodes, rail entries — is a projection.


7. Implementation: validation rules

lib/sysml/validate.ts is a pure function (model: SysMLModel) => ValidationIssue[]. Rules are grouped by category, each with a stable code so prompts and tests can reference them by id.

Structural (referential integrity)
  S1  every association.fromBlockId / toBlockId resolves to a block
  S2  every constraint.appliesTo[i] resolves to a block
  S3  every requirement satisfy.blockId resolves to a block
  S4  every requirement derive.fromReqId resolves to a requirement
  S5  block ids unique; requirement tags unique; association ids unique

Semantic (SysML metamodel)
  M1  generalization is acyclic (no inheritance loops)
  M2  composition is acyclic (no part-of loops)
  M3  exactly zero or one block has kind='system'
  M4  constraint blocks may not be sources/targets of composition
  M5  property names unique within a block

Traceability (the spec's "everything links back")
  T1  every requirement has ≥1 satisfy relation                → warning
  T2  every block is referenced by ≥1 association OR satisfies ≥1 req → soft warning
  T3  every property is referenced by ≥1 narrative chip        → soft warning (Phase 1.5)

Project-specific (user-authored constraints; Phase 1.5)
  user Constraint blocks with parseable expressions become validators at runtime

Issue shape:

type Severity = 'error' | 'warning' | 'soft';
interface ValidationIssue {
  code: string;                    // 'S1', 'M2', 'T1', ...
  severity: Severity;
  message: string;                 // PM-readable, written by Socrates prompts
  anchor: { kind: 'block' | 'association' | 'constraint' | 'requirement' | 'property'; id: string };
}

Issues surface in four places:

  • Rail — colored dot on the entry
  • Diagram — red border / underline on the affected node
  • Narrative — margin note tied to the chip
  • Dock — Socrates summary, only the highest-severity new issues from this edit

8. Implementation: dependency graph for impact analysis

lib/sysml/depgraph.ts builds a directed graph each time the model changes. It is the structural input to every Socrates impact analysis.

Nodes: every Block, Property, Constraint, Requirement, Assumption, Risk

Edges:
  Block        → Property                (contains)
  Block        → Block                   (per Association, edge labeled with kind)
  Constraint   → Block                   (applies-to)
  Requirement  → Block                   (satisfy)
  Requirement  → Requirement             (derive)
  Assumption   → Block | Property        (linked)
  Risk         → Block | Property        (linked)
  Experiment   → Requirement             (verify)

Impact closure: when a proposed op touches an element, dependents(elementId) returns the BFS closure across the graph. That set is exactly what Socrates summarizes in impact analysis. Rebuilds are incremental — only nodes touched by the change re-resolve their adjacency.

This is what makes impact analysis computed rather than guessed — it is the single biggest reason for the SysML metamodel investment.


9. Implementation: mapping the metamodel to the UI

8.1 React Flow (diagram canvas)

Each metamodel kind gets a custom node or edge type:

Metamodel React Flow type Visual treatment
block nodeTypes.block rounded rect, stereotype label, divider, property compartment
actor nodeTypes.actor rounded rect, distinct fill, «actor» label
constraint nodeTypes.constraint dashed border, no property compartment
system nodeTypes.system larger, stronger border, «system» label
association edgeTypes.association solid line, label pill, default arrow
composition edgeTypes.composition solid, filled diamond marker
generalization edgeTypes.generalization solid, hollow triangle marker
constraintApplies edgeTypes.constraintApplies dashed line, no marker

Markers and dash patterns follow SysML conventions, softened per the prototype's visual language (no sharp corners, gentle shadows).

The property compartment renders inside the block as a flex column, matching diagram.jsx. Selecting a node opens an inline editor for label, properties, stereotypes.

8.2 TipTap (text canvas)

Custom inline node chip with attrs { kind, refId }:

  • kind: 'block'refId = Block.id
  • kind: 'property'refId = '${blockId}.${propertyId}' (composite)
  • kind: 'association'refId = Association.id
  • kind: 'requirement'refId = Requirement.id
  • kind: 'constraint'refId = Constraint.id

NodeView renders identically to the prototype's chip styles. Slash-menu commands (/block, /property, /req, /assoc, /constraint) insert a chip — autocomplete picks an existing element or offers "create new".

When a referenced element is renamed in the diagram or rail, every chip pointing at its refId updates automatically (M5 sync).

8.3 Left rail

The rail's three sections map directly to model projections:

  • Outline — narrative-only, derived from heading hierarchy in the ProseMirror doc
  • Model — every Block grouped by kind, with property-count tooltip
  • Requirements — every Requirement, traced/untraced dot per T1

10. Implementation: serialization & versioning

Each commit (changelog entry that mutates the model) writes:

  1. The op set into ChangelogEntry.payload — the delta.
  2. A fresh full-model snapshot into SysMLModel(version=v+1, json=…) — the state.

Snapshots are immutable. Projection tables (Block, Association, Constraint, Requirement) always represent the latest version. Diffs between any two versions are computed by lib/sysml/diff.ts over the JSON snapshots — block-level / property-level / association-level.

This is the foundation for Phase 2 branching (a branch is a fork of the snapshot lineage) and the spec's audit-trail requirement.


11. The SysML→PM translation layer

PMs don't say "I want a composition". They say "Course contains Assignment". Socrates is the translator:

  • NL → metamodel: parses natural-language relationship hints — contains / consists of / made up of → composition; is a / is a kind of → generalization; obeys / must comply with → constraintApplies; everything else → association. Returns confidence per inference; low-confidence relations surface as proposals, not auto-commits.
  • Metamodel → NL: renders validation issues in PM-friendly language. "Course is part of Aristotle, but Aristotle is also part of Course — that's a circular composition (M2). Did you mean an association instead?"
  • Hides ceremony: the PM never sees "association multiplicity 0..*". They see "a Student can take many Courses." The mapping is one-way internal — the model stores 0..*, the UI renders prose.

12. What this metamodel buys us

  1. Computed impact analysis. Every change has a deterministic blast radius — Socrates walks the graph rather than guessing.
  2. Free traceability. Requirements ↔ blocks ↔ assumptions ↔ findings are already linked; the rail is just a view.
  3. Constraint enforcement. Validation surfaces contradictions the PM didn't notice.
  4. Glossary, specs, release notes (Phase 3) are projections of the model — generated, not authored.
  5. Future SysML interop. If a customer wants XMI export or imports a tool's model, the metamodel maps cleanly.

13. What this metamodel risks

  • Over-formalization. PMs may reject the model if they have to choose composition vs. association. Mitigation: Socrates picks defaults from NL; choice is surfaced only when ambiguous and high-impact.
  • Auto-generation accuracy. Wrong block / association inference erodes trust quickly. Mitigation: confidence threshold ≥ 0.85 for auto-commit; under-suggest rather than over-suggest; every auto-generated element is reviewable in one dock action.
  • Performance at size. Dependency graph rebuilds and validation passes on every save. Mitigation: incremental graph updates, debounced background validation, Haiku-cheap continuous detection.
  • Locked metamodel. If we get the core types wrong, migrations are painful. Mitigation: event-sourced storage means the JSON snapshots are replayable; we can recompute projections under a new metamodel without losing user data.

14. Open questions for the build

These are SysML-specific decisions that should be revisited as M4 (validator) and M5 (sync) land:

  • Aggregation vs. composition in the UI. Data shape supports both; the UI ships only composition in MVP. Should we expose aggregation when users ask for "shared part-of"?
  • Constraint expression language. Free text in MVP. Phase 2 candidates: a tiny DSL (property < value, property in {…}), JS-Sandbox expressions, or natural language with LLM-time evaluation.
  • Stereotype extensibility. Hard-coded set in MVP. When does user-defined stereotype support land — Phase 2 or Phase 3 with profiles?
  • Multiplicity rendering. SysML shows 0..* next to association ends. PMs find this opaque. Show as prose ("many") in the UI? Hide entirely until edit? Default to 0..* and only surface when explicitly set?
  • Property derivation. SysML supports derived properties (/computed_value). Useful for KPIs ("/dau = activeUsers / day"). Defer to Phase 2?