---
status: landed
summary: Deep research over the toolkit's bootstrapping, versioning and updates, dependency handling, launcher ecosystem and gated releases, measured against the code and the live session on 2026-09-08. Produced the website guide "How FNSTools is built" (a new guides route on the site), corrected twenty stale claims in place, and lists what is still flagged, with the ideas worth explaining and their code anchors.
since: 2026-09-08 (owner ask: explain how the toolkit is built, Launcher included, and put it on the website)
verified: 2026-09-08 (live session FNSTools_PRIV on TD 2025.33070; four code traces; read-only probes of the gate and bucket)
---
The owner asked for a deep pass over the architecture (bootstrapping, version and update management, dependency handling including externals, tooling, the Launcher, gated releases) that ends up as a dedicated section of the website documentation. This is the record of that pass. The deliverable itself is the guide; this document says how it was made, what it measured, which documents disagreed with the code, and what was done about each.
- The guide.
website/content/guides/how-fnstools-is-built.md, served at/docs/guides/how-fnstools-is-built/(renamed 2026-09-10 so the slug matches the title;/docs/guides/architecture/is now the one-page overview that links to it). Sections: the shape in one paragraph; what a package is; registries; derived dependencies; bootstrapping (first drop, served picker, the store as a mirror, plan then install, the other rails, set up like last time); versioning and updates (four questions, versions decide and hashes verify, pinned releases and the rolling pointer, discovery and signing, three motions, applying, what a reload keeps); where settings live; shared code; the release pipeline and foreign packages; the launcher and the ecosystem; gated releases; what is deliberately absent or unproven; ideas worth borrowing. - A guides route in the site generator (
website/tools/build-site.mjs). Anywebsite/content/guides/<slug>.mdwithtitleandsummaryrenders through the package-page markdown pipeline into the docs chrome, is indexed by Pagefind with the rest of/docs/, and takes part in the internal-link check both ways and in the house-style dash note. Asectionkey places it:guidesleads the sidebar and the index,referencecloses both beside the common-parameters page. Documented inwebsite/README.md. - Placement, per the owner's review the same day: the architecture page
is long-form and sits at the end of the docs under Reference. The Guides
group up front holds
getting-started.md, a short set of instructions (install, pick, update, settings, Plus, help) whose first line links to the architecture page for readers who want the machinery. - The landing page's "How updates work" button now points at the getting-started guide's Updating section instead of the wiki-era Updater page.
- A one-page overview, added 2026-09-10 on the owner's follow-up ask for
"a much simpler version too":
architecture.mdat/docs/guides/architecture/, around 650 words in the Guides group. One tool one package; the nine registries and the surface each owns; dependencies derived from hosted registries; one bucket, versions decide and checksums verify; settings that roam; and the reach past the toolkit (commands, the launcher, Plus). It links down to the long version and the long version links back up. The deep page moved to/docs/guides/how-fnstools-is-built/in the same pass so each slug matches its title and the guessable URL lands on the short page. Guides also gained an explicitorderfrontmatter key, since their position in the sidebar had been an accident of the filename. - Twenty dated corrections in place, listed below, and a status refresh of
website/README.md.
Every in-force and open design document under docs/ that touches the
subject was read (Overview, PackagingScheme, ConfiguratorDistribution,
RegistryScheme, RegistryHomeContract, UpdaterHardening, RailHardening,
ReleaseSigning, ScopeAndPersistence, ConfigScope, LastInstallRecord,
InstallSurfaceDesign, NativeInstallerDecision, FNSToolsRedesign,
ExternalizationOwnership, CmsResearch, ForeignPackages, PlusCapabilityPackaging,
CommandCuration, LauncherToolkitBoundary, TDXLUGateIntegration,
GatedDeliveryResearch, EntitlementLifecycle, the head of EntitlementFunnelPlan,
DistributionComparison), plus packaging/README.md, RELEASING.md,
CREATING.md, CHANGELOG.md, the fns-packaging and fns-registry skills,
and the launcher repo's own README.md, docs/fns-integration.md,
docs/fns-gate.md, docs/fns-command-registry.md, docs/licensing.md,
docs/releases-r2.md and docs/fns-plus-capabilities.md.
Four code traces were run against the files as they are on disk: the
install and update machinery (InstallerExt.py, build_installer.py,
ExtUpdater.py, ExtAuth.py, the secure-storage modules, the picker page,
the tests that pin them); the dependency derivation and the release rail
(build_manifest.py, publish.py, upload.py, release_one.py,
sign_release.py, gate_package.py, foreign_sync.py, RegistryBase.py,
the ExtUtils masters and distributor); the launcher, the gate worker and the
family (worker/src, the tests, the mirror check, the four capability
packages); and the site generator. Where a document and the code disagreed,
the code won, and the document got a dated note.
| Measurement | Value |
|---|---|
Children of /sys/FNS_Registries |
11: ten RegistryBase registries at 3.0.4 or 3.0.5, plus FNS_CommandRegistry |
Live packages under /FNSTools (depth-1 suspects with externaltox) |
58, of which FNS_Installer is a rail; 57 carry FNS_About |
| Manifest | 58 packages (48 tool, 10 core), 6 gated, 1 foreign (TDXMap, self-managed), 13 starter packages |
| Config-registry family under the root | 48 COMPs; ScopeAudit() empty; root Configscope is global |
| Registered commands | 152 |
| Gate | /health answers; unauthenticated GET of plus/v3.1.4/FNS_Collect.tox returns 401; the plus canary 404s; /trial/start is not_found |
| Discovery pins | pins 1 and 2 answer with v3.1.4; pin 3 (raw.githubusercontent.com/function-store/fnstools-links) is 404 |
| Launcher mirror record | packaging/launcher_mirror.json records v3.1.3 builds (0.1.3 / 3.0.4) while the bucket serves v3.1.4 (0.1.4 / 3.0.5) |
Each got a dated note beside the original text; nothing was deleted.
| Where | It said | The code and the live state |
|---|---|---|
docs/Overview.md §7 |
three holes open: silent package removal, no upload read-back, one bucket URL with no fallback | all three closed 2026-08-27 (publish.py removed, upload.py read-back, discovery document); pin 3 and the server-side kill switch remain open |
docs/Overview.md §8 |
gated delivery "decided by nobody yet" | deployed 2026-08-29, six packages gated |
docs/PackagingScheme.md §1, §7, §8, §9 |
pre-3.0 core names; the three holes "present in the code today"; updater at UPDATER/ExtUpdater.py mirrored in scripts/; a local-only branch note; aws s3 sync as the upload |
owners are FNS_ConfigRegistry and FNS_ToolbarRegistry; holes closed; the updater is modules/suspects/FNSTools/FNS_Updater/ExtUpdater.py and scripts/UpdaterExt.py is Embody's self-updater; the upload is upload.py behind Guided Release |
docs/ConfiguratorDistribution.md intro |
native .exe/.dmg installers are the bootstrap |
the one-drop FNSTools.tox; native installers deferred 2026-08-21 (the edit NativeInstallerDecision.md listed and nobody applied) |
packaging/README.md, The bucket |
same native-installer line | same correction |
docs/GatedDeliveryResearch.md frontmatter, banner, §10 |
research, "not deployed, TD client side not started", PLACEHOLDER_TIER in the catalog |
status landed; deployed and walked; real tier ids on six packages; the placeholder survives only as a test fixture |
docs/EntitlementLifecycle.md banner |
"BUILT, NOT DEPLOYED", 62 checks, 48 packages all free | deployed; 91 checks; 58 packages, 6 gated |
worker/README.md |
"Nothing here is deployed yet"; route table without claim, revoke, recheck, entitlement, pubkey | deployed since 2026-08-29; src/index.js dispatch is the truth |
docs/RailHardening.md §2.2 and the landed banner |
kill switch "enforced both ends"; pin 3's repo "does not exist yet" | client-only today (ExtUpdater._belowFloor); pin 3 still 404 on 2026-09-08 |
docs/RegistryScheme.md table, §3, §5, §6 |
nine registries; a healing watch every 120 frames and a 2 s tick; op.FNS_TOOLBAR.op('ToolbarRegistry'); the command registry has no master here and is promoted from the launcher |
ten registries (Timeline missing from the table); REGISTRY_WATCH_ENABLED = False, healing runs at project save; the clone expression is op.FNS.op('FNS_ToolbarRegistry'); the command registry has a master and source here since v3.1.1 and ships as a store package the launcher mirrors |
docs/RegistryHomeContract.md, family members |
the command registry's source lives in TDXLPP | it lives here; the launcher mirrors the released artifact |
docs/CommandRegistration.md pieces table |
"We NEVER ship it" | shipped since v3.1.1 |
packaging/RELEASING.md step 4 |
Release()/ReleaseMany() run Preflight and refuse on a blocker |
only Release() does; ReleaseMany(), the PI and CMS buttons and the wizard's Continue do not refuse |
website/README.md |
Worker undeployed, PLACEHOLDER_TIER, Gateurl unresolvable; a frontmatter example with credit: |
all three false since 2026-08-29; credit is refused by the build and lives in catalog.json |
Found by the traces and left for the owner or for the next release, because each needs a decision or a code change the research pass should not make:
packaging/release.json_commentandpublish.py(around line 192) still mention native installers;build_installer.pytitles the bootstrap README "FunctionStore_tools";InstallerExt.py:27namesUPDATER/ExtUpdater.py.ExtUpdater._rewriteBounddocstring still describes the pre-flipreloadcustomsplit (9 off / 41 on); the fleet flipped 2026-08-31.packaging/parameters.json: theGateurlhelp says "Placeholder until the gate is deployed"; the par default is blank andExtAuth.DEFAULT_GATEis the live host. Fix ispar.helpin TouchDesigner.scripts/UpdaterExt.pyis a byte-identical duplicate of Embody'smodules/suspects/Embody/updater/UpdaterExt.py; stale location.packaging/launcher_mirror.jsonis one release behind the bucket (v3.1.3 record, v3.1.4 published); the mirror check passes against the old record, so the launcher carries a registry one version behind ours.- The third discovery pin's repository (
function-store/fnstools-links) does not exist; the pin is compiled into the shipped updater. CHANGELOG.mdhas no v3.1.0 entry and an empty v3.0.9:_setReleaseLabelruns before Build and Stage and a refused stage does not restore the label, so a refusal burns a label.upload.py --pruneenumerates CHANGELOG labels only.packaging/docs/FNS_Updater.mdis wiki-era (the?toolbar icon) and says nothing about Refresh Store, Check for Updates, Update This Project, discovery or signing. The landing button now bypasses it; the page itself wants a rewrite through the CMS.- Core membership is stated three ways:
packaging/docs/FNS_ConfigRegistry.md(six surface registries plus Console and Updater),build-site.mjs:198("eleven registries"), and the manifest (ten core: eight registries plus Hub and Updater, with Timeline, Palette and Command registries shipping as tools). docs/CustomParHelperContract.md(exec DATs resolve viamod(me.dock.name)) anddocs/RegistryScheme.md(rewritten dock-free) disagree; the on-disk templates side with dock-based; live DAT text not verified.docs/ExternalizationOwnership.mdnamesscripts/QuickExt/templates/as the shared CustomParHelper source; the live master syncs frommodules/suspects/FNSTools/QuickExt/ExtUtils/CustomParHelper.pyand the templates copy is the 2024 version.docs/CmsResearch.mdandEntitlementFunnelPlan.mdsay FNS_CMS owns entitlement authoring; today the content CMS (cms.mjs) writesaccessthroughgate_package.pyand FNS_CMS's Entitlement tab is empty, so the launcher'sTDXLU_Progrant has no authoring surface (G5 inTDXLUGateIntegration.md, still open with G3 and G4).docs/PaletteTabContract.mdsays the launcher's palette tabs still come from a pre-registry injector; the launcher's README says the strip belongs toFNS_PaletteRegistry. One is stale; not verified which.docs/LauncherToolkitBoundary.mdproposes anexternalCompare row "when A lands"; A landed, the row does not exist.website/index.htmllines near 1144 and 1180 still say the command palette is "the launcher palette today, an in-TouchDesigner palette next";FNS_CommandPaletteshipped 2026-09-01. Landing copy; left for the owner.README.mdat the repo root links its download badge to GitHub releases while the rail publisheslatest/FNSTools.toxon the bucket; not verified whether a GitHub release mirror exists.docs/UpdaterSelfUpdateVerification.md(open) asks whether self-update is ordered last; the code answers yes (_apply,_selfUpdate), and the end-to-end run it plans has still not been done.
The guide's closing list is the reader-facing version. This is the longer one, for anyone explaining the toolkit or borrowing from it.
- Versions decide, hashes verify:
.toxexport is not reproducible (three exports, three hashes, diverging at byte 9), soPkgversionread live off the component is the update signal (build_manifest._version,ExtUpdater.Compare). - The version lives on
FNS_About, a child the reload rebuilds, so it travels inside the artifact; child-first readers and a severed-mirror preflight block keep authority from inverting (release_one._versionWritePar). - Dependencies derived from stamped hosts (
build_manifest.pyrequires), so the installer needs a topological walk and no solver (InstallerExt._order). - One COMP, two roles: a registry copy is both the shipped host and the
/sysglobal (RegistryBase._become_global_registry), and promotion never prompts (_compare_versions, newest wins, ties to the incumbent). - Tool-page parameters as bind masters: registration settings persist with the
tool and roam through ConfigRegistry (
RegistryBase._ensureToolRegistryPage). - Healing at project save, never per frame, because the toolkit runs inside
live shows (
registry_presave_exec.healAllRegistries). StampHostas the one blessed copy recipe, so the paid-for copy hazards stay fixed in one place.- A master plus an init-time heal plus a pre-release audit for anything that
must reach every clone host (
ConfigRegistryExt._ensureScopePar,ScopeAudit). - The bootstrap is the live dev root castrated (
build_installer._bootstrapRoot), and the first run is a storage flag the shipped root does not carry. - The store is a mirror;
.partstaging and sha promotion mean a refused download can never overwrite good bytes (ExtUpdater._verifyFetched). - The install record is written per package as it lands
(
InstallerExt.RecordInstalled), so an interrupted pass re-runs. - Pinned immutable releases, one rolling
no-cachepointer,latest/aliases for humans only (publish.py,upload.py). - A signed discovery document at pinned URLs with
minimum_updaterandnotices; the last good copy cached apart from the fetch target; a local base URL outranks it (ExtUpdater.DISCOVERY_PINS,BaseUrl,_belowFloor). - Fail closed on a bad signature, fail open on a missing one during the
transition (
REQUIRE_SIGNED), and refuse a floor only when it parsed. - One package per drain tick, self-update last and detached, backup before
destroy, and a reload proven by renewed child ids (
_drain,_selfUpdate,_replacePackage,_settleVerifications). reloadcustomoff withreloadbuiltinon, measured across five tox generations, so TouchDesigner reconciles the parameter set itself.- Two settings stores with the
.toeas the default and the JSON as a once-per-boot overlay; one scope switch; credentials in the keystore. - Read-back verification with a printed rollback command, gated objects
verified through the authenticated path, a privacy canary every upload
(
upload.py). removedversus a declaredretiredlist, so a package cannot vanish silently (publish.Stage).- Release notes accumulate per commit and become both the changelog bullet and
the in-tool
whatsnew(build_manifest.AttributedNotes). - Foreign packages declared by
source, mirrored on the shell, pinned by a committed lock,self-managedin the updater (foreign_sync.py). - Gate the bytes: same host,
plus/prefix, the Worker fails closed on a claim that does not name the package; the client names the missing tier and never decides access; the tier map lives once, inwrangler.toml, written together with the catalog bygate_package.pyand cross-checked at stage. - A one-time grant code on the loopback redirect, a product list as the claim,
per-kind session lifetimes, a 30-day stale-trust backstop, and a revoke
that is not an oracle (
worker/src/index.js). - Tag-based command discovery and
announce(): a consumer that arrives later rediscovers every tool;surfaceandcapabilityas progressive enhancement that is never a gate (FNSCommand.py, registry 1.7.0). - The launcher installs through the toolkit's installer,
fns.installis dry run by default and minimal never removes, and the launcher seeds the store only on a cold start (InstallerExt.FnsCommands,ResolvePlan(minimal=)). - One ownerless curation file with per-entry merge, tombstones and an in-file
seededstamp (CommandCuration.py). - A generated public mirror that withholds gated sources and refuses a root
tox that embeds one (
scripts/publish_public.py). - The
FNS_prefix is identity; every listing shows the public name (PublicToolNames.md).
The four trace reports live only in this session's transcript; every claim
they made that reached the guide or a correction was re-checked against the
file it cites before being used. The live measurements above were taken with
one read-only execute_python on the running dev project. The gate and pin
probes were read-only GETs.