Four defects found by dogfooding Speckl against the v0.3 product-layer
spec (FederatedMeetupProduct.speckdl, 74 members):
D1 — CRITICAL: List<T> fields in interface records emitted empty type
Root cause: interface record fields called speckTypeToProto(f.type.name)
which returns '' for List/Map/Set types (their .name is undefined).
Event and state fields had inline List/Map dispatch, but interfaces did not.
Fix: extracted typeExprToProto() shared function that handles all TypeExpr
variants (primitive, ident, list, map, set). All four field-emitting code
paths (interfaces, events, state, transitions) now use it.
D2 — MODERATE: Transition type discriminator field silently dropped
Root cause: parser stores the type field in t.typeDiscriminator (string)
rather than in t.fields array. Generator only emitted t.fields.
Fix: emit typeDiscriminator as 'type' field at number 1, offset other
fields by 1 to avoid collision.
D4 — LOW: Enum variant name collisions across enums in same proto scope
Proto3 requires all enum values unique within enclosing scope, not just
the enum. PENDING in Role and PENDING in OrderStatus collided.
Fix: prefix all variants with upperSnake(enumName) — the proto3 convention.
This also matches the handwritten proto's naming (TRANSITION_TYPE_*).
D5 — LOW: Date in interface fields did not trigger timestamp import
The usesTimestamp check only scanned events and state, not interfaces.
Fix: added interface member check to the timestamp import scan.
D3 (deferred): rc_ typo in spec source silently swallowed by parser.
Parser bug, not generator bug. Filed for future cycle.
Verified: 56/56 tests pass, both FederatedMeetup.speckdl and
FederatedMeetupProduct.speckdl compile to protoc-clean output.
The .proto backend previously emitted:
package speckl.<name>.v1;
option go_package = "github.com/sscoble/<name>/proto/.../v1;...v1";
hard-coded. Both fields are now overridable via the SpeckNode's
protoPackage / goPackage fields (populated by top-of-file metadata in
the .speckdl; see upcoming cycle 57 for the related oneof syntax which
will also be reading from the same SpeckNode).
For federated-meetup the overrides are:
proto_package: "federated_meetup.v1"
go_package: "github.com/sscoble/federated-meetup/proto/...;federated_meetupv1"
The consumer Go code (internal/types/types.go, internal/group/) imports
"federated_meetup/v1" — without this override, the generated proto
would emit package "speckl.federated_meetup.v1" and the bindings
would not match the imports, breaking the consuming project's build.
Picks up a half-applied change left in the working tree from the
v0.6 review-v2 session. dist/ is gitignored; tsc build was clean
and all 39 baseline parser tests pass.
Parser fixes:
- Fixed multiline state/init block parsing: trailing commas no longer
treated as continuation markers, merging all state vars into one
- Added splitFields() function for comma-aware emit field splitting
that respects parenthesized groups and quoted strings
- Fixed interface block parsing: enum-style interfaces (name-only
values like 'Idle, Parsing, Parsed, ...') now correctly parsed as
methods instead of being skipped (methods.length was 0)
TypeScript generator fixes:
- String[] array type syntax now correctly maps to string[] in output
- collectIdents filters out names containing brackets
- Duplicate event interfaces deduplicated (implicit emit events
filtered when explicit events already declare them)
- join(X, Y) translates to X + Y (string concatenation) instead of
X.join(Y) which is wrong for string args
- now() generates new Date().toISOString() instead of Date.now()
to match String/string type annotations
- decisionLog type uses proper union: (A | B | C)[] instead of A[]
- String literals protected from this.-prefixing during identifier
replacement (fixes 'leader' inside quotes becoming 'this.leader')
- Removed debug console.log from enum generation
Both TigerBeetleLedger.speck and speckl-compile.speckdl now generate
TypeScript that passes tsc --noEmit with zero errors.
Closes#9
- Add braceDepth tracking to Map/Set type parsing for inline records
- Fix multiline state block joining to preserve type expressions
- Fix multiline object literal parsing in assignments
- Add smart set-difference for Maps vs Sets
- Fix bare notIn for Sets
- Add non-null assertion (!) to Map.get() calls
- Add implicit event interface generation from emit statements
- Protect object literals from this. prefix injection
- check-dist.sh: Prevents DIST DESYNC by verifying all dist/*.js files
are newer than their src/*.ts sources. Fails build if stale.
- Makefile: Standard build targets (build/check-dist/test/recompile/clean)
- src/index.ts: Integrated dist freshness check before compilation
- src/parser.ts: Added List<T> type parsing alongside existing Set<T>/Map<K,V>
(e.g., 'log: List<LogEntry>' now parses as { type: 'list', elementType })
- examples/: Restored v0.3 state machine specs (TigerBeetleLedger,
AccountLedger, ToggleSwitch, TwoPhaseCommit)
- DIST DESYNC is a known recurring anti-pattern — this guard prevents it
Tests: 39/41 pass (2 pre-existing E2E WASM failures due to missing wabt)
Adds a first-class TypeScript code generator to speckl-compile that emits:
- Const enum for workflow states (IssueWorkflowState)
- Strongly-typed class with guards, state transitions, decision log
- Proper this. prefixing (state vars only, not local params)
- Enum references instead of string literals for state comparisons
- Clean tsc --noEmit compilation
Also includes:
- specs/forgejo-issue-workflow.speckdl — full workflow with 9 actions,
8 constraints, 5 temporal verifications
- Generator deduplication of duplicate AST nodes
- Support for init block initialization of state variables
The generator was validated against the Forgejo issue workflow spec,
producing IssueWorkflow.ts that compiles with zero errors.
Refs: heartbeat conversation re: speckl-compile extensibility
- Fixed speck name regex to allow hyphens (\w → [\w-])
- Added provenance { } block handling in parseMember()
- Updated hyphenated name test from 'My' to 'My-Service-Handler'
- All 48 tests passing (27 parser + 21 validator)
- Connected PROV-O, CycloneDX, and SPDX generators to CLI entry point
- Fixed parseAuthor regex to handle angle-bracket email format
- Fixed parseDerives to handle hyphenated names and quoted rationale
- Fixed parseSatisfies to handle hyphenated requirement IDs
- Fixed parseSource to handle ref keyword with quoted value
- Added graceful fallbacks (warn instead of throw) for parse errors
- All 5 example .speck files compile and validate successfully
- CLI entry point with yargs parsing for speckl-compile <file> --output-dir --bom-format
- TypeScript parser with AST types for all SpeckDL primitives
- Generator for PROV-O JSON-LD provenance graphs
- Generator for CycloneDX v1.6 BOMs
- Generator for SPDX 3.0.1 BOMs
- E2E test verifying RetryHandler.speck parsing and all output formats
All tests pass.