- Core extension management is generic and closed to two kinds:
flyto-modules-*intoflyto.modulesandflyto-plugin-*intoflyto.plugins, declared once inEXTENSION_KINDSand read by every other decision. No Core source names an individual extension, so a pack such asflyto-modules-roboticsis managed by the generic path with no Core change. Served at/v1/extensions(bearer token on all four routes; the two mutating routes additionally requireFLYTO_EXTENSIONS_INSTALL_ENABLED=1). An install is reported successful only after entry-point proof; a failed new install is rolled back, a failed upgrade is not; upgrades and uninstalls reportrestart_required. Failures carry a stable code and never package-manager output. Pinned bytests/core/api/test_extensions.pyand gated by thelint_extensionsandextension_managementchecks in.flyto/coding.yaml. Verified 2026-08-12 — see Last Verification. - Both security boundaries are now closed registry-wide and enforced in CI,
rather than patched per advisory:
- Filesystem — 88 modules declare a path-shaped parameter; 71 reach
validate_path_with_env_config, 17 are documented as not filesystem paths, 0 unaccounted. Enforced bytests/core/test_write_sink_coverage.py. - Outbound network — 57 modules declare a URL/host-shaped parameter; 46 reach
an SSRF guard, 11 are documented as never reaching the network or as
validating locally, 0 unaccounted. Enforced by
tests/core/test_outbound_guard_coverage.py(MRO-aware, so inherited guards count). Exemptions require a written reason and are re-verified each run. The audits confined roughly 30 modules that no advisory had named — see CHANGELOG[Unreleased]and the two 2026-08-08 entries in DECISIONS.md.
- Filesystem — 88 modules declare a path-shaped parameter; 71 reach
- Two breaking changes come with that: paths outside
FLYTO_SANDBOX_DIR(default: the process working directory) are refused, and connections to private/link-local hosts needFLYTO_ALLOWED_HOSTSorFLYTO_ALLOW_PRIVATE_NETWORK=true. Loopback is unaffected. - Package metadata is prepared for the 2.27.0 release. It is a minor, not a
patch, because the two boundary changes above refuse inputs earlier releases
accepted — it is not a drop-in upgrade for callers that passed absolute paths
outside the sandbox or connected to private hosts.
SECURITY_STATUS.mdpublishes all 24 advisories with the regression test covering each, generated fromsecurity/advisories.jsonand verified in CI. - The preceding 2.26.12 security patch release closed the remaining browser
file-write and SSRF gaps the 2.26.11
hardening waves left open (
browser.download/screenshot/pdf,warroom.report,verify.report/visual_diff/run,browser.launch,data.dedup;browser.goto's www-toggle retry), plus a tar-extract symlink (Tar Slip), aport.checkIPv6 SSRF fail-open, and a regex ReDoS, tracked by public GitHub security advisories. - MCP clients no longer have to guess whether a connection is ready or keep a fragile server-side session alive. Core supports the stateless MCP 2026-07-28 request model, publishes discovery and cache guidance, validates HTTP request mirrors before execution, and still accepts handshake-based clients from 2024-11-05 through 2025-11-25.
- Warroom deterministic verification v1 exists in
flyto-core: it can build a redacted site graph, generate replay scenarios, execute module assertions, and emit JSON/Markdown evidence packs. LLM review is disabled by default and advisory only. - The generated catalog currently exposes 468 modules across 85 categories, and the bundled recipe inventory contains 41 recipes.
- Catalog search and detail results carry each module's registry-declared
provides_capabilityandplugin; neither is derived from the module ID. - Plugin discovery is a transaction keyed on what each
register_all()actually registered, held for exactly the span of that call. A forced pass removes modules a plugin stopped providing, a failed load restores rows it overwrote to their real owner instead of deleting them, and the contribution record is replayed only into a registry that began the pass empty. Verified and accepted 2026-08-11 — see Last Verification. - A registry read is answered from a registry that stood whole at one instant,
and the answer is a copy rather than a handle. Every public read holds
_discovery_lockfor its whole body;discover_plugins()andrefresh()return a copied plugin mapping on every path;PluginInfois frozen, closing the last route by which a caller could edit registry state through a value it was handed.REGISTRY_VERSIONis 1.4.0. Verified and accepted 2026-08-11 — see Last Verification. flyto.core.capability-manifest.v1describes what an installation can do, derived from the registry and free of timestamps, paths, and host identity, so two hosts with the same installed distributions produce byte-identical documents. It is served read-only overGET /v1/capabilitiesand the MCPget_capability_manifesttool;POST /v1/capabilities/refreshre-runs discovery and requires bearer authentication.- The manifest cache is ordered by a monotonic registry generation, not by which
build stored last.
ModuleRegistry.capability_snapshot()reportsgenerationunder the same lock hold that produced the data, and a build only publishes when its generation is at least the cached one. A build that read the registry before a refresh and finished after it is therefore rejected instead of silently republishing the pre-refresh surface. Verified 2026-08-12 — see Last Verification. - The registry transaction covers the in-process path only. The out-of-process
PluginService/ runtime plugin lifecycle (src/core/api/plugins/service.py,src/core/plugin/) is a separate surface: it is outside the transaction and outside the coverage kernel, and remains open work. It is not the only open plugin surface — theflyto.plugin.v1manifest is still a DRAFT specification andRuntimeInvoker.set_plugin_managerstill has no caller, so a workflow step cannot reach a plugin subprocess. Seedocs/specs/PLUGIN_MANIFEST_SPEC.md. - Project memory structure has been bootstrapped for repeatable workflow and validation handoffs.
- The repository already contains workflow assets and CI for maintained recipe bundle tests.
- Core is positioned as the validation layer for frontend, engine, admin, and enterprise deployment smoke loops.
- Python runtime code and tests use one canonical package identity,
core. The suite rejectssrc.coreimports because duplicate module identities can split auth, registry, and security state inside one process. - Security-sensitive test exceptions are fixture-scoped. Collection-time private-network and verification-auth overrides are rejected by contract.
- The browser-contract JavaScript runtime is declared and locked in
package.json/package-lock.json;npm auditis part of local closure. - Core API, OAuth2 token, and provider-webhook HTTP emitters use the shared connect-time DNS and per-redirect SSRF guards. OAuth2 error responses do not reflect provider response bodies.
- Azure, GCS, and S3 download destinations are canonicalized and confined to
FLYTO_SANDBOX_DIRbefore any provider SDK can create or write a local file. - CSV, YAML, Excel, PDF, image, browser persistence, and document-generation paths are canonicalized and confined before any filesystem sink. Browser cookie imports and document readers use the same boundary for reads.
- Agent-chain Ollama calls are loopback-only by default. Loopback calls run in an exact host/port network scope; explicitly enabled remote calls still use the shared DNS-pinned, redirect-revalidating SSRF boundary and do not reflect provider error bodies.
- Cryptography uses the patched 48.x line, while Python 3.10+ API and CI environments select patched Starlette, pip, and setuptools floors. Python 3.9 keeps explicit compatibility branches for upstream lines that no longer publish patched Python 3.9 releases.
- The 60% line coverage gate measures the maintained orchestration and security-control kernel. Pluggable module implementations and product overlays remain covered by catalog, contract, and integration suites.
- Source-backed documentation now covers 955 maintained Python files, 5,631 declarations, 483 literal module registrations, all CLI/HTTP/environment surfaces (28 static HTTP operations, 107 environment names), and all maintained recipe/workflow assets. CI rejects drift, missing ownership, broken local links, stale naming, and mailbox violations.
- Workflow status and evidence reads now require bearer authentication.
crypto,dns, andaiextras express tested optional dependency boundaries; the development extra supports the complete offline suite.testing.visual.comparenow delegates real PNG decoding and pixel comparison to a detachable TypeScript worker with a scrubbed environment, bounded JSON and image inputs, pre-decode PNG dimension checks, non-overwriting diff evidence, and content hashes. Its no-mock subprocess matrix covers 101 distinct cases across identical, pixel-difference, and dimension-mismatch tiers.- Trusted security campaigns may open a task-local exact host/port outbound scope. Redirects and connect-time DNS checks remain inside that scope, cloud metadata endpoints remain permanently denied, and concurrent tasks do not inherit one another's authorization.
- Plugin runtime loads now select only directories recorded by confined manifest discovery. External plugin IDs cannot construct filesystem paths, symlink escapes are rejected, and MCP header-decoding failures return a stable generic error instead of exception details.
- No repo-specific release blocker is recorded from this audit.
- Cross-repo production readiness still depends on remote CI stability and on adding stronger enterprise airgap smoke recipes.
- Browser/E2E integrations that require external services, browsers, or credentials remain environment-backed evidence and are not inferred from the offline suite.
reverse.*(CDP debugger, Phase 1 + Phase 2, hardened 2026-07-25) known risks: session state (ReverseSessionobjects, keyed bydebugger_sessionid) is process-local, matching the existingbrowser_sessionsconstraint across all three transports (STDIO MCP, HTTP MCP, plain REST) — adebugger_sessionid minted by one server process cannot be resolved by another; this remains an accepted constraint, not something planned for redesign. Cleanup is now three-layered: explicitreverse.detach/browser.close(primary path), a shared idle-timeout reaper (src/core/session_reaper.py, 30 min default viaFLYTO_SESSION_IDLE_TIMEOUT_S) wired into all three transports that closes/detaches sessions with no recorded activity past the timeout, and the STDIO transport's EOF-cleanup loop as a final backstop. A session abandoned mid-workflow (crash, disconnect) no longer leaks its Chromium process/CDP session for the server's entire lifetime, though process-local scoping is still unchanged.reverse.hooknow traps the target property withObject.defineProperty(get/set accessor) instead of a one-time overwrite, so it survives both a page assigning the property after install and a page reassigning it later; the one remaining known limitation is narrower — a path whose immediate parent object doesn't exist yet at document-start (e.g.myNamespace.fnwheremyNamespaceis itself lazily created) still can't be trapped. See DECISIONS.md for the pause/resume design rationale, the CDP-freeze caveat, and the hook/reaper redesign rationale.reverse.code(Phase 3) requires the optionaljsastextra (tree-sitter,tree-sitter-javascript,jsbeautifier) — raises a clearpip install 'flyto-core[jsast]'error if it isn't installed. Unlike the rest ofreverse.*, it needs no permission (no browser/CDP access, no code execution) and does not depend onreverse_sessionstate at all. It only beautifies and structurally searches JS source text — real semantic deobfuscation isreverse.deobfuscate(Phase 4, see below).reverse.sourcemaprequires no extra pip dependency (hand-rolled VLQ decoder) and no permission, same reasoning asreverse.code. It never fetches an external.mapfile itself — the caller useshttp.get(already SSRF-guarded) for that and passes the fetched text in. It only resolves generated-to-original locations and readssourcesContentthat was already embedded in the source map — it cannot fetch an original file that isn't inlined.reverse.request_breakpoint(request-level XHR/fetch breakpoints viaDOMDebugger.setXHRBreakpoint) reuses the sameDebugger.pausedpause/resume pipeline as script breakpoints, so it carries the same CDP-freeze caveat and process-local session scoping as the rest ofreverse.*.reverse.attachnow reuses an existing enabled session on the same page instead of always detaching and recreating one (passforce_new=Truefor the old behavior) — this changes what a redundantreverse.attachcall returns (reused: trueplus the session's existing snapshot) but not what a fresh attach on a new page returns. SeeDECISIONS.md(2026-07-25 request breakpoint / session reuse entry).reverse.deobfuscate(Phase 4) requires a system-installed Node.js 22 or 24 onPATHplus a one-timenpm installinsrc/core/modules/atomic/reverse/deobfuscate_worker/— raises a clearModuleErrornaming the exact fix if either is missing. Unlikereverse.code, it is gated behind a new deny-by-defaultcode.executepermission, since itswebcrackengine unconditionally evaluates the input inside anisolated-vmsandbox. It manages its own dedicated Node.js subprocess (spawned and killed per invocation) rather than the generic JSON-RPC plugin runtime (src/core/runtime/manager.py), which was found to have unenforced resource limits, no kill-on-timeout, and no wiring from plugin manifests intoModuleRegistry— not something to build on silently. Does not includerestringer: the npm-published package is maintained by an unofficial fork whose dependency tree has dropped theisolated-vmsandbox the canonicalHumanSecurity/restringerproject still declares. SeeDECISIONS.md(2026-07-25 deobfuscate entry).
| Gate | Command | Scope |
|---|---|---|
| Project memory | bash scripts/lint-project-memory.sh |
Required docs, handoffs, architecture headings, secret-like material |
| Documentation | python scripts/check_documentation.py |
Generated drift, ownership, catalog, local links |
| Brand | python scripts/check_brand_identity.py |
Flyto2 naming and approved public aliases |
| Dependencies | PYTHON=.venv/bin/python bash scripts/lock-deps.sh plus pip_audit |
Canonical base lock and vulnerability scan |
| Audited lint | CI Ruff command in .github/workflows/ci.yml |
Changed maintenance, API security, dependency-boundary surfaces |
| Offline tests | python -m pytest -m 'not browser and not e2e' |
Full offline suite plus 60% control-kernel coverage gate |
| JS runtime | npm ci --ignore-scripts && npm run audit |
Locked jsdom runtime and dependency audit |
| Build | python -m build && twine check dist/* |
Wheel/sdist integrity and metadata rendering |
| Indexer | flyto-index verify . --full-scan --strict --json |
Repository closure and 90-point docs budget |
Codex independently reviewed the full working diff and found one additional
policy-alias defect: database.scan can route to
flyto-official/database, but the gate queried the manifest under the caller's
legacy database spelling. The resolved plugin id now drives manifest and
plugin-grant policy before either primary or fallback execution; legacy-first
routes retain that id. A regression test proves the dangerous permission is
denied and the real resolved id is queried.
All repository-pinned checks pass on the current source: project-memory,
compile, four Ruff surfaces, extension management (81 passed), runtime
lifecycle (45 passed), generated documentation, registry/plugin
(78 passed), and the full non-browser/e2e suite at 2785 passed, 11
skipped, 273 deselected, 63.20% coverage against the 60% floor. Generated
references report 955 source files and 5,631 declarations; the catalog remains
468 modules across 85 categories. After commit 0a353ff, strict Indexer passed
19/19 on the clean tree with no warnings or failures, closing the one
pre-commit hygiene finding caused by dirty .flyto/coding.yaml.
Scope: documentation only. No source, test, or config file was changed. This
pass exists to close the one thing the "generic extension management" entry
below recorded as outstanding and mechanically fixable — docs/reference/
was known stale because the generator had never been run against the extension
surface.
What was executed here, and what it returned:
| Check | Result |
|---|---|
generate_reference (declared project action) |
exit 0 — 5,631 declarations, 955 source files |
generate_catalog (declared project action) |
exit 0 — 468 modules across 85 categories |
generate_reference did write this working tree: docs/reference/ now
reports 955 files / 199,204 lines / 5,631 declarations across 808 files, and
docs/reference/http-api.md carries all four /v1/extensions routes while
docs/reference/configuration.md carries FLYTO_EXTENSIONS_INSTALL_ENABLED.
generate_catalog reported writing /workspace/docs/TOOL_CATALOG.md — the
sandbox copy, not this tree — so it is evidence the catalog figures are
unchanged at 468/85, not evidence that this tree's TOOL_CATALOG.md was
rewritten. It did not need to be: src/core/catalog_facts.py still declares
468/85/41.
Documentation drift found and fixed (the extension surface moved these):
| File | Token | Was | Now (generated) |
|---|---|---|---|
ARCHITECTURE.md |
Python files / declarations / HTTP operations | 954 / 5,599 / 24 | 955 / 5,631 / 28 |
STATE.md |
Python files / declarations | 954 / 5,599 | 955 / 5,631 |
docs/README.md |
Python files / declarations | 954 / 5,599 | 955 / 5,631 |
docs/README.md |
environment readers | 93 | 107 |
docs/MIGRATION_STATUS.md |
files / lines / declarations / HTTP operations | 954 / 198,471 / 5,599 across 807 / 24 | 955 / 199,204 / 5,631 across 808 / 28 |
docs/WHITEPAPER.md |
files / lines / declarations | 954 / 198,471 / 5,599 | 955 / 199,204 / 5,631 |
docs/FEATURES.md |
declarations | 5,599 | 5,631 |
docs/README.md's environment-reader count is a new find: the 2026-08-11
lifecycle-closure entry corrected that token in README.md,
docs/CONFIGURATION.md and docs/MIGRATION_STATUS.md but missed this file,
which still read 93. It is not covered by check_current_inventory(), same
root cause as the six tokens that entry recorded.
Reviewed and found already correct — deliberately not edited: README.md,
docs/API.md, docs/CONFIGURATION.md, CHANGELOG.md, and DECISIONS.md
already document the generic extension surface. docs/API.md's error table was
checked row by row against ExtensionErrorCode and _STATUS_BY_CODE and its
ten codes and statuses match, including the transport-level
extension_management_disabled → 403 that lives in the router rather than the
loader; its kinds table matches EXTENSION_KINDS. The closure rule "no change
is acceptable if already correct" was honored.
Every command-line gate was again unavailable in this session.
.venv/bin/python, python3, scripts/check_documentation.py,
scripts/lint-project-memory.sh, scripts/check_brand_identity.py, pytest,
ruff, compileall, the package build, npm audit, and git all required an
approval this session could not obtain; the two declared project actions above
were the only execution route. The flyto-index CLI and the flyto-indexer
MCP verify tools were likewise unavailable, so strict full-scan Indexer
verification was NOT run.
The 27 inventory tokens that check_current_inventory() pins across the seven
prose files were matched by hand, byte-for-byte, against its exact expected
strings and the regenerated headers. That is a reading, not a run: the
documentation gate itself has not been executed against this tree, and it
is the only check that settles whether docs/reference/ is current. Re-run
scripts/check_documentation.py before treating this as closed.
Unchanged by this pass: the extension suite
(tests/core/api/test_extensions.py) has still never been executed, so the
generic extension management change remains functionally unverified — see the
entry immediately below. This pass closed its documentation debt only.
No commit, push, or deployment was made.
Scope: src/core/plugin/loader.py, src/core/api/routes/extensions.py,
src/core/api/routes/__init__.py, src/core/api/server.py,
tests/core/api/test_extensions.py, .flyto/coding.yaml, and the prose
surfaces (README, docs/API.md, CHANGELOG, DECISIONS, this file).
Every executable gate was skipped: command execution was unavailable in the session that made these edits. Nothing below has been observed to pass. Treat the change as unverified until the gates are run.
Skipped, and required before this is releasable:
.venv/bin/python -m pytest -p no:cacheprovider --no-cov -q tests/core/api/test_extensions.py(extension_management) — the new suite has never been executed..venv/bin/python -m ruff checkover the five files inlint_extensions..venv/bin/python -m compileall -q src— no syntax check was run..venv/bin/python scripts/generate_reference.pythen.venv/bin/python scripts/check_documentation.py. This change adds four HTTP routes and one environment reader (FLYTO_EXTENSIONS_INSTALL_ENABLED), sodocs/reference/http-api.md,docs/reference/configuration.mdanddocs/reference/python-api.mdwere known stale and the documentation gate was expected to fail until the generator was re-run. The generator half is now done — see the documentation-closure entry above, which regenerateddocs/reference/and corrected the prose counts that moved with it.scripts/check_documentation.pyitself is still unrun.bash scripts/lint-project-memory.sh,python scripts/check_brand_identity.py, and the offline suite-m 'not browser and not e2e'— the last matters becauseGET /v1/infonow advertises anextension_managementcapability.
Design intent that the suite is written to pin, for whoever runs it: prefix and
entry-point-group admission with no per-extension branch; argv-only pip with a
scrubbed environment; stable error codes with no subprocess output in any
response; entry-point proof; rollback of a failed new install but not of a failed
upgrade; restart_required on upgrade; auth on all four routes plus the operator
opt-in on the mutating two. The suite performs no network I/O — pip is
intercepted at subprocess.run and entry-point reads are served from a fake
group.
Scope: a read-only closure pass over the lifecycle edits already in
src/core/runtime/invoke.py, src/core/runtime/manager.py, and
src/core/runtime/exceptions.py, plus the prose inventory surfaces. No source
file was changed: the three runtime modules and their suites were re-read
against the three closure claims and found already correct, so the closure rule
"no source change is acceptable if already correct" was honored.
What was re-read and confirmed, by reading rather than by running:
- Malformed manifest shapes fail closed.
_require_sequencerejects string/bytes/mapping before iteration,_manifest_step_policyrejects a non-step entry and a non-string permission, and every raising path in_policy_denial(lookup, field read, malformed shape) returns a denial that names the plugin and never interpolates the cause.tests/core/test_runtime_policy_gate.pypins all of these, including the no-leak assertions. - invoke/start/idle-stop/unload/shutdown are race-safe.
_registry_lockguards the registry,info.lockserializes lifecycle transitions,claim/releaseorder the timestamp before the count,_drainis bounded, and both re-read the registry under the lock.tests/runtime/test_manager.pypins each interleave with events rather than sleeps. - Docs report 5,599, confirmed by an executed generator, not by reading.
What was executed here, and what it returned:
| Check | Result |
|---|---|
generate_reference (declared project action) |
exit 0 — 5,599 declarations, 954 source files |
Real gaps found and fixed (documentation only, no source):
Six prose inventory tokens had drifted from the generated reference and are not
covered by check_current_inventory(), which is why they went stale unnoticed —
that gate pins module/recipe/declaration/registration counts but not line,
route, or environment totals.
| File | Token | Was | Now (generated) |
|---|---|---|---|
docs/WHITEPAPER.md |
maintained Python lines | 197,902 | 198,471 |
docs/MIGRATION_STATUS.md |
maintained Python lines | 197,902 | 198,471 |
docs/MIGRATION_STATUS.md |
static HTTP operations | 22 | 24 |
docs/MIGRATION_STATUS.md |
environment-variable names | 93 | 107 |
docs/CONFIGURATION.md |
environment-variable names | 93 | 107 |
README.md |
environment readers | 93 | 107 |
The HTTP-operation and environment-name corrections are the same ones the
2026-08-11 stale-refresh entry recorded as applied to ARCHITECTURE.md; that
pass missed docs/MIGRATION_STATUS.md, docs/CONFIGURATION.md, and
README.md. All six now match docs/reference/ as regenerated in this session.
Strict Indexer post: NOT RUN — blocked on authorization. The exact target
(flyto-index verify . --full-scan --strict over the lifecycle and docs
surfaces) could not be executed. mcp__flyto-indexer__verify and
mcp__flyto-indexer__verify_workspace are not authorized in this session, and
the flyto-index CLI and every other command-line route
(pytest, ruff, scripts/check_documentation.py,
scripts/check_brand_identity.py, scripts/lint-project-memory.sh, build,
npm audit) require an approval this session could not obtain. The declared
project action above was the only execution route available. This entry
therefore remains PARTIALLY VERIFIED: treat the strict Indexer receipt and every
command-line gate as outstanding, and re-run them before this is released.
Scope: the fail-closed manifest-shape check in src/core/runtime/invoke.py, the
PluginManager lifecycle work in src/core/runtime/manager.py, the new
PluginManagerShutdownError, the extended suites
(tests/runtime/test_manager.py, tests/core/test_runtime_policy_gate.py), the
two new required checks in .flyto/coding.yaml, and the regenerated
docs/reference/ plus the six prose inventory surfaces.
What was executed here, and what it returned:
| Check | Result |
|---|---|
generate_reference (declared project action) |
exit 0 — 5,599 declarations, 954 source files |
generate_catalog (declared project action) |
exit 0 — 468 modules across 85 categories |
What was NOT executed, and must be run before this is treated as released:
compile,lint,lint_runtime_invoke,lint_runtime_manager,generated_reference(scripts/check_documentation.py),registry_plugin_contract,runtime_manager_lifecycle, andtests— every command-line gate. The session running this work could not execute.venv/bin/python; only the two declared project actions above were available.scripts/check_brand_identity.pyandscripts/lint-project-memory.sh, for the same reason.- Strict full-scan Flyto2 Indexer verification: the
flyto-indexerMCP tools were not authorized in that session.
The six inventory tokens were matched by hand against
check_current_inventory()'s exact expected strings and the regenerated
docs/reference/python-api.md header (**5,599 declarations across 807 files**). That is a reading, not an execution: the documentation gate itself
has not been run against this tree.
Scope: the capability-manifest cache ordering fix, its regression test, the
regenerated docs/reference/, the widened .flyto/coding.yaml lint surface,
and the prose inventory tokens. Registry/plugin work and unrelated edits were
left as they stood.
What was executed here, and what it returned:
| Check | Result |
|---|---|
generate_reference (declared project action) |
exit 0 — 5,584 declarations, 954 source files |
generate_catalog (declared project action) |
exit 0 — 468 modules across 85 categories |
Those two are the only commands this session could run. The declared project
actions were the sole execution route available: pytest, ruff,
compileall, scripts/check_documentation.py and the Indexer MCP tools were
all denied here, as they were for the sessions that built this change.
Not run in this session, and therefore not claimed green: project_memory,
compile, lint, generated_reference, registry_plugin_contract, and
tests — the six pinned checks in .flyto/coding.yaml — plus strict Indexer
verification, the package build, and npm audit.
The declaration total moved 5,572 → 5,584 across 953 → 954 maintained Python
files (807 of them now carrying declarations, up from 806), which is the
capability-manifest module and the methods added with it. The six prose
inventory files — ARCHITECTURE.md, STATE.md, docs/README.md,
docs/MIGRATION_STATUS.md, docs/WHITEPAPER.md, docs/FEATURES.md — were
updated to those figures, which is what the preceding audit round found stale.
ARCHITECTURE.md's HTTP-operation and environment-name counts were corrected
to the generated 24 and 107 in the same pass.
The regression test for the stale-store race is deterministic by construction — the interleave is forced with events rather than sleeps — but it has not been executed here, so it is a written test, not a passing one. Treat every correctness claim in this entry as read from source, not as a green run.
No commit, push, deployment, or hardware claim is made.
The registry plugin-load transaction and the return-value closure that followed it are verified and accepted. This supersedes the "NOT verified" status the three 2026-08-11 entries below were written under; those entries remain as the record of how the change was built, not of its current status.
Accepted on branch main.
Receipts. The revisions are flyto coding implementation revisions (SHA-256), not Git commit hashes:
| Covers | Acceptance job ID | Accepted implementation revision (SHA-256) |
|---|---|---|
| Documentation | job_453f3754aa2041309060b75a |
ebeb0ebfcab2d56bec576a944dcadd23fa197ff9726c558379df1c76eb12e341 |
| Source and tests | job_ad0baf4f580e4bc6aaac37de |
b391189517db77146c4ab51def48ed7ada04fb30308296480e2e083df46bf65c |
| Generated catalog and tests | job_8d8d49019afa402a8c503aa0 |
a08df544401cf36a54dfe4f6fc084512cb3035a9febf885442baca5cd8366f15 |
Passed against those receipts: the six pinned checks in .flyto/coding.yaml
(project_memory, compile, lint, generated_reference,
registry_plugin_contract, tests), the Core module-contract proof
(flyto.core.module-contract.v1), strict Indexer verification, and an
independent replay of 78 registry tests and 25 catalog tests.
REGISTRY_VERSION is 1.4.0.
Provenance of this record. The acceptance above was produced by the audited
acceptance run against those receipts; it was reported into this session rather
than re-executed here. This session could execute only the two declared project
actions, as prior sessions on this work could: generate_reference (exit 0 —
5,572 declarations across 953 files) and generate_catalog (exit 0 — 468
modules across 85 categories), both matching the figures already recorded.
That agreement is weaker evidence than this entry originally claimed, and the
claim is withdrawn. The declared actions run in the isolated project-action
sandbox, not against this checkout: the 2026-08-11 closure session observed
generate_catalog reporting Generated /workspace/docs/TOOL_CATALOG.md, and
git status was byte-for-byte unchanged after both actions ran. Stable figures
therefore show the generators are deterministic and that the sandbox copy has
the recorded shape; they do not establish that docs/reference/ and
docs/TOOL_CATALOG.md in this working tree are current. Only the pinned
generated_reference check (scripts/check_documentation.py) settles that, and
it is unrun here. The six pinned checks, the Core proof and
the Indexer strict run were not re-run in this session — pytest,
compileall, ruff and the Indexer MCP tools were all denied here. No commit,
deployment, or hardware claim is made.
Still open, tracked separately: the out-of-process PluginService / runtime
plugin lifecycle. That is this change's remaining scope, not a claim that it is
the last open plugin surface — the DRAFT flyto.plugin.v1 manifest and the
uncalled RuntimeInvoker.set_plugin_manager are documented open in
docs/specs/PLUGIN_MANIFEST_SPEC.md and were neither touched nor assessed here.
Status at the time of writing: not verified. Build record only — the change is now verified and accepted as registry 1.4.0. Do not read the status lines below as current.
Closes the two gaps the 1.3.0 audit left in this tree. Like every session on
this work, it could execute only the two declared project actions; no check in
.flyto/coding.yaml was available, so the change is unverified.
discover_plugins()returns_plugins.copy()on all three paths — the reentrant answer to plugin code, the already-initialised fast path, and the completed pass — sorefresh(), which returnsdiscover_plugins(force=True), is copied too.PluginInfois frozen, which closes the route the shallow copy leaves open: the values in the copy are the registry's own objects, so an edit to one was an unlocked write to what the registry says a plugin contains.- The TOCTOU regression
(
test_a_forced_pass_waits_for_a_reader_that_is_past_the_fast_path) now sets aforcer_enteredevent immediately beforediscover_plugins(force=True)and waits on it before asserting the forced pass is blocked. Without it the negative assertion could pass because the forcing thread had not started yet rather than because the lock held it. - Six new tests in
tests/core/test_plugin_policy_scope.py: no discovery path returns the live dict; a caller cannot edit the registry through the mapping; a plugin cannot empty the record by clearing what it was handed mid-pass; a caller cannot edit it through aPluginInfovalue; a new pass replaces aPluginInforather than editing one; a retained mapping does not change under its caller. - Ran and passed:
generate_reference(5,572 declarations across 953 files), run after the final source edit, sodocs/reference/python-api.mdanddocs/reference/source-modules.mdmatch the tree as it stands. It imports and exercises the modified registry end to end, so the frozenPluginInfoand the copied returns are import-clean and first-party registration is undisturbed. That is the only execution evidence. The declaration total was already 5,572 in this tree while the six pinned prose tokens still read 5,570; all six —ARCHITECTURE.md,STATE.md,docs/README.md,docs/MIGRATION_STATUS.md,docs/WHITEPAPER.md,docs/FEATURES.md— now match the generated reference. - Not run, status unknown:
project_memory,compile,lint,generated_reference,registry_plugin_contract,tests. Also not run: brand identity, npm audit, package build/Twine, and Indexer verification.
Status at the time of writing: not verified. Build record only — the change is now verified and accepted as registry 1.4.0. Do not read the status lines below as current.
Two defects were closed on top of the plugin-load transaction work below. Like
it, they could not be run: this session also had only the two declared project
actions, and every check in .flyto/coding.yaml was unavailable.
- The host/container catalog disagreement.
generate_catalog --checkpassed at 468 modules inside the clean release container and failed on a developer host. The catalog is rendered from the liveModuleRegistry, which is deliberately open to any distribution declaring aflyto.modulesentry point, so a host with a module pack installed generated a catalog carrying that pack's modules.scripts/generate_catalog.pynow skips rows whose registry owner is a plugin (_is_plugin_owned), so the file is a property of this source tree and not of the machine. The generated header says so. - First discovery is now serialised.
_ensure_discoveredanswered any caller that arrived mid-pass from the half-built registry, so two threads in one process could takeRegistrySnapshots with differentmodule_countandmodules_hashfor the same install. A reentrant_discovery_lockplus a recorded_discovery_threadnow separate the two callers: the discovering thread re-entering through a plugin is still answered from the partial state (the only answer that cannot deadlock), and every other thread waits and is handed the finished registry._ensure_discoveredasks whether a pass is in flight before whether the registry is initialised: a forced rediscovery rebuilds an already-initialised registry and never lowers_initialized, so the opposite order sent readers past the lock and into the rebuild — which is everyrefresh(), not a corner case.refresh()also holds the lock acrossclear()+ rediscover so the empty gap between them is not observable. Rollback, ownership andclear()semantics are untouched. - Known residual, deliberately not closed here: a reader releases the lock
before copying
_modules, so a forced pass starting inside that window can still be observed torn. Closing it means holding the lock across every reader's copy, which is a wider change than this fix and is recorded intasks.mdrather than done silently. REGISTRY_VERSIONmoved 1.1.0 → 1.2.0, with the pinned assertion intests/core/test_plugin_policy_scope.pymoved deliberately alongside it: a checkpoint carrying 1.1.0 cannot be assumed to have been matched against a complete registry.- Ran and passed:
generate_catalog(468 modules / 85 categories — unchanged, which is the evidence that the exclusion drops no first-party module) andgenerate_reference(5,570 declarations across 953 files). The declaration total moved 5,568 → 5,570 for the two added helpers (ModuleRegistry._discover_locked,generate_catalog._is_plugin_owned); all six pinned prose inventory tokens were moved with it. - New tests: nine thread-safety cases in
tests/core/test_plugin_policy_scope.py(reentrancy without deadlock, no nested pass, a concurrent read and a concurrent snapshot seeing the whole registry, one pass for four concurrent first reads, owner id not leaked, and three more that begin from an initialised registry and run a slow forced pass — the case_initialized-first ordering let through), and four catalog cases intests/core/test_catalog_determinism.pydriven by asitecustomizeshim that installs a realflyto.modulesentry point, one of which is a guard proving the shim still registers something. - Not run, status unknown:
project_memory,compile,lint,generated_reference,registry_plugin_contract,tests. Also not run: brand identity, npm audit, package build/Twine, and Indexer verification.
Status at the time of writing: not verified. Build record only — the change is now verified and accepted as registry 1.4.0. Do not read the status lines below as current.
The change to src/core/modules/registry/core.py and
tests/core/test_plugin_policy_scope.py described under Current State has not
been run. Successive sessions on it could execute only the two declared project
actions; every check in .flyto/coding.yaml was unavailable to them, including
the pinned registry_plugin_contract Core proof. Nothing below is a substitute
for running them, and the change should not be treated as released until they
are.
- Ran and passed:
generate_catalog(468 modules / 85 categories, unchanged) andgenerate_reference(5,568 declarations across 953 files, 806 of them declaration-bearing). Both import and exercise the modified registry, so the change is import-clean and does not disturb first-party registration or the generated catalog. - The reference was regenerated after the final source edit, so
docs/reference/python-api.mdanddocs/reference/source-modules.mdmatch the tree as it stands. The declaration total moved 5,567 → 5,568 because the fix adds one method (ModuleRegistry._note_pass_touch); the six prose inventory tokens thatscripts/check_documentation.pypins to that total —ARCHITECTURE.md,STATE.md,docs/README.md,docs/MIGRATION_STATUS.md,docs/WHITEPAPER.md,docs/FEATURES.md— were all moved with it. REGISTRY_VERSIONmoved 1.0.5 → 1.1.0. It is a contract version carried in everyRegistrySnapshot, and rollback becoming total changed what a caller may conclude from a registry that survived a failed load, so a resumed checkpoint must be able to tell the two apart.clear()is now pass-aware, closing an ownership escalation. A plugin whoseregister_allcalledModuleRegistry.clear()had the loading owner reset mid-pass, so every module it registered afterwards was stamped with no plugin at all — first-party, the one identity the process-global permission grant reaches. Inside a pass the owner and the rollback ledger are now kept, and the rows the clear drops are banked first, so a pass that wipes the registry and then raises is rolled back whole rather than against an empty ledger._load_pluginalso restores the entire prior_pluginsmap instead of the failing entry point's single line, since a wipe takes every plugin'sPluginInfowith it. Outside a passclear()is byte-identical to before.- Not run, status unknown:
project_memory,compile,lint,generated_reference,registry_plugin_contract,tests. Also not run: brand identity, npm audit, package build/Twine, and Indexer verification. - The coverage-floor obstacle previously recorded here is resolved: the pinned
registry_plugin_contractargv in.flyto/coding.yamlnow carries--no-cov(and-p no:cacheprovider), so the check no longer inherits the 60% gate frompyproject.tomland a non-zero exit is once again a real defect signal rather than an artefact of running one file against a whole-suite floor.
Verified locally on 2026-08-08 for the 2.27.0 release candidate — the full
closure in docs/TESTING.md, every gate run, none skipped silently:
- documentation contract, brand identity, project-memory lint, generated catalog (468 modules / 85 categories), generated reference (5,559 declarations across 806 files), and the new security-status check (24 advisories) all passed;
- audited-surface Ruff (the CI list plus
generate_security_status.py) passed with zero findings; - 2,467 tests passed, 13 skipped, 273 deselected, with 61.63% coverage against the 60% control-kernel gate;
requirements.lockregenerated and unchanged (the only diff was the pip-compile generator's Python 3.11 → 3.12 header comment, reverted; no dependency moved),pip-auditreported no known vulnerabilities,npm auditreported 0;- wheel and sdist built and Twine-validated. The wheel was then installed into
a clean venv and the boundaries were exercised against the installed
package, not the source tree:
/etc/passwdrefused and an in-sandbox path accepted; the metadata address, an RFC1918 host, and an IPv4-mapped IPv6 loopback literal each refused while loopback was accepted;redis://to the metadata address refused; and end to end,file.deleterefused/etc/hostsandssh.execrefused the metadata host. The installed registry reports 468 modules, matching the committed catalog; - Flyto2 Indexer strict full scan passed 19/19 checks;
- package and MCP registry metadata both resolve to
2.27.0.
Not run: browser and E2E suites (require browsers, services, or credentials).
actionlint was not re-run — no workflow file changed in this release.
Environment note: this machine's venv has transformers (via the vector
extra), which CI does not install. huggingface is an optional module category
(src/core/modules/atomic/__init__.py:_OPTIONAL_CATEGORIES), so generating the
catalog here would have advertised 475 modules / 86 categories instead of the
468 / 85 the released package actually exposes. Generated artifacts were
produced with transformers hidden so they match CI and the shipped wheel.
Verified locally on 2026-08-07 for the 2.26.12 release candidate:
- documentation, brand, generated catalog/reference (5,558 declarations across 805 files, regenerated after the fix set), and both the CI's fixed audited-surface Ruff list and a full changed-surface Ruff diff (every file touched by this release, compared byte-for-byte against its pre-fix baseline) passed with zero new findings;
- 2,615 tests passed, 18 skipped, with 64.42% coverage (the Juice Shop container e2e test is excluded, same as CI's own gate);
- Python
pip-audit(against a freshly regeneratedrequirements.lockadding theregexdependency) and npm audit both reported 0 vulnerabilities; - wheel and source distribution built, and Twine validated both artifacts; additionally, the built wheel was installed into a clean venv and the two named advisories plus the four same-class findings (below) were each re-exercised directly against the installed package (not the source tree) to confirm the shipped artifact — not just the source — carries the fix;
- Flyto2 Indexer strict full scan passed 19/19 checks with 0 warnings/failures, docs score 100, README score 100, 0 secret findings, and 0 high-risk taint flows;
- package, MCP registry, and changelog version metadata all resolve to
2.26.12.actionlintwas not re-run — no workflow file changed in this release.
Remote CI note: the v2.26.12 tag and its release commit (989f3db)
initially failed remote CI twice — a requirements.lock transitive-pin
drift (soupsieve resolved to a newer patch between local lock and CI's
own re-lock) and a stale doc regeneration (one file's line numbers hadn't
been refreshed after a later same-day edit) — plus a separately-failing
npm audit gate on 5 pre-existing Dependabot alerts for undici (a JS
devDependency used only by the test_hints.py harness, never shipped in
the wheel). None of the three affected the published package's actual
content, confirmed by re-downloading the live PyPI wheel and diffing its
screenshot.py against source. Three follow-up commits on main
(5067f54, b32d36c, a0587eb) fixed all three; remote CI is green as of
a0587eb. No new PyPI version was needed since nothing shippable changed.
Closes the two publicly-reported advisories plus four same-class findings surfaced while scoping the first (CWE-22, unvalidated caller path to a write sink — the exact pattern the advisory calls out as recurring wave-over-wave):
browser.download(GHSA-p64w-hgfm-824v, critical) and, found in the same sweep,browser.screenshot,browser.pdf,warroom.report,verify.report(+ unescaped HTML),verify.visual_diff,verify.run,browser.launch'srecord_video_dir, anddata.dedup'shash_file— all now confined toFLYTO_SANDBOX_DIR.browser.goto's www-toggle retry (GHSA-662f-hr85-mg6c, high) — the toggled host is now revalidated against the SSRF guard before navigating, plus a driver-level_guard_navigation()defense-in-depth layer.- Also included: Tar Slip in
archive.tar_extract(GHSA-pxvx-67rw-8352, high), theport.checkIPv6-transition SSRF fail-open (GHSA-v7q9-pr72-5fmv, medium), and regex ReDoS inregex.*(GHSA-v468-p4jx-7vj3, medium).