[AS-SDK] Phase 5 — @view/@mutating/@payable intent transform - #23
Merged
Conversation
…otigen.toml
Adds the ergonomic top layer for AssemblyScript contracts: authors mark
what an entry point DOES, next to the logic that does it, and an `asc`
plugin holds those markers up against `[functions.*]`.
@view export function __get_impl(): u64 { ... }
@mutating export function __increment_impl(): u64 { ... }
@payable export function __deposit_impl(): void { ... }
It ships inside `@pyde-net/host` on the `/transform` subpath rather than
as a second npm package — one package per language directory, like go/,
and the scaffold already depends on the SDK, so wiring it costs an
asconfig line and no new install.
The manifest stays authoritative. The plugin injects no schema, reads no
[state] / [events], and feeds nothing back into the compile: it collects
the markers, strips them from the AST, and either agrees with the
manifest or fails the build citing both sides and the line each claim
sits on. Annotation is opt-in per function, so an unannotated contract —
every contract written before this — is not checked at all.
Two properties are asserted byte-for-byte in the suite, because they are
the design rather than a nice-to-have:
* Annotating a contract produces identical wasm.
* Deleting the transform from asconfig.json still builds that same
deployable wasm from the Phase 2-4 substrate. Only the cross-check
is lost, which is why an `asc` bump can at worst disable the sugar:
outside the supported range (>=0.27.30, <0.29.0) the plugin warns
and stands down instead of reading an AST it cannot vouch for.
The AST walk is deliberately structural — no `NodeKind`, no
`instanceof`. Both alternatives fail SILENTLY, which is the one failure
mode a drift checker must not have: `NodeKind` is a const enum, so tsc
bakes in one compiler's numeric ids (FunctionDeclaration moved 55 -> 56
between 0.27 and 0.28) and the walk then matches nothing; `instanceof`
goes quietly false whenever the project resolves a different
`assemblyscript` install than the plugin does, which is exactly what a
linked or unhoisted SDK produces. Both were real, both were caught by
building an actual contract rather than by the unit tests, and both now
have regressions: a plain-object AST fixture, and a CI step that re-runs
the suite against the far end of the supported range.
The manifest reader is dependency-free on purpose. It runs inside `asc`
on the build path of every contract that opts in, and a TOML dependency
would put a third-party package inside the trust boundary of a check
whose whole job is to tell the author the truth about their manifest. It
reads the slice of TOML a manifest can express and throws — rather than
guessing — on an `attributes` value it cannot parse, since silently
ignoring one would turn a real mismatch into a pass.
Closes #10
This was referenced Aug 6, 2026
zarah-s
added a commit
that referenced
this pull request
Aug 6, 2026
) Two follow-ups from the #23 review. F1 — a decorator one edit away from a marker (@veiw, @mutatng, @payble) was silently left on the AST and the function it meant to constrain compiled unchecked — the one silent-miss that most undermines the 'checked against otigen.toml' promise. partitionDecorators now takes an optional warn callback and, for a non-marker decorator within one edit (transposition included) of a real marker, tells the author it is being left unchecked and what they likely meant. Purely diagnostic — the wasm is unaffected, and a real marker or an asc built-in (@inline) never trips it. Covered by two collectIntents tests. F2 — the asc dep floor (^0.27.0 / peer >=0.27.0) sat below the range the transform actually enforces (>=0.27.30). A clean install resolving 0.27.0-0.27.29 would stand the transform down while the tests expect it to run. Both the devDep and the peer range now start at 0.27.30 and cap below 0.29.0, matching SUPPORTED_ASC_RANGE. F3 (unconventional manifest shapes) is left as-is: the parser reads only [functions.NAME] headers by design — a tested choice — it is fail-closed with an actionable error, and otigen only ever emits headers. Suite: 31/31 on asc 0.27.37 and 0.28.20.
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.
Closes #10.
Adds the hybrid's ergonomic top layer: authors mark what an entry point does, next to the logic that does it, and an
ascplugin holds those markers up against[functions.*]inotigen.toml.Where it lives
The issue called for a separate
assemblyscript-transform/package scoped@pyde-net/asc-transform. It ships instead inside@pyde-net/host, on the/transformsubpath — one package per language directory, matching thego/consolidation in #18. Two things fall out of that: the scaffold already depends on the SDK, so wiring it costs oneasconfig.jsonline and no second install; and there is no publish-ordering dance between two packages that must move together.{ "options": { "transform": ["@pyde-net/host/transform"] } }The manifest stays authoritative
The plugin injects no schema, reads no
[state]/[events], and feeds nothing back into the compile. It collects the markers, strips them from the AST, and either agrees with the manifest or fails the build:Annotation is opt-in per function, so an unannotated contract — every contract written before this — is not checked at all.
attributes@viewview@mutatingview@payablepayable@entryentry@payableis the one symmetric rule: on an annotated function, saying nothing about value reads as "this entry refuses value" — a safety claim the manifest may not be making.Acceptance criteria
otigen build; the ABI is untouched because it never comes from the markers.@viewon a fn the manifest marks mutating failsotigen build(exit 2) citing both sides — verified on a freshly-generated scaffold, not just in tests.asconfig.jsonstill yields a correct, deployable contract — asserted byte-for-byte: annotated-with-plugin and unannotated-without both produce634b5a85…onexamples/counter-as.[functions.*]viaotigen build, which this plugin cannot influence in either direction.Two bugs worth reading
Both were found by building a real contract, not by the unit tests, and both fail silently — the worst failure mode a drift checker can have, because every mismatch sails through unchecked:
NodeKindis aconst enum. tsc inlines one compiler's numeric ids at build time;FunctionDeclarationmoved 55 → 56 between asc 0.27 and 0.28, so the walk matched nothing.instanceofassumes a singleassemblyscriptinstall. It goes quietly false whenever the project resolves a different copy than the plugin does — exactly what a linked or unhoisted SDK produces, which is what this repo's own examples use.The walk is now structural: no node kinds, no class identity. Regressions for both: a plain-object AST fixture that is not an
assemblyscriptinstance and carries nokindids, plus a CI step that re-runs the suite against the far end of the supported range.Version policy
Supported:
asc >= 0.27.30, < 0.29.0, checked against the compiler actually running the build. Outside it the plugin warns and stands down rather than reading an AST it cannot vouch for — anascbump can cost the sugar, never the substrate.Notes for review
ascon the build path of every opted-in contract; a TOML dependency would put a third-party package inside the trust boundary of a check whose whole job is to tell the author the truth about their manifest. It throws — rather than guessing — on anattributesvalue it cannot parse, since silently ignoring one turns a real mismatch into a pass.assemblyscript/package.jsongains"type": "module"and anexportsmap.ascresolves AS sources by file path and never consultsexports, so@pyde-net/host/assembly/*is unaffected; the map exists for Node, which is what loads the plugin.Follow-ups
^0.1.0-alpha.15, so this must publish first — npm is on alpha.14.pyde-net/otigen-templatesholds the actualotigen init --lang ascontract starter and needs the same wiring; it also carries stale/assemblyhost-fn imports that no longer compile against alpha.14, independent of this work.