Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -704,3 +704,33 @@ Then add to root aggregation: `.aggregate(..., mymodule, mymoduleJS, mymoduleNat
40. **RiddlLib analysis API methods** - `getHandlerCompleteness()`, `getMessageFlow()`, `getEntityLifecycles()` on shared RiddlLib trait + JS `RiddlAPI` facade. Each runs standard passes plus the relevant analysis pass
41. **HandlerCompleteness in ValidationOutput** - `ValidationOutput.handlerCompleteness: Seq[HandlerCompleteness]` populated in `ValidationPass.postProcess()`. Categories: `BehaviorCategory.Executable`, `PromptOnly`, `Empty`
42. **Downstream integration plans** - Each downstream project (riddlsim, riddl-gen, riddl-mcp-server, synapify) has a `RIDDL-INTEGRATION-PLAN.md` describing how to consume new library features. Designed for separate Claude instances working in those projects
43. **gh CLI requires unset GITHUB_TOKEN** - When using `gh`
commands locally, `unset GITHUB_TOKEN` first so `gh` uses the
user's keychain credentials instead of the (possibly expired
or wrong-scope) env var
44. **PR merge with branch protection** - Use
`gh pr merge --admin --merge --delete-branch=false` to bypass
branch protection when merging development→main for releases
45. **RiddlLib.ast2bast(root)** - Converts parsed AST to BAST
binary bytes. Shared trait returns `Array[Byte]`, JS facade
returns `Int8Array`. Uses BASTWriterPass internally. Test
verifies BAST magic header bytes
46. **Consumer update notes** - `RIDDL-UPDATE-NOTES.md` in
synapify, riddl-mcp-server, ossum.ai covers 1.5.0 breaking
change (opaque Root) and 1.7.0 new functions. Separate from
the detailed `RIDDL-INTEGRATION-PLAN.md` files
47. **Schema parser uses `time-series` (hyphenated)** - The
`schemaKind` parser in RepositoryParser.scala expects
`"time-series"`, not `"timeseries"`. Check `StringIn(...)`
in `schemaKind` for all valid schema kind keywords
48. **Consecutive schemas need `with` terminators** - Schema
definitions inside a repository need `with { ... }` blocks
to terminate because `data.rep(1)` is greedy and consumes
subsequent `of` clauses. Without `with`, the parser can't
find the boundary between consecutive schemas
49. **Adaptor cross-context type resolution** - Use parent-
independent `resolution.refMap.definitionOf[Type](pathId)`
(no parent arg) for resolving types referenced in adaptor
handlers. The parent-keyed overload fails because the
resolution pass stores refs keyed under the OnMessageClause
parent, not the adaptor's parent
378 changes: 228 additions & 150 deletions NOTEBOOK.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
passes/input/check/adaptor-direction/adaptor-direction.riddl(12:9->34):
Inbound Adaptor 'BadInbound' handles Command 'SourceCtx.DoIt' from Context 'SourceCtx', but inbound adaptors should handle events and results (the target's output):
on command SourceCtx.DoIt {
passes/input/check/adaptor-direction/adaptor-direction.riddl(12:9->34):
Processing for commands should result in sending an event:
on command SourceCtx.DoIt {
passes/input/check/adaptor-direction/adaptor-direction.riddl(22:9->34):
Outbound Adaptor 'BadOutbound' handles Event 'SourceCtx.ItDone' from Context 'SourceCtx', but outbound adaptors should handle commands and queries (the target's input):
on event SourceCtx.ItDone {
34 changes: 34 additions & 0 deletions passes/input/check/adaptor-direction/adaptor-direction.riddl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
domain AdaptorDir is {
context SourceCtx is {
type DoIt is command { data: String }
type ItDone is event { what: String }
} with {
explained as "Source context with message types"
}

context TargetCtx is {
adaptor BadInbound from context SourceCtx is {
handler InHandler is {
on command SourceCtx.DoIt {
prompt "handling command in inbound adaptor"
}
}
} with {
explained as "Inbound adaptor that incorrectly handles commands"
}

adaptor BadOutbound to context SourceCtx is {
handler OutHandler is {
on event SourceCtx.ItDone {
prompt "handling event in outbound adaptor"
}
}
} with {
explained as "Outbound adaptor that incorrectly handles events"
}
} with {
explained as "Target context with adaptors"
}
} with {
explained as "Domain for adaptor direction tests"
}
24 changes: 24 additions & 0 deletions passes/input/check/handler-types/handler-types.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
passes/input/check/handler-types/handler-types.riddl(12:7->27):
Handler 'EventHandler' in Repository 'EventRepo' handles events; repositories typically handle commands and queries, not events:
handler EventHandler is {
passes/input/check/handler-types/handler-types.riddl(21:5->27):
Projector 'CmdProjector' handler does not handle any events; projectors typically handle events to build read models:
projector CmdProjector is {
passes/input/check/handler-types/handler-types.riddl(22:7->22):
Record 'ProjRecord' is unused:
type ProjRecord is record { field1: String }
passes/input/check/handler-types/handler-types.riddl(23:7->25):
Handler 'CmdHandler' in Projector 'CmdProjector' handles commands or queries; projectors typically handle events to build read models:
handler CmdHandler is {
passes/input/check/handler-types/handler-types.riddl(24:9->31):
Processing for commands should result in sending an event:
on command SomeCommand {
passes/input/check/handler-types/handler-types.riddl(5:5->20):
Record 'RecordType' is unused:
type RecordType is record { field1: String }
passes/input/check/handler-types/handler-types.riddl(7:5->25):
Repository 'EventRepo' handlers do not handle any commands or queries; repositories typically handle commands (for mutations) and queries (for reads):
repository EventRepo is {
passes/input/check/handler-types/handler-types.riddl(8:7->24):
Schema 'RepoSchema' should have a description:
schema RepoSchema is flat
36 changes: 36 additions & 0 deletions passes/input/check/handler-types/handler-types.riddl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
domain HandlerTypes is {
context TypeCtx is {
type SomeEvent is event { what: String }
type SomeCommand is command { data: String }
type RecordType is record { field1: String }

repository EventRepo is {
schema RepoSchema is flat
of records as type RecordType
with { briefly as "Repo schema" }

handler EventHandler is {
on event SomeEvent {
prompt "handling event in repo"
}
}
} with {
explained as "Repository that handles events"
}

projector CmdProjector is {
type ProjRecord is record { field1: String }
handler CmdHandler is {
on command SomeCommand {
prompt "handling command in projector"
}
}
} with {
explained as "Projector that handles commands"
}
} with {
explained as "Context for handler type tests"
}
} with {
explained as "Domain for handler type tests"
}
33 changes: 33 additions & 0 deletions passes/input/check/schema-kinds/schema-kinds.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
passes/input/check/schema-kinds/schema-kinds.riddl(7:7->23):
Schema 'FlatMulti' is flat but defines 2 data nodes; flat schemas typically represent a single table or collection:
schema FlatMulti is flat
passes/input/check/schema-kinds/schema-kinds.riddl(7:7->23):
Schema 'FlatMulti' should have a description:
schema FlatMulti is flat
passes/input/check/schema-kinds/schema-kinds.riddl(12:7->23):
Schema 'TSNoIndex' is a time-series schema but has no indices; time-series schemas should index the time dimension:
schema TSNoIndex is time-series
passes/input/check/schema-kinds/schema-kinds.riddl(12:7->23):
Schema 'TSNoIndex' should have a description:
schema TSNoIndex is time-series
passes/input/check/schema-kinds/schema-kinds.riddl(16:7->25):
Schema 'HierNoLinks' is hierarchical with 2 data nodes but has no links; consider adding links to define the tree structure:
schema HierNoLinks is hierarchical
passes/input/check/schema-kinds/schema-kinds.riddl(16:7->25):
Schema 'HierNoLinks' should have a description:
schema HierNoLinks is hierarchical
passes/input/check/schema-kinds/schema-kinds.riddl(21:7->25):
Schema 'StarNoLinks' is a star schema with 2 data nodes but has no links; consider adding links from fact table to dimension tables:
schema StarNoLinks is star
passes/input/check/schema-kinds/schema-kinds.riddl(21:7->25):
Schema 'StarNoLinks' should have a description:
schema StarNoLinks is star
passes/input/check/schema-kinds/schema-kinds.riddl(26:7->26):
Handler 'RepoHandler' in Repository 'SchemaRepo' should have content:
handler RepoHandler is { ??? }
passes/input/check/schema-kinds/schema-kinds.riddl(3:5->18):
Type 'Customer' is unused:
type Customer is { name: String, email: String, age: Number }
passes/input/check/schema-kinds/schema-kinds.riddl(4:5->19):
Type 'OrderInfo' is unused:
type OrderInfo is { orderId: UUID, amount: Number, date: Date }
35 changes: 35 additions & 0 deletions passes/input/check/schema-kinds/schema-kinds.riddl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
domain SchemaKinds is {
context DataCtx is {
type Customer is { name: String, email: String, age: Number }
type OrderInfo is { orderId: UUID, amount: Number, date: Date }

repository SchemaRepo is {
schema FlatMulti is flat
of customers as type Customer
of orders as type OrderInfo
with { briefly as "Flat schema with multiple data nodes" }

schema TSNoIndex is time-series
of metrics as type Customer
with { briefly as "Time-series schema without indices" }

schema HierNoLinks is hierarchical
of parent as type Customer
of child as type OrderInfo
with { briefly as "Hierarchical schema without links" }

schema StarNoLinks is star
of fact as type Customer
of dim as type OrderInfo
with { briefly as "Star schema without links" }

handler RepoHandler is { ??? }
} with {
explained as "Repository for schema kind validation tests"
}
} with {
explained as "Context for schema kind tests"
}
} with {
explained as "Domain for schema kind tests"
}
12 changes: 12 additions & 0 deletions passes/input/check/sink-reach/sink-reach.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
passes/input/check/sink-reach/sink-reach.riddl(12:5->18):
Sink 'Consumer' is a sink but has no upstream path from any source:
sink Consumer is {
passes/input/check/sink-reach/sink-reach.riddl(12:5->18):
Sink 'Consumer' should have a handler:
sink Consumer is {
passes/input/check/sink-reach/sink-reach.riddl(5:5->19):
Flow 'Processor' should have a handler:
flow Processor is {
passes/input/check/sink-reach/sink-reach.riddl(6:7->19):
Inlet 'DataIn' is not connected:
inlet DataIn is type Data
29 changes: 29 additions & 0 deletions passes/input/check/sink-reach/sink-reach.riddl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
domain SinkReach is {
context StreamCtx is {
type Data is String

flow Processor is {
inlet DataIn is type Data
outlet DataOut is type Data
} with {
explained as "A flow processor"
}

sink Consumer is {
inlet ConsumeIn is type Data
} with {
explained as "A data consumer sink"
}

connector FlowToSink is {
from outlet StreamCtx.Processor.DataOut
to inlet StreamCtx.Consumer.ConsumeIn
} with {
explained as "Connect flow output to sink input"
}
} with {
explained as "Context for sink reachability test"
}
} with {
explained as "Domain for sink reachability test"
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,9 @@ class CheckMessagesTest extends AbstractValidatingTest {
"check saga" in { (td: TestData) => checkADirectory("saga", td) }
"check streaming" in { (td: TestData) => checkADirectory("streaming", td) }
"check t0001" in { (td: TestData) => checkADirectory("t0001", td) }
"check schema-kinds" in { (td: TestData) => checkADirectory("schema-kinds", td) }
"check handler-types" in { (td: TestData) => checkADirectory("handler-types", td) }
"check adaptor-direction" in { (td: TestData) => checkADirectory("adaptor-direction", td) }
"check sink-reach" in { (td: TestData) => checkADirectory("sink-reach", td) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,43 @@ trait StreamingValidation(using pc: PlatformContext) extends TypeValidation {
)
}
}

// Check 3: Sink←Source reverse reachability via BFS
val reverseAdjacency = mutable.Map.empty[Streamlet, mutable.Set[Streamlet]]
adjacency.foreach { case (from, toSet) =>
toSet.foreach { to =>
reverseAdjacency.getOrElseUpdate(to, mutable.Set.empty) += from
}
}

val sourceSet = sources.toSet
sinks.foreach { sink =>
if connectedStreamlets.contains(sink) then {
val visited = mutable.Set.empty[Streamlet]
val queue = mutable.Queue.empty[Streamlet]
queue.enqueue(sink)
visited += sink
var reachedBySource = false

while queue.nonEmpty && !reachedBySource do
val current = queue.dequeue()
if sourceSet.contains(current) then
reachedBySource = true
else
reverseAdjacency.getOrElse(current, mutable.Set.empty).foreach { neighbor =>
if !visited.contains(neighbor) then
visited += neighbor
queue.enqueue(neighbor)
}
end while

if !reachedBySource then
messages.addWarning(
sink.errorLoc,
s"${sink.identify} is a sink but has no upstream path from any source"
)
}
}
}
}

Expand Down
Loading
Loading