feat(fiber): offline DefinitionLinter for authoring safety#195
Open
ottobot-ai wants to merge 2 commits into
Open
feat(fiber): offline DefinitionLinter for authoring safety#195ottobot-ai wants to merge 2 commits into
ottobot-ai wants to merge 2 commits into
Conversation
Proposal 01 (fiber-ergonomics/01-authoring-safety): a pure, advisory-only
validator over a StateMachineDefinition. Resolves every {"var":...} path
against the context roots ContextProvider injects, rejects misspelled
_-directive keys (the F4 killer), checks reachability, checks read/write
conformance (shape-based or internal-consistency), and surfaces the existing
expression-depth cap -- as structured, source-located Diagnostics.
Vocabulary (directives + roots) is read from ReservedKeys so the linter can
never disagree with the runtime. PURE: no F[_], no CalculatedState, no
network. NOT a consensus surface -- never called from validateSignedUpdate or
a combiner (CLAUDE.md rules #2/#3).
Test: all 9 shipped riverdale *.definition.json lint clean of Errors, plus
synthetic cases for unknown-directive / undeclared-state-read /
unreachable-state / shape-based errors. 14/14 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt
Adds the advisory static recipient-shape check to DefinitionLinter, the
offline shift-left for the chain's object-form-only `_transferAsset` recipient
(EffectExtractor now raises CombineRejected at combine on anything else).
The resolved recipient value is checked at combine (it's usually dynamic);
this catches the STATIC shape offline: a literal bare-string recipient (the
removed legacy form) is a Warning, a literal object that can never decode to
an AssetHolder (wrong/typo'd variant, or a Fiber/Wallet wrapper missing its
inner field) is an Error, and a dynamic {"var":..}/computed recipient is left
to the combiner. Advisory only — no hard registration gate (the maintainer-
chosen posture). +3 suite cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt
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.
Implements Proposal 01 / P1 of the fiber-ergonomics program (
docs/proposals/fiber-ergonomics/01-authoring-safety.md, RFCs in #192): a pure, offline, advisory-only validator that catches the hand-rolled-JSON-Logic footguns (F4/F5/F10) before a definition is signed and the cluster sees it — instead of misbehaving silently at combine.What it is
DefinitionLinter.validate(definition, shape: Option[MachineShape] = None): List[Diagnostic]— pure (noF[_], noCalculatedState, no network), returning source-locatedDiagnostics (Severity = Error | Warning | Info, stablecode,Location{transitionIndex, field, path}). Empty == clean.It is tooling, never a consensus surface — never called from
validateSignedUpdateor any combiner (CLAUDE.md #2/#3). It reads its vocabulary (the_-directive set and the context roots) straight fromReservedKeys— the same constantsEffectExtractor/ContextProviderdispatch on — so it cannot drift from the runtime.Checks (RFC §2 a–e)
{"var":…}first segment resolved against the engine's actual roots →unknown-rootError /undeclared-state-read(Error with a shape, Warning without) /undeclared-event-read/undeclared-dep-read(amachines.<uuid>not independencies)._-prefixed effect key outside the recognizedReservedKeysset ⇒unknown-directiveError (with nearest-directive suggestion); a non-_key one edit from a directive ⇒likely-dropped-underscoreWarning.initialState⇒unreachable-stateError /dead-end-stateWarning.MachineShapeif supplied, else internal-consistency (a field written by any transition is known for reads).FiberRules.definitionExpressionsWithinDepthLimits⇒expression-too-deep. (Gas estimation left out — the RFC marks it optional.)Tests — 14/14
DefinitionLinterSuitereads all 9 shippedriverdale-economy/*.definition.jsonand asserts each lints clean (no Errors), plus an embedded copy so the property holds without file access; and asserts the diagnostics fire: a typo'd_triger⇒unknown-directive, a never-writtenstate.Xread ⇒undeclared-state-read, an unreachable state ⇒unreachable-state, and shape-based undeclared read/write ⇒ Errors.sharedData/Test/compile+scalafmtCheckAllclean.Follow-ups (noted, not in scope)
A TypeScript mirror for the e2e runner / SDK editor-time checks, and wiring the dry-run into the runner pre-send, are the natural next step (the SDK template work is #227).
🤖 Generated with Claude Code