Skip to content

docs(lez): Extensions section with admin-authority and extension-library guides - #355

Open
mmlado wants to merge 58 commits into
logos-co:mainfrom
mmlado:docs/admin-authority-rfp-001
Open

docs(lez): Extensions section with admin-authority and extension-library guides#355
mmlado wants to merge 58 commits into
logos-co:mainfrom
mmlado:docs/admin-authority-rfp-001

Conversation

@mmlado

@mmlado mmlado commented Jun 24, 2026

Copy link
Copy Markdown

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 in spel-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/ folder

lez/ already groups capabilities by folder (get-started/, transfer-tokens/). Extension libraries are a new capability class. Placing both pages under lez/extensions/ mirrors the existing structure and keeps consumer and author guides discoverable side-by-side.

Companion repositories

Open questions for maintainers

  1. Folder name. Used extensions/ for brevity. Alternatives: extension-libraries/, program-extensions/, access-control/ (too narrow for the generic guide).
  2. Page placement. Both under lez/. The author guide could arguably sit under build-an-app/ instead. Open to splitting if you prefer.
  3. Cross-linking. The pages reference each other directly. No links from the broader 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

mmlado added 5 commits June 24, 2026 17:16
…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.
@kashepavadan

Copy link
Copy Markdown
Contributor

@mmlado is this ready for review? does it need updating for v0.2?

@mmlado

mmlado commented Aug 3, 2026

Copy link
Copy Markdown
Author

@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.

mmlado added 9 commits August 4, 2026 06:50
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.
End-to-end usage guide for the freeze-authority extension: auto vs
manual mode, seven management instructions, lifecycle (including
recoverable renounce), per-account freeze, PDA-as-authority via CPI,
F3 carve-outs.

Extends build-a-spel-extension-library.md with three sections
motivated by shipping a second extension: attribute-order convention
for libs that re-export the #[instruction] shim, the optional
wrap_instructions metadata for module-wide gate application, and
the hard-dep composition pattern (freeze on top of admin).

SUMMARY.md picks up the new page.
freeze_initialize self-elects the admin and takes no candidate, every
CLI block uses the real invocation form, the candidate enum moves to
the transfer section where it applies, and the embedded mode section
covers the shared-account layout, born-vacant slots, and splice-only
writes.
The transfer commands now take --new-account and --candidate. Both CLI
examples updated to match the shipped surface.
The struct shows #[admin_slot] and #[freeze_slot] and the notes cover
the #[admin_initialize] bootstrap next door and the layout agreement
check the markers add.
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.
@mmlado
mmlado force-pushed the docs/admin-authority-rfp-001 branch from 9cb2b1a to b4854e3 Compare August 4, 2026 07:16
mmlado added 4 commits August 4, 2026 09:18
Frontmatter and admonitions, matching the admin and library pages.
The admin branch's M2.5 alignment and the freeze branch each carried
an auto-wrap section, and the merge kept both. One section survives at
the earlier position with the richer body, the activation walk-through,
and the composition note.
British spellings, abbreviations spelled out, and the flagged
anthropomorphism reworded on the freeze page.

