docs(lez): Extensions section with admin-authority and extension-library guides - #355
docs(lez): Extensions section with admin-authority and extension-library guides#355mmlado wants to merge 13 commits into
Conversation
…library guides
Two new pages under docs/lez/extensions/:
- admin-authority.md: integration guide for app developers. Covers
annotation, gating, AdminCandidate (Signer / PDA), initialization
window, transfer, renounce, verification, security notes.
- build-a-spel-extension-library.md: author guide for the generic
[package.metadata.spel] discovery mechanism. Covers two-crate
layout, metadata fields, marker / gate macros, multi-extension
stacking, troubleshooting.
SUMMARY.md updated with new section.
Relates to: RFP-001 (logos-co/rfp#46)
Gate attributes are ordinary proc-macros that re-expand on the emitted handler and remove themselves, so a library does not declare them for stripping. Update the build-a-spel-extension-library guide to match. Remove the instruction_attrs field from the metadata example and the paragraph that described stripping.
Extension library guide: remove the leftover instruction_attrs references (mechanism removed), rewrite the gate attribute section to the real pattern (re-expansion body injection, param names from attribute args, never touch #[account]), and document the [[package.metadata.spel.inject]] block for gate account params. Admin-authority guide: admin_initialize self-elects the caller and takes no candidate argument, AdminCandidate moved to the transfer section, gate params are injected rather than required, CLI examples switched to the real syntax, deploy-time bundling corrected as not possible on LEZ today.
The admin guide fixes the gate kwargs to the inject account names and gains the embedded mode section. The author guide gains auto-wrap, embedded mode with bound args, the kwarg contract with the alignment self-test, marker-order and conflict rules, and the corrected discovery scope: git and registry deps are discovered, and a present marker with failed resolution refuses to compile.
|
@mmlado is this ready for review? does it need updating for v0.2? |
|
@kashepavadan It's still in draft. It's part of the milestone 3 for logos-co/rfp#46 and logos-co/rfp#47. I'm still working on the milestone with the testing. I left it in draft so it can be review, but it'll be ready to merge only after the review process has been done for the RFPs. |
The admin guide's embedded section showed a manual bootstrap_at call. The example now marks the slot field with #[admin_slot] and the creating instruction with #[admin_initialize], and the notes cover the injected bootstrap, the derived offset const, and the layout agreement check. The extension-library guide gains the author side of the same surface: the slot marker name derived from the role, and the bootstrap attribute pattern for slots that must never exist uninitialized.
The site switched from GitBook to docusaurus. The extensions section gains its category file, both pages gain the standard frontmatter, and the GitBook hint blocks become admonitions.
The transfer commands now take --new-account and --candidate. Both CLI examples updated to match the shipped surface.
British spellings where the house style asks for them, abbreviations spelled out (parameters, attributes, functions), and the flagged anthropomorphisms reworded. Code blocks untouched.
9cb2b1a to
b4854e3
Compare
danisharora099
left a comment
There was a problem hiding this comment.
I ran the packet rather than just reading it. The content is faithful to the shipped code: the IDL surfaces, injection behavior, exempt semantics and embedded diagnostics all matched when I executed the guides against correct pins, and the site builds clean. But a fresh developer following the text as written cannot get past the first build:
- The dependency snippets are unrunnable:
spel-admin-authoritywith no branch resolves to an empty main (README and LICENSE, no crate), andspel-frameworkpointing at logos-co/spel resolves to a main with no extension support (it only exists on the fork until logos-co/spel#233 lands). Either pin branch/rev in the snippets, or gate this PR on merging the library m3 branches to main and landing logos-co/spel#233. I'd prefer the latter, it fixes the cause. - Missing prerequisites that stop a
cargo newuser: serde and lee_core in the consumer deps (the guide lists two crates, the samples need five), the fn main collision with the generated main, and thecargo fetchstep (the scanner's cargo metadata --offline call deterministically fails for fresh git consumers without it). - Smaller: the "library README's dependency table" claim is false for the admin repo; the
.write_tohelper in the embedded snippet is hand-written in the sample, not provided, so show it; and add a caveat that a marker matching nothing is currently dropped silently when resolution is healthy.
The dependency snippet lists all five crates the reference samples need, with a note on what nssa_core, borsh and serde are for. A fresh cargo new consumer learns to delete the default fn main, the macro generates the entry point, and to run cargo fetch once so the scanner's offline metadata call can resolve git dependencies. The embedded walkthrough shows the hand-written write_to helper, and the README reference no longer claims a dependency table.
The embedded metadata example gains the mandatory state_type key and the overlap assert it powers, the bound args section states the trailing position rule, and the troubleshooting sections describe the unmatched marker hard error instead of a silently missing surface. The admin guide walks the co-sign exchange for Signer transfers, says a candidate PDA must be a claimed account rather than deployed, and points the missing-trio checklist at the hard error.
|
This packet is written against the released end state and lands last, after the framework enabler and the library lineage reach their release. That is the gating option, and it is why the dependency snippets stay unpinned, they become correct the moment the release they describe exists. Until then all four repos carry an m3-settle tag on the reviewed settle revs, for anyone who wants to run the guides against a reproducible line today. The content items are in on the branch, fde4a30 and fe8409d:
|
weboko
left a comment
There was a problem hiding this comment.
Dogfooded both pages end-to-end on a clean Ubuntu 24.04 container (Rust 1.94.1), building a real consumer program from scratch and running every command.
The pages are blocked at step one, and the second failure is silent.
cargo fetchhard-fails immediately:admin-authorityis unpinned and its default branchmainholds only a README and licences. The crate lives onm3/ tagm3-settle.- After pinning that, the build succeeds — but produces a program with no admin instructions at all, because
spel-frameworkpoints atlogos-co/spelmain(v0.6.0), which does not carry the extension scanner these pages are built on. That scanner is still unmerged in logos-co/spel#233.
The second one matters most because both pages promise it cannot happen: "a marker that matches no discoverable extension is a hard compile error." I verified that guarantee holds on the scanner-carrying revision and does not hold on logos-co/spel main, where a bogus marker compiles clean in 3.13s.
With the pins corrected to what admin-authority's own CI fixture uses, everything works and matches the docs:
$ spel generate-idl src/main.rs | jq -r '.instructions[].name'
create_pool
admin_initialize
admin_transfer
admin_renounce
...and the compiled binary embeds all three. So the content is sound — the technical claims I spot-checked in the author guide (read_inject_specs, wrap_instructions keys, inject/embedded/bound_args metadata shapes) all match the shipped code. It's the dependency pointers and one over-broad safety claim that need fixing.
Inline comments have suggested changes for each. Two things for you to decide rather than for me to patch:
- Shipping order. Both corrected pins point at a personal fork (
mmlado/spel) and an unmerged branch. It may be cleaner to hold these pages until logos-co/spel#233 lands and the library has a tagged release underlogos-co; otherwise the draft banner should say plainly that the page tracks unreleased code on a fork. spelCLI prerequisites. The pages use the CLI but never say how to install it, and the CLI's own framework revision silently determines whethergenerate-idlreports the extension instructions. That deserves a short prerequisites section (details on line 122 and 241).
| admin-authority = { git = "https://github.com/mmlado/spel-admin-authority" } | ||
| spel-framework = { git = "https://github.com/logos-co/spel" } |
There was a problem hiding this comment.
Blocker — this dependency block cannot resolve. cargo fetch fails on the first step.
I followed this page end-to-end on a clean Ubuntu 24.04 box with Rust 1.94.1. Copying this block verbatim and running the cargo fetch from the next paragraph gives:
error: failed to get `admin-authority` as a dependency of package `my_program`
Caused by:
Unable to update https://github.com/mmlado/spel-admin-authority
Caused by:
Could not find Cargo.toml in `/root/.cargo/git/checkouts/spel-admin-authority-7dd198e322803bb1/2cd79b3`
Two separate problems:
-
admin-authorityis unpinned, so it resolves to that repo's default branchmain— which contains onlyREADME.mdand two LICENSE files. There is no crate there at all. The library actually lives onm3(also taggedm3-settle) andfeat/derived_embedding. -
spel-frameworkpoints atlogos-co/spel, whosemainis v0.6.0 and does not contain the extension scanner this page depends on. There is nospel-framework-core/src/extension.rson that branch — the scanner is still unmerged in RFP-001 framework enabler: generic[package.metadata.spel]extension scanner spel#233.admin-authorityitself pinsmmlado/spel.gitreva0dfcd85for exactly this reason (seeadmin-authority/Cargo.tomlandci/consumer-fixture/guest/Cargo.tomlonm3).
Problem 2 is the dangerous one, because it does not fail loudly. With logos-co/spel as the framework, the program compiles cleanly and you silently get a binary with no admin instructions — see my comments on lines 241 and 252.
With the two pins below, I got a clean build and all three admin instructions in the IDL and in the compiled binary:
| admin-authority = { git = "https://github.com/mmlado/spel-admin-authority" } | |
| spel-framework = { git = "https://github.com/logos-co/spel" } | |
| admin-authority = { git = "https://github.com/mmlado/spel-admin-authority", branch = "m3" } | |
| spel-framework = { git = "https://github.com/mmlado/spel.git", rev = "a0dfcd85af7dfdb7ecb8117474d39e85415afc96" } |
Since both pins point at a personal fork and an unmerged branch, it's worth deciding whether this page should ship before logos-co/spel#233 lands and the library gets a release tag on logos-co. If it ships now, the draft banner should say explicitly that it tracks unreleased code.
|
|
||
| ```rust | ||
| use spel_framework::prelude::*; | ||
| use admin_authority::{admin_authority, require_admin}; |
There was a problem hiding this comment.
Importing the marker produces an unused-import warning. #[admin_authority] is matched by attribute name only — the framework never resolves it as a path, so nothing needs to be in scope for it. Building this snippet as written gives:
warning: unused imports: `admin_authority` and `require_admin`
--> src/main.rs:2:23
The CI-verified reference program (admin-authority-sample/src/main.rs on m3) imports only the gate:
use admin_authority::require_admin;
use spel_framework::prelude::*;| use admin_authority::{admin_authority, require_admin}; | |
| use admin_authority::require_admin; |
(require_admin is unused in this particular snippet too, since create_pool isn't gated — it becomes used at the "Gate an instruction" section below. Worth either dropping it here as well or adding #[require_admin] to the example so the import earns its place.)
| `admin_initialize` takes no arguments. The signing caller becomes the admin (self-election). There is no candidate argument at initialize because the LEZ duplicate-account rule rejects a transaction listing the same account twice, so a caller could never also pass itself as candidate evidence. | ||
|
|
||
| ```bash | ||
| spel --idl program-idl.json --program <program-id> -- \ |
There was a problem hiding this comment.
This is the first use of the spel CLI, but the page never says how to get it, and there is no prerequisites section pointing anywhere that does. Following the page end-to-end stalls here.
For what it's worth, building it from source is not a one-liner in a clean environment — cargo build --manifest-path spel-cli/Cargo.toml pulls logos-blockchain-circuits-pol-sys, whose build script downloads a prebuilt circuits artifact and panics if that download fails. Also note the package is named spel, not spel-cli, so cargo build -p spel-cli fails with package ID specification 'spel-cli' did not match any packages.
A short "Prerequisites" section before "Add the dependency" would fix this — Rust toolchain, how to install/build spel, and (per my comment on line 241) which framework revision the CLI must be built from.
| After building your program, check that the admin instructions appear in the IDL: | ||
|
|
||
| ```bash | ||
| spel generate-idl path/to/your/program/src/main.rs | jq '.instructions[].name' |
There was a problem hiding this comment.
This verification step silently under-reports unless the spel CLI itself was built from a revision carrying the extension scanner. generate-idl resolves extensions in the CLI, not in your program, so the CLI's framework revision decides what you see.
I ran this against one identical consumer program with two CLIs:
# spel built from logos-co/spel main (v0.6.0)
create_pool
# spel built from mmlado/spel rev a0dfcd85 (has the extension scanner)
create_pool
admin_initialize
admin_transfer
admin_renounce
Same source file, same Cargo.toml. The first CLI reports success and just omits the trio — there is no warning and no non-zero exit, so the "Expected output" below reads as a passed check when it actually failed.
Worth stating explicitly here which build of spel this step requires, e.g.:
| spel generate-idl path/to/your/program/src/main.rs | jq '.instructions[].name' | |
| spel generate-idl path/to/your/program/src/main.rs | jq '.instructions[].name' |
...with a following note: the spel binary must be built from the same framework revision pinned in your Cargo.toml; a CLI built without the extension scanner omits the admin instructions from this output without reporting an error.
| "admin_renounce" | ||
| ``` | ||
|
|
||
| Plus your own instructions. A marker that matches no discoverable extension is a hard compile error naming the marker, so a broken setup refuses loudly rather than building without the trio. When you hit that error, the most common causes are: |
There was a problem hiding this comment.
This safety guarantee does not hold for the dependency set this page tells you to use. It's true only when the framework build carries the extension scanner.
I tested both ways with a deliberately bogus marker (#[no_such_extension]) on a #[lez_program] module:
- Framework =
mmlado/spelreva0dfcd85(has the scanner) → hard error, exactly as promised:error: marker(s) ["no_such_extension"] matched no extension in the program's direct dependencies... - Framework =
logos-co/spelmain (what line 39 specifies) →Finished dev profile ... in 3.13s. Clean build, no warning, no error.
And with the real #[admin_authority] marker against logos-co/spel main, the program builds and generate-idl shows only create_pool — precisely the "building without the trio" outcome this sentence promises is impossible.
Suggest scoping the claim so a reader on the wrong framework revision isn't told their silent failure can't happen:
| Plus your own instructions. A marker that matches no discoverable extension is a hard compile error naming the marker, so a broken setup refuses loudly rather than building without the trio. When you hit that error, the most common causes are: | |
| Plus your own instructions. On a framework build that carries the extension scanner, a marker that matches no discoverable extension is a hard compile error naming the marker, so a broken setup refuses loudly rather than building without the trio. Note that this safety net is itself a property of the pinned framework revision: on a framework without the scanner, the marker is ignored and the program builds silently without the trio. When you hit that error, the most common causes are: |
| ```toml | ||
| [dependencies] | ||
| my-extension = { git = "https://github.com/you/my-extension" } | ||
| spel-framework = { git = "https://github.com/logos-co/spel" } |
There was a problem hiding this comment.
Same issue as admin-authority.md line 39: logos-co/spel main (v0.6.0) does not contain the extension scanner described throughout this page — there is no spel-framework-core/src/extension.rs on that branch, and extension_attr appears nowhere in the tree. A consumer who copies this line gets a program where the marker is silently ignored.
Everything else I spot-checked on this page matches the shipped code on the scanner-carrying revision: read_inject_specs(crate_dir: &Path) exists at spel_framework_core::extension::read_inject_specs (line 178), and the wrap_instructions keys wrapper / skip / self_exempt_marker / exempt (lines 202-207), the [[package.metadata.spel.inject]] shape, embedded.skip / state_type, and [[package.metadata.spel.bound_args]] all match admin-authority/Cargo.toml and the framework's metadata reader. So it's only the dependency pointer that's wrong.
| spel-framework = { git = "https://github.com/logos-co/spel" } | |
| spel-framework = { git = "https://github.com/mmlado/spel.git", rev = "a0dfcd85af7dfdb7ecb8117474d39e85415afc96" } |
Swap this for the logos-co URL once logos-co/spel#233 merges.
| spel generate-idl path/to/sample/src/main.rs | ||
| ``` | ||
|
|
||
| The IDL should contain your extension's instructions alongside the consumer's own. A marker that matches no discoverable extension is a hard compile error naming the marker, regardless of why it did not match, so a broken setup refuses loudly instead of building a program silently missing its extension surface. When you hit that error, the most common causes are: |
There was a problem hiding this comment.
Same qualification as admin-authority.md line 252 — this "refuses loudly" guarantee is a property of the framework revision, not of the mechanism. Verified with a bogus marker: hard compile error on mmlado/spel rev a0dfcd85, clean successful build on logos-co/spel main (which line 247 currently points readers at).
Given this page is the canonical author guide, it's worth being explicit that the fail-closed behaviour requires a framework build carrying the scanner, so an author debugging a missing extension surface checks their framework pin before their metadata.
Summary
Adds two pages under a new
docs/lez/extensions/folder, plus the section's_category_.json:admin-authority.md, integration guide for app developers. Covers annotation, gating, AdminCandidate (Signer / PDA), initialization window risk, transfer, renounce, IDL verification, security notes, and embedded mode (the admin slot inside a consumer account via#[admin_slot]and#[admin_initialize]).build-a-spel-extension-library.md, author guide for the generic[package.metadata.spel]discovery mechanism. Covers two-crate layout, metadata fields, marker / gate macros, multi-extension stacking, auto-wrap, embedded mode with bound args and slot markers, common troubleshooting.Context
These pages document the user-facing surface of RFP-001 (admin-authority library) and the framework mechanism it relies on. The framework mechanism, a generic
[package.metadata.spel]extension scanner inspel-framework-core::idl_gen, is delivered in logos-co/spel#233. These docs are the consumer-facing complement.Future extension libraries (e.g. RFP-002 freeze-authority) can ship without any framework change, so the "Build a SPEL extension library" page is written generically to serve as the canonical author guide for any future extension.
Why a new
extensions/folderlez/already groups capabilities by folder (get-started/,transfer-tokens/). Extension libraries are a new capability class. Placing both pages underlez/extensions/mirrors the existing structure and keeps consumer and author guides discoverable side-by-side.Companion repositories
mmlado/spel-admin-authority: the admin-authority library implementation referenced byadmin-authority.md.[package.metadata.spel]extension scanner spel#233: the framework PR carrying the generic extension scanner referenced by both guides.Open questions for maintainers
extensions/for brevity. Alternatives:extension-libraries/,program-extensions/,access-control/(too narrow for the generic guide).lez/. The author guide could arguably sit underbuild-an-app/instead. Open to splitting if you prefer.lez/pages yet, happy to add.Style notes
Pages carry the standard docusaurus frontmatter and use admonitions for the draft-status banner and warnings. Prose follows the house British English style checked by Vale.
Relates to
[package.metadata.spel]extension scanner spel#233