@danisharora099 danisharora099 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. The dependency snippets are unrunnable: spel-admin-authority with no branch resolves to an empty main (README and LICENSE, no crate), and spel-framework pointing 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.
  2. Missing prerequisites that stop a cargo new user: 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 the cargo fetch step (the scanner's cargo metadata --offline call deterministically fails for fresh git consumers without it).
  3. Smaller: the "library README's dependency table" claim is false for the admin repo; the .write_to helper 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.

mmlado added 6 commits August 10, 2026 12:13
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.
FreezeCandidate joins both import snippets, the generated dispatcher
references it. The Pda variant's program_id field is a ProgramId, not
an AccountId.
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.
The frozen-state callable list includes the per-account freeze edits
and consumer exemptions, the consumer prerequisites match the admin
guide (six crates, cargo fetch, no default fn main), and the two
inits go back to back because a LEZ transaction carries a single
instruction. The freeze-account target is documented as hex, releasing
a target that is not frozen documents its refusal, Signer transfers
walk the co-sign exchange, and the candidate Pda field says ProgramId.
@mmlado

mmlado commented Aug 10, 2026

Copy link
Copy Markdown
Author

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:

  • The dependency snippet lists all five crates the samples need, with a sentence on what nssa_core, borsh and serde are for.
  • The cargo fetch step is documented with its reason, the scanner's offline metadata call.
  • A cargo new consumer learns to delete the default fn main.
  • The README dependency table claim is gone.
  • The hand-written write_to helper is shown next to the embedded snippet.
  • The silent-drop caveat dissolved upstream: an unmatched marker is now a hard compile error regardless of resolution state, and the troubleshooting sections document that error instead.
  • fe8409d. Notable corrections: the embedded metadata example gains the now mandatory state_type key, the bound args trailing rule is stated, Signer transfers walk the co-sign exchange, and a candidate PDA is a claimed account, not a deployed one.

@weboko

weboko commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

CI has not run on this branch since 2026-08-31 — the last three heads are stuck awaiting workflow approval

Not a re-dogfood, and here is why. The head moved from a44384f to dfaa7e2c, but that commit is a merge of main and touches only AGENTS.md, docs/basecamp/swap-eth-and-lez-tokens-in-logos-basecamp.md, docusaurus.config.ts and sidebars.ts — none of this PR's five files. The three extension pages are byte-identical to the ones I ran end to end on 2026-09-01, so re-running the toolchain would reproduce that pass exactly. The five inline findings from it still stand as written.

What is new is that the checks quietly stopped running:

head Build Check Vale
6363393 (08-31 08:31) success failure
f95d112 (08-31 14:34) success failure
6262364 (08-31 15:03) action_required action_required
a44384f (09-01 08:07) action_required action_required
dfaa7e2 (09-01 13:40) action_required action_required

So 8a732b9 ("clear the warning-level prose alerts"), which was the fix for that red Vale run, has never been verified by CI — and neither has the merge. The PR sits at pending with no check result to read, which is easy to misread as "checks still going". Someone with write access needs to hit Approve and run on the queued workflow runs for this branch.

I ran both checks locally on dfaa7e2 in the meantime, so that approval should come back green:

  • Buildnpm ci && npm run build gives [SUCCESS] Generated static files in "build". The merge did not break the new pages against main's updated sidebars.ts and redirect config, which was the one real risk in pulling main in.
  • Vale 3.11.1 (the version .github/workflows/vale.yml pins) over docs/lez/extensions/0 error-level alerts. 18 warnings and 174 suggestions remain, none of which block, since the action runs filter_mode: added with fail_on_error: true and only errors on added lines fail the job. 8a732b9 did its job.

Nothing here changes the review state: the open items are still the five inline comments from 2026-09-01, the spel init ones in particular.

@weboko

weboko commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Follow-up: I approved the queued runs, and the Vale failure is a CI bug, not a prose problem

I approved the two stuck workflow runs on dfaa7e2. Results:

  • Build Check — success.
  • Vale — failure. I said in my previous comment that this should come back green. It did not, and the reason turns out to matter more than the prediction: the Vale job on this branch has never once failed because of prose.

What actually fails

##[error]reviewdog: Too many results (annotations) in diff.
##[error]Vale and reviewdog exited with status code: 1

The job runs vale --output=JSON docs and .vale.ini sets MinAlertLevel = suggestion, so every suggestion and warning in the tree is emitted — 1742 of them across docs/ on this head. filter_mode: added narrows that to the lines this PR adds, which is still 192 annotations. reviewdog posts those as GitHub logging-command annotations, hits GitHub's cap, errors, and exits 1. The action reports that non-zero exit as a failed check.

I pulled the logs for all three failing Vale runs on this branch. Same error every time, and the severity histogram is the giveaway:

run head annotations ERROR WARNING INFO why it failed
33373313460 6363393 194 0 46 342 too many annotations
33403437242 f95d112 194 0 46 342 too many annotations
33514807536 dfaa7e2 192 0 18 174 too many annotations

Zero error-level alerts in all three. Per AGENTS.md — "only error-level alerts on lines you added block the merge; warnings and suggestions never do" — every one of these runs should have been green.

@mmlado, this means 8a732b9 ("clear the warning-level prose alerts") was chasing a phantom. It did what it says (46 warnings → 18) but the check was never going to pass, because 192 annotations is still over the cap. Sorry — that one is on the CI config, not on you, and I should have caught it before you spent a commit on it.

The fix, verified both ways

Cap what Vale emits in CI to the level the policy actually enforces. In .github/workflows/vale.yml:

       - uses: errata-ai/vale-action@reviewdog
         with:
           version: 3.11.1
           files: docs
+          vale_flags: "--minAlertLevel=error"
           reporter: github-pr-check
           filter_mode: added
           fail_on_error: true

--minAlertLevel=error is a first-class Vale flag (vale --help lists it with that exact spelling). I ran both sides on this PR's head:

  • vale --minAlertLevel=error --output=JSON docs0 alerts across the whole tree, so reviewdog gets nothing to post, stays under the cap, and the check goes green.
  • The gate still bites. Appending one line with mispeling, color and spelled to admin-authority.md and re-running at the same level: Logos.SpellingGB and Logos.BritishSpelling both fire. A real error still fails the build.

This keeps .vale.ini at MinAlertLevel = suggestion, so the local vale <changed files> workflow that AGENTS.md describes still shows contributors the full picture. Only CI narrows to the blocking level.

Worth noting this is repo-wide, not specific to this PR: any PR that adds enough prose will trip the same cap. It just bit here first because this branch adds ~1065 lines of docs. Since .github/workflows/vale.yml is not in this PR's diff I could not leave it as an inline suggestion — it probably wants its own small PR, and this branch should not have to carry it.

Review state unchanged

Still not a re-dogfood: the three extension pages are byte-identical to a44384f. The five inline findings from 2026-09-01 are the open items, the two spel init ones in particular.

The flags have to precede the project name. The parser stops
reading flags at the first argument that is not one, takes it as
the name, and ignores the rest, so the documented invocation
scaffolded the default pin without saying so. The guest binary is
snake-cased from the project name. Getting the pin wrong is not
silent either: the default resolves a second lee_core alongside
the one admin-authority pins, and the guest fails with type errors
out of the lez_program expansion, so the page no longer promises a
quiet failure it does not produce.

The dependency block names the manifest it belongs in, the
scaffold's root is a workspace that excludes methods/guest. The
co-signing exchange merged upstream after the pinned revision, so
both pages describe it as arriving with the pin rather than as
still in review.
@mmlado

mmlado commented Sep 2, 2026

Copy link
Copy Markdown
Author

Agreed on all of it, and vale_flags: "--minAlertLevel=error" is the better spelling of the fix, it stops the annotations being produced instead of stopping them being reported. For 8a732b9, the warnings it cleared were worth clearing on their own terms even though they were never what failed the check. Agreed the workflow change wants its own PR rather than riding this branch.

@kashepavadan kashepavadan mentioned this pull request Sep 2, 2026

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dogfooding run: built all three pages end to end

Clean Ubuntu 24.04, rustc 1.94.1, nothing pre-installed beyond the apt-get line in Prerequisites. I worked each page from the top rather than reading it, and re-ran the whole thing because 947aa99 rewrote the scaffold instructions after the last review.

What worked, verified rather than assumed

  • The spel init correction in 947aa99 is right. I read the argument loop in spel-cli/src/lib.rs at the pinned revision: it breaks out at the first argument that is not a recognised flag and takes it as the project name, silently dropping the rest. Flags-ahead-of-name scaffolds with the fork pin actually applied; the old ordering would not have. The snake-cased my_program.rs and the root [workspace] with exclude = ["methods/guest"] are both exactly as now described.
  • The circuits pre-seed workaround is correct and necessary. cargo install died precisely as the page predicts, invalid peer certificate: UnknownIssuer out of logos-blockchain-circuits-pol-sys v0.5.3. The documented curl | tar xz -C ~/.cache/logos/blockchain/ produces the directory name the build script asserts on (logos-blockchain-circuits-v0.5.3-linux-x86_64), and the retry then went green. v0.5.3 is the right version for f7aa464.
  • Admin page: gated program compiles, and the IDL puts the injected accounts where the page says, set_fee_bps comes out as admin_config, caller, config. All three lifecycle commands run under --dry-run=text placed before the -- separator, including the one-key JSON {"Pda": ...} candidate.
  • Freeze page: auto, manual and embedded all compile, and the auto-mode IDL matches the ten expected names in the page's own order. Embedded drops admin_initialize and freeze_initialize and retargets every config account onto my own account, and freeze_authority_renounce really does fall to two accounts. The claim that FreezeCandidate must be imported even though nothing in consumer code names it is true, dropping it gives cannot find type FreezeCandidate in this scope pointed at #[lez_program].
  • Extension-library guide: I built the my-extension / my-extension-macros pair verbatim from the page, plus a consumer. extension_action is discovered and merged into the consumer IDL. The gate section works as written, #[require_my_gate] synthesises my_state, caller ahead of my own parameter in the declared order. Auto-wrap works too, and #[my_extension(manual)] opts back out, auto_gated goes from my_state, caller, thing to thing. read_inject_specs(&Path) -> Result<Vec<InjectSpec>, String> and the idl-gen gating on the extension module are both accurate at the pinned rev.

CI: npm ci && npm run build succeeds, and vale 3.11.1 (the version the workflow pins) reports 0 errors on the three pages, so neither gate blocks. The accept.txt diff looks large only because the file got re-sorted; it is 29 additions and no removals.

What stopped me: one real blocker, on the "Add the dependency" section that 947aa99 just retargeted at the scaffold, plus a few smaller path fixes. Inline below, each with a suggested change.

Comment thread docs/lez/extensions/admin-authority.md Outdated
Comment thread docs/lez/extensions/freeze-authority.md Outdated
Comment thread docs/lez/extensions/admin-authority.md
Comment thread docs/lez/extensions/admin-authority.md
Comment thread docs/lez/extensions/freeze-authority.md
Comment thread docs/lez/extensions/admin-authority.md Outdated
A spel init guest manifest already has a [dependencies] table, so
the dependency block read as something to paste and cargo rejected
it with a duplicate key before anything built. Both pages now say
which entries are already there and which single line is actually
new on that path.

The generate-idl invocations carried a cargo new path that does not
exist in a scaffold, where the guest lives under methods/guest and
is auto-detected from the project root. The admin marker is
described as sitting directly below lez_program rather than inside
the module, which is where a bare attribute would be a syntax
error.
FreezeCandidate and freeze_exempt have to be imported outside the
module. The generated transfer instruction references the candidate
type at the file's outer scope, so an in-module import fails with a
scope error pointed at lez_program rather than at the import, which
the example showed but the prose never said.

The verify sections say generate-idl reads source instead of
compiling it, so it emits a complete IDL for a program that does
not build and confirms the surface rather than the integration. The
gate snippet says it hardcodes the conventional parameter names for
brevity and that a shipped gate parses its attribute args, which
the kwarg contract already required.

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-dogfooding at 4af1de3 — clean Ubuntu 24.04, everything rebuilt from the pins on this branch

Ran the whole thing again because d5e8b57 and 4af1de3 landed after my last pass at fd23547. Both commits fix things I raised, and both hold up under test.

The two new commits, verified rather than read

  • d5e8b57, merging into the scaffold's manifest. The blocker is gone. spel init --spel-git ... --spel-rev f7aa464 my-program writes a guest manifest already carrying spel-framework, nssa_core, risc0-zkvm, my_program_core, serde, borsh and ruint, so the four the page names are all there. Adding only the admin-authority line and running cargo check from methods/guest went green, and spel generate-idl from the project root printed create_pool, set_fee_bps, admin_initialize, admin_transfer, admin_renounce. The error: duplicate key warning earns its place: appending a second [dependencies] table reproduces it verbatim, --> Cargo.toml:23:2. On the freeze page the "two lines" count is right too, admin-authority plus freeze-authority merged into the scaffold manifest built and gave all ten names in the page's own order.

  • 4af1de3, the limits of generate-idl. This is the most useful sentence added in either commit, and it is exactly true. I broke set_fee_bps with a call to an undefined function: cargo check fails with E0425, and spel generate-idl on that same tree still exits 0 and prints the complete five-instruction IDL. The mechanism matches the claim, generate_idl_from_file_with_deps in spel-framework-core/src/idl_gen.rs reads the file with read_to_string and parses it, nothing is compiled.

  • 4af1de3, the kwarg contract. Also true, and now demonstrably so. I built the my-extension / my-extension-macros pair verbatim from the library page, kept the minimal gate that ignores _attr, then gave a consumer instruction its own parameter names and pointed the gate at them with #[require_my_gate(my_state = their_cfg, caller = owner)]. It fails exactly as the new paragraph implies:

    error[E0425]: cannot find value `my_state` in this scope
    error[E0425]: cannot find value `caller` in this scope
    

    With the conventional names the same gate compiles fine, so "enough only while every gated instruction happens to use the conventional names" is the right framing.

Also re-checked at this head

  • Auto, manual and embedded freeze modes all compile on a spel init scaffold. In auto mode the IDL shows transfer carrying freeze_config, freeze_account, caller, thing and balance_of carrying only thing, so the exemption does what the page says. Embedded mode drops admin_initialize and freeze_initialize, as documented.
  • --dry-run=text placed before the -- separator still resolves accounts and prints the transaction.
  • The borsh line is worth one note in passing: the scaffold pins borsh = "1.5" with no derive feature, while the block here asks for features = ["derive"]. "admin-authority is the only line you add" still holds, because admin-authority turns the feature on and cargo unifies it, and #[derive(BorshSerialize, BorshDeserialize)] on PoolConfig compiled for me on that path. Just be aware the claim leans on a transitive feature rather than on the guest manifest itself.

CI: npm ci && npm run build exits 0. Vale 3.11.1, the version the workflow pins, reports 0 errors, 0 warnings and 0 suggestions on all three pages, so neither gate blocks.

What I found: one factual overstatement introduced by 4af1de3, and two troubleshooting bullets the wording fix in 947aa99 left behind. All three inline below with suggested changes, and all three vale-clean at error level.

Comment thread docs/lez/extensions/freeze-authority.md Outdated
Comment thread docs/lez/extensions/admin-authority.md Outdated
Comment thread docs/lez/extensions/freeze-authority.md Outdated
FreezeCandidate has to be imported outside the module because the
generated transfer instruction references it at the file's outer
scope. freeze_exempt resolves from either position, so the sentence
no longer asks for both, and it says what dropping freeze_exempt
costs instead.

The troubleshooting bullets ask for the marker below lez_program
rather than inside it, matching the annotate section, the library
guide, and the CLI's own diagnostic, which names the direction.

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dogfooding run: followed all three pages end to end on a clean machine

I worked through admin-authority.md, freeze-authority.md and build-a-spel-extension-library.md literally, from an empty container: installed the prerequisites, built the spel CLI at the pinned revision, scaffolded programs with spel init, wired the extensions in, compiled, and ran the lifecycle commands in dry-run. Environment: Debian/glibc, 4 cores, rustc 1.94.1, spel 0.6.0 built from mmlado/spel@f7aa464, admin-authority and freeze-authority at v0.1.2.

Almost everything on these pages is accurate, including the parts that are easy to get wrong. Verified against a real build rather than by reading:

  • The spel CLI install fails exactly as the page predicts, with invalid peer certificate: UnknownIssuer out of the logos-blockchain-circuits-pol-sys v0.5.3 build script. The documented pre-seed recipe works verbatim and lands the artifact under precisely the directory name the build script looks for (~/.cache/logos/blockchain/logos-blockchain-circuits-v0.5.3-linux-x86_64). The v0.5.3 pin for f7aa464 is right.
  • spel init writes the guest to methods/guest/src/bin/<snake_name>.rs, the root manifest is a [workspace] that excludes it, and with the two source flags the scaffold's spel-framework and nssa_core pins already match, so admin-authority really is the only line to add. Flags must precede the project name, as stated: the parser breaks at the first non-flag argument.
  • Skipping cargo fetch reproduces the exact offline cargo metadata failure the pages warn about.
  • IDL output matches the documented expected lists exactly, in the documented order: the admin trio, and all seven freeze instructions.
  • Injection order is as documented: set_fee_bps resolves to [admin_config, caller, config], and an auto-gated transfer to [freeze_config, freeze_account, caller, balance], while #[freeze_exempt] instructions get nothing injected.
  • Embedded mode behaves as described: neither initializer appears, the config accounts become the consumer's own account, and freeze_authority_renounce does drop from 3 accounts to 2.
  • --dry-run=text before the -- separator works, PDAs are derived so only --caller is needed, and --target takes 64 hex characters and rejects base58 as stated.
  • The author guide is sound end to end: I built the my-extension skeleton purely from its snippets and a consumer with exactly the four documented dependencies, and the extension's instruction was discovered and merged into the consumer's IDL. Dropping the dependency produces the documented fail-closed error naming the marker.
  • The three consumer programs (admin dedicated, freeze auto, admin+freeze embedded) all cargo check clean, no warnings. FreezeCandidate imported outside the module produces no unused-import warning, confirming it is genuinely required, and moving it inside reproduces the documented cannot find type FreezeCandidate in this scope pointed at #[lez_program]. freeze_exempt does resolve from either position, as the latest commit says.

Repo checks: vale --minAlertLevel=error (3.11.1, the CI version) is clean on all three pages, npm ci && npm run build succeeds, the pages route to /lez/extensions/... and pick up the autogenerated sidebar via _category_.json, and all five cross-page anchors resolve to real headings. Worth noting the last three commits have never been checked by CI: the fork PR is sitting at action_required, so a maintainer needs to approve the workflow runs. Both checks pass locally.

Two inline comments below. One is a real trap I hit; the other is a small robustness fix.

Separately, and out of scope for this PR: in --dry-run=text the target argument of freeze-account encodes as 128 bytes on the wire (0x0b000000 then 44000000 repeated 32 times) for a [u8; 32] IDL type that borsh should encode as 32 bytes. That looks like an argument-encoder bug in spel rather than anything wrong on this page, and I could not confirm it against a live node, but it may be worth checking upstream since the page documents this command against a real stack.


Generated by Claude Code

Comment thread docs/lez/extensions/freeze-authority.md Outdated
Comment thread docs/lez/extensions/admin-authority.md Outdated
The freeze marker rejects placement above lez_program from v0.1.3,
so the troubleshooting bullet no longer has to describe a build that
succeeds with the whole surface missing. The pins diverge for the
first time, freeze v0.1.3 against admin v0.1.2, which is the pairing
freeze itself pins, so the dependency paragraph says to use them
together rather than naming one shared version.

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-dogfooding at ed3b6ab — clean Ubuntu 24.04, everything rebuilt from the pins on this branch

ed3b6ab moves a dependency pin, so it earns a full rebuild rather than a read. Fresh container, nothing pre-installed beyond the apt-get line in Prerequisites, rustup default stable (rustc 1.98.1 today, comfortably above the documented 1.94.0 CLI floor), spel 0.6.0 built from mmlado/spel@f7aa464.

The new commit, verified rather than read

  • The v0.1.3 bump and the rewritten dependency paragraph are correct. freeze-authority v0.1.3 pins admin-authority v0.1.2 and the same f7aa464 framework rev — read off the tag's own Cargo.toml, not inferred. A consumer declaring both as direct dependencies resolves exactly one copy of each: cargo tree -i admin-authority shows v0.1.2 reached both directly and through freeze-authority v0.1.3, unified into one node. One spel-framework v0.6.0, one spel-authority v0.1.0, no From<AdminError>. "Use the tags above together" is the right framing, and it reads better than naming one shared version.
  • The curl -fL change is right and the recipe still works. The pre-seed lands the artifact under exactly the directory name the build script asserts on (logos-blockchain-circuits-v0.5.3-linux-x86_64), and cargo install then goes green without ever attempting the download. -f is a genuine improvement: without it a proxy error page would be piped into tar and produce a confusing failure downstream instead of at the fetch.
  • The v0.1.3 guard exists and fires. Confirmed in the library source (misplaced_above_lez_program in freeze-authority-macros), and confirmed by test — see the inline comment.

What ed3b6ab set out to do but did not land

The commit message says the troubleshooting bullet "no longer has to describe a build that succeeds with the whole surface missing", but the diff only touches the tag and the dependency paragraph. The bullet list is unchanged, and it is now wrong for the pins this page specifies. That is the one finding, inline below with a suggested change. The unimported entry ed3b6ab adds to accept.txt is currently used nowhere in the repo, which is the other half of the same dropped edit; the suggested wording uses the word, so the vocabulary entry earns its place again.

Also re-verified at this head, on the new pin

  • Auto mode compiles clean and the IDL carries all ten extension instructions in the page's own order. set_fee_bps under both markers resolves to admin_config, caller, freeze_config, freeze_account, config, so the admin gate and the auto-wrap compose in marker order as documented.
  • Manual mode does what it claims: #[require_not_frozen] transfer picks up freeze_config, freeze_account, caller, and the un-annotated balance_of picks up nothing.
  • Embedded mode compiles with zero warnings. Neither admin_initialize nor freeze_initialize appears, every config account retargets onto my own config, and freeze_authority_renounce really does drop to two accounts.
  • All ten lifecycle commands run under --dry-run=text placed before the -- separator, including the one-key JSON {"Pda": ...} candidate. PDAs derive, so only --caller is needed.
  • The author guide still stands on its own: I built the my-extension / my-extension-macros pair verbatim from its snippets plus a spel init consumer, and extension_action was discovered and merged into the consumer IDL with my_state, caller.
  • The [u8; 32] --target argument still encodes as 128 bytes on the wire. Unchanged from my last pass, still looks like a spel encoder bug rather than anything wrong on this page, and still out of scope here.

CI: npm ci && npm run build exits 0 with [SUCCESS] Generated static files. Vale 3.11.1 (the version the workflow pins) reports 0 errors and 0 warnings on all three pages, so neither gate blocks. The suggested change below is vale-clean at both levels.

Comment thread docs/lez/extensions/freeze-authority.md Outdated
The troubleshooting list still asked the reader to look for absent
instructions, which the v0.1.3 pin makes impossible. Every cause on
that list is a hard compile error now, so the section says so and
the misplacement bullet separates the imported case, where the
guard names it, from the unimported one, where the attribute does
not resolve at all.

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ran the end-to-end dogfooding on c930105, this time covering all three pages rather than the two consumer ones. Clean Ubuntu 24.04 container, nothing preinstalled beyond rustc 1.94.1.

What I ran

  • The apt-get line in Prerequisites covers the build. cargo install --git ... spel failed at exactly the point the page predicts, invalid peer certificate: UnknownIssuer out of the logos-blockchain-circuits-pol-sys build script, and the pre-seed recipe fixed it verbatim — the tarball unpacks to logos-blockchain-circuits-v0.5.3-linux-x86_64/ under ~/.cache/logos/blockchain/, which is the exact path provision_artifact probes, and v0.5.3 is what f7aa464 resolves to. curl -fL failing loudly on a moved asset is doing its job. spel v0.6.0 installed on the second run.
  • spel init with both source flags, admin-authority merged into the scaffold's guest [dependencies], module annotated, #[require_admin] on a gated instruction. cargo check clean; spel generate-idl | jq '.instructions[].name' from the project root printed initialize, create_pool, set_fee_bps, admin_initialize, admin_transfer, admin_renounce. The expected-output block is right and the path-free invocation auto-detects.
  • Freeze on top of the same program. Auto mode gave all ten names in exactly the order the page lists. Manual mode compiles, and set_fee_bps carrying both gates shows admin_config, caller, freeze_config, freeze_account, config — marker order is the ABI, as documented. Embedded mode compiles verbatim from the page: admin_initialize and freeze_initialize both gone, freeze_authority_renounce down to ["config","caller"], every config account retargeted onto my own. Widening padding to [u8; 25] fails the build with the marker offset 32 disagrees with ProgramConfig::ADMIN_SLOT_OFFSET, so the layout guard is real.
  • Moving FreezeCandidate inside the module fails with cannot find type FreezeCandidate in this scope pointed at the #[lez_program] line, not at the import, exactly as the page warns.
  • Dry runs of every lifecycle command on both pages. admin-initialize, admin-transfer --candidate Signer, the '{"Pda": {...}}' JSON form, freeze-initialize, freeze-program, freeze-account --target <64 hex> — all resolve, and the CLI's own --help matches the documented flag names one for one. --dry-run=text does have to sit before --.
  • Built the toy extension from Build a SPEL extension library from its own snippets. Discovery works, the consumer dependency list on that page is exactly sufficient (no borsh needed), and removing the dependency gives marker(s) ["my_extension"] matched no extension in the program's direct dependencies.
  • Repo checks: npm ci && npm run build succeeds and routes the three pages under /lez/extensions/; the autogenerated lezSidebar picks the new folder up and position 7 is free. Vale 3.11.1 is clean at error level on the three pages, and the 18 warnings are all Google.WordList on CLI, which 24 other pages already use. accept.txt adds 30 entries and drops none against main.

Findings

Four comments below. The one that matters is on the library-author guide: its recommended pass-through marker is the shape that let freeze-authority v0.1.2 compile clean with the marker above #[lez_program] and lose its whole surface silently. That was fixed in the library in v0.1.3 and is written up on both consumer pages, but this page still teaches the unguarded version to the next author. I reproduced it on the toy extension and verified the suggested guard closes it. The other three are a stale toolchain parenthetical, a missing troubleshooting bullet, and one section-ordering pointer.

The consumer pages themselves came through this run without a correction, which is a change from the previous rounds.


Generated by Claude Code

Comment thread docs/lez/extensions/build-a-spel-extension-library.md Outdated
Comment thread docs/lez/extensions/build-a-spel-extension-library.md
Comment thread docs/lez/extensions/admin-authority.md Outdated
Comment thread docs/lez/extensions/admin-authority.md Outdated
The guide's pass-through marker is the shape that compiles clean
when a consumer writes it above lez_program, with the whole surface
missing and only generate-idl reporting it. The framework cannot see
a marker that expanded above it, so the guard belongs in the
library's macro, where both shipped libraries already carry it. The
snippet now has the same check, the closing paragraph says why it is
the author's job, and the troubleshooting list gains the one cause
that produces no hard error.
The ruint 1.20.0 floor belongs to the cargo new route. spel init
pins ruint at 1.17.0 in the guest manifest, so the paragraph says
which route each floor applies to and what was verified on 1.94.0.
The dependency section was the first step a reader could not do top
to bottom, the guest manifest does not exist until spel init two
sections later, so it now says to install the CLI and scaffold first.

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ran the end-to-end dogfooding on the two commits pushed after my last pass (ea294c0, 7413f29), on a clean Ubuntu 24.04 with rustc 1.94.1.

What I ran, following the pages literally

  • apt-get install line from Prerequisites, verbatim.
  • cargo install --git https://github.com/mmlado/spel --rev f7aa464b… spel — installed in 5m21s. The circuits pre-seed remedy is needed in this environment and works exactly as written; v0.5.3 is the right version, Cargo.lock at f7aa464 resolves logos-blockchain-circuits to tag=v0.5.3.
  • spel init --spel-git … --spel-rev f7aa464… my-program, added the one admin-authority line to methods/guest/Cargo.toml, put #[admin_authority] below #[lez_program], cargo fetch, cargo check → clean, then spel generate-idl | jq '.instructions[].name'initialize, do_something, admin_initialize, admin_transfer, admin_renounce. The whole admin-authority page works top to bottom on the scaffold route.
  • Built the extension library from Build a SPEL extension library exactly as the page specifies it (my-extension + my-extension-macros, the metadata block, the runtime lib.rs, the proc-macro lib.rs including the new guard), wired it into the scaffold as a path dependency → cargo check clean and extension_action shows up in the consumer's IDL. The page's code samples compile as written.

7413f29 verified. spel init does pin ruint = "=1.17.0" in methods/guest/Cargo.toml (spel-cli/src/init.rs:725), and the resolver confirms it (Adding ruint v1.17.0 (available: v1.20.0)). The scaffolded program builds on the same 1.94.0 the CLI needs, so the reworded paragraph is right and the forward pointer from Add the dependency fixes the ordering problem it set out to fix.

ea294c0 verified, with one condition missing. I ran all four placements against a real build:

marker placement name imported? guard in the marker macro? result
below #[lez_program] n/a n/a green, surface present in the IDL
above no irrelevant error: cannot find attribute … in this scope
above yes no green build, surface missing; spel generate-idl exits 1
above yes yes the guard's own must come after #[lez_program] error

So the guard is worth shipping and the snippet is correct — but the silent-success case only exists once the consumer has imported the marker's name, which is the condition freeze-authority's own v0.1.3 changelog states ("compiled clean once its name was imported") and which freeze-authority.md already documents at line 352. Unimported — which is what these pages tell consumers to do — rustc rejects it with a message that says nothing about attribute order. Two comments below add that qualifier.

The third finding is that admin-authority.md's troubleshooting list still carries the pre-ea294c0 understanding: it files misplacement under the framework's hard error, which I confirmed it never produces. That page is the one page of the three that this PR's own refinement did not reach.

Checks. GitHub Actions on 7413f29 is sitting at action_required, so neither check has actually run on the fresh head. I ran both locally: npm ci && npm run build succeeds, and vale --minAlertLevel=error docs/lez/extensions/ is clean (vale 3.11.1, matching the workflow pin). The accept.txt change is a full re-sort plus 30 additions with nothing removed, so no existing vocabulary is lost.

Nothing here blocks the substance of the two new commits; all four comments are text fixes.

Comment thread docs/lez/extensions/admin-authority.md
Comment thread docs/lez/extensions/build-a-spel-extension-library.md Outdated
Comment thread docs/lez/extensions/build-a-spel-extension-library.md Outdated
Comment thread docs/lez/extensions/admin-authority.md Outdated
The pages described a marker above lez_program as a green build
with the surface missing. That holds only once the consumer has
imported the marker's name, which none of the pages tell them to
do. Unimported, rustc stops at name resolution with cannot find
attribute, a message that says nothing about order, so both cases
are now spelled out where the claim was made. The admin
troubleshooting list also filed misplacement under the framework's
hard error, which it never reaches, so it moves out of the list
into its own paragraph the way the freeze page already has it. The
guide's bullet carries generate-idl's actual error and exit status.

The scaffold's guest manifest holds seven dependencies, not four,
so both consumer pages name them all, ruint pin included, and say
to scaffold first when there is no program crate yet. The freeze
page's prerequisites now count that third admin section among what
it needs from there.

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ran the full end-to-end dogfooding on a clean Ubuntu 24.04 (rustc 1.94.1) against the current head, since the approval on this PR predates nine substantive commits. Almost all of it now works exactly as written. Two things still break a reader following the pages literally, both below as suggestions.

What I ran, and what passed

  • Prerequisites: the apt-get line installs clean, and unzip is load-bearing, rust-rapidsnark's build.rs shells out to a bundled download_rapidsnark.sh that calls unzip -o (crates/build.rs:79, crates/download_rapidsnark.sh:60).
  • cargo install --git ... spel failed exactly as the page predicts, with invalid peer certificate: UnknownIssuer out of logos-blockchain-circuits-pol-sys v0.5.3. The documented escape hatch then worked verbatim: the tarball unpacks to precisely the directory name provision_artifact probes for (~/.cache/logos/blockchain/logos-blockchain-circuits-v0.5.3-linux-x86_64), and the rebuild skipped the download and installed spel v0.6.0.
  • spel init with both source flags ahead of the name: scaffolds methods/guest/src/bin/my_program.rs, root Cargo.toml is a [workspace] with exclude = ["methods/guest"], and the guest manifest carries exactly the seven entries the page now lists.
  • admin-authority, dedicated mode: builds with admin-authority as the only added line. IDL gives create_pool, set_fee_bps, admin_initialize, admin_transfer, admin_renounce, and set_fee_bps comes out admin_config, caller, config, so the "prepended, ahead of the accounts you declared" wording is right.
  • admin-authority, embedded mode: the ProgramConfig + #[admin_slot] + #[admin_initialize] example compiles verbatim. No admin_initialize in the IDL, and every config account is config rather than the PDA, as documented.
  • freeze-authority, auto mode: compiles, and the ten expected names come out in exactly the order the page's block lists them.
  • freeze-authority, manual mode: compiles; balance_of is ungated, transfer gets freeze_config, freeze_account injected.
  • Both slots embedded in one account: compiles. Neither initializer appears, and freeze_authority_renounce really does drop from 3 accounts (admin_config, freeze_config, caller) to 2 (config, caller).
  • Lifecycle commands: --dry-run=text before the -- separator works for all of them. --candidate Signer, the {"Pda": {...}} one-key JSON form, and --target as 64 hex characters all resolve and print a transaction.
  • build-a-spel-extension-library: built the toy extension verbatim, both crates plus the metadata. A spel init consumer picks it up and extension_action lands in the IDL beside my_user_instr. Adding the gate macro, the [[package.metadata.spel.inject]] block and the dev-dependency works too: my_user_instr comes out my_state, caller, thing, and the alignment self-test compiles and passes, read_inject_specs(&Path) -> Result<Vec<InjectSpec>, String> matches the source and extension is indeed behind idl-gen and unreachable through spel-framework.
  • Marker misplacement, all three branches confirmed on a real build: guarded marker above #[lez_program] with the name imported gives #[my_extension] must come after #[lez_program]: ...; unimported gives cannot find attribute my_extension in this scope; guardless-and-imported gives a green cargo check with the surface absent, and spel generate-idl exits 1 with Malformed extension metadata: 'extension marker #[my_extension] is above #[lez_program]: ...'. 4ef83ea is accurate.
  • Both CI gates are green locally: npm ci && npm run build succeeds, and vale at --minAlertLevel=error is clean on all three pages (verified the rules actually fire by seeding a test file). All six cross-page anchors resolve against the built HTML, and _category_.json position: 7 no longer collides.

Comment thread docs/lez/extensions/admin-authority.md Outdated
Comment thread docs/lez/extensions/admin-authority.md Outdated
Comment thread docs/lez/extensions/freeze-authority.md Outdated
mmlado and others added 2 commits September 11, 2026 07:09
A root cargo fetch resolves a workspace that excludes methods/guest,
prints nothing and fetches nothing, so the next check in the guest
hits the offline metadata failure the sentence exists to prevent.
Both consumer pages now say to run it from methods/guest and why.

The require_admin paragraph claimed a renamed signer or PDA parameter
is reused in place of injection, which is true of the account list
and the IDL but not of the gate's prologue, which still names caller
and admin_config and fails to compile. The kwarg form is the fix,
and the paragraph now says so.

Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com>
@mmlado
mmlado force-pushed the docs/admin-authority-rfp-001 branch from fb93629 to 0e56d4e Compare September 11, 2026 05:23

@weboko weboko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after bfa1a32 / 0e56d4e — full end-to-end dogfood on a clean Ubuntu 24.04

Re-ran the whole thing from scratch against the PR head, since the two commits after my last pass changed the cargo fetch instruction and the require_admin kwarg paragraph. Everything below was actually executed — CLI installed, scaffold built, every code sample compiled.

Toolchain and CLI

  • The apt prerequisite line installs cleanly, and both of its non-obvious entries are genuinely load-bearing: crates/download_rapidsnark.sh really does unzip -o the release zip, and pyo3 v0.29.2 really is in the CLI's build graph.
  • cargo install --git … spel on rustc 1.94.1: the package name note is right, spel resolves.
  • The TLS-proxy failure reproduced verbatiminvalid peer certificate: UnknownIssuer out of logos-blockchain-circuits-pol-sys v0.5.3's build script — and the documented workaround works exactly as written. The curl+tar pre-seed lands at ~/.cache/logos/blockchain/logos-blockchain-circuits-v0.5.3-linux-x86_64, which is precisely what the build script's get_cache_dir() + build_artifact_name() compute, and the reinstall then succeeds. The v0.5.3-for-f7aa464 pairing is correct.

Scaffold

  • spel init layout, my-programmy_program.rs, root [workspace] with exclude = ["methods/guest"], and the seven-entry guest [dependencies] table (incl. ruint = "=1.17.0") are all exactly as described.
  • The flag-order warning is correct and earns its place. spel init <name> --spel-git … --spel-rev … — which is the order the CLI's own --help prints — silently drops both flags and pins git = "https://github.com/logos-co/spel.git", branch = "main". The resulting failure is exactly what the page predicts: adding admin-authority there fails with expected lee_core::account::AccountId, found spel_framework::prelude::AccountId pointed at #[lez_program], never naming the marker.

admin-authority

  • Marker-only example compiles; IDL is create_pool, admin_initialize, admin_transfer, admin_renounce.
  • The #[require_admin] + PoolConfig/write_to example compiles verbatim.
  • The new kwarg paragraph is right. #[require_admin(admin_config = my_cfg, caller = owner)] over renamed params compiles; bare #[require_admin] over a renamed signer fails with exactly cannot find value caller in this scope at the attribute. (Renaming the PDA as well adds a second cannot find value admin_config, same fix — the text stands as written.)

freeze-authority

  • v0.1.3 + v0.1.2 resolve to one copy of each, as claimed.
  • Auto mode compiles; the IDL carries all ten extension instructions in exactly the documented order. Auto-wrap is right too: transfer gets freeze_config, freeze_account prepended while both #[freeze_exempt] instructions get nothing.
  • Manual mode compiles.
  • FreezeCandidate imported inside the module fails with cannot find type FreezeCandidate in this scope pointed at #[lez_program] — exactly as documented.
  • Embedded mode compiles verbatim with both slots in one account. IDL drops admin_initialize/freeze_initialize, and freeze_authority_renounce is config, caller — the documented 3→2. The byte ranges check out against the sources: AdminConfig is one AuthoritySlot (32), FreezeConfig is slot + is_frozen (33), so 32..64 and 64..97 are correct.
  • The layout guard fires as promised: a field added above the slot gives the marker offset 32 disagrees with ProgramConfig::ADMIN_SLOT_OFFSET.
  • All ten lifecycle commands dry-run cleanly with the documented syntax, including --candidate Signer, the '{"Pda": {…}}' JSON form, and --target as 64 hex characters.

build-a-spel-extension-library

  • Built the entire guide: two-crate layout, extension_attr metadata, extern crate self as my_extension, the marker and the #[instruction] shim, all verbatim. A consumer picks it up and the IDL shows my_user_instr, extension_action.
  • The misplacement guard works and emits the guide's own error text.
  • The gate + inject section works: #[require_my_gate] with the [[package.metadata.spel.inject]] block synthesises my_state and caller, and the IDL confirms the documented ordering — gated_op: my_state, caller, thing, injected ahead of declared.
  • spel_framework_core::extension::read_inject_specs(&Path) -> Result<Vec<InjectSpec>, String> exists with that exact signature, and pub mod extension is indeed behind #[cfg(feature = "idl-gen")].

Repo CI

npm ci && npm run build passes, vale --minAlertLevel=error is clean on all three pages, and the accept.txt re-sort is lossless — 30 additions, nothing dropped.


One thing to fix, in the sentence 0e56d4e just rewrote, on both consumer pages. The methods/guest instruction itself is correct and I verified it; it's the "prints nothing, and fetches nothing" half that doesn't survive contact. Details and suggestions inline.

Comment thread docs/lez/extensions/admin-authority.md Outdated
Comment thread docs/lez/extensions/freeze-authority.md Outdated
The guest-fetch sentence said a root cargo fetch prints nothing and
fetches nothing. It fetches the root workspace's own graph, which is
noisy on a cold cache and silent on a warm one, and in neither state
does the output say the extension was skipped. Both consumer pages
now describe the symptom the reader can actually see, which is a
successful-looking run that omitted the one dependency that matters.

Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants