docs: 0.18.0 contract confinement — $ref root, DuckDB sandbox, contract-loading API - #139
Merged
Merged
Conversation
…ct-load API Companion to forge-cli #687, #688 and #689, which release together as 0.18.0. Adds four pages and one separate sidebar group; no existing page, heading or the pinned CLI version is changed. - concepts/contract-refs.md: $ref composition, the ref root, FLUID_REF_ROOT and ref_root=, the monorepo migration, OAS-REF-EXTERNAL - advanced/duckdb-sandbox.md: what contract SQL can reach, declared locations, FLUID_DUCKDB_ALLOWED_DIRS, the duckdb>=1.5.0 floor, limits - advanced/contract-loading-api.md: fluid_build.api.load_contract, load_contract_from_text/_dict, LoadedContract, ContractLoadError (API 1.1) - RELEASE_NOTES_0.18.0.md: breaking changes and migration steps Example output was produced by running the 0.18.0 code.
…EXTERNAL, allowlist scope - contract-loading-api: FLUID_REF_ROOT reaches load_contract only; the in-memory forms always confine refs to base_dir. - release notes + duckdb-sandbox: contract SQL can no longer read http(s)/gs/Azure URLs, declared or not; only declared s3:// is reachable. New upgrade-table rows and migration sections for that and for OAS-REF-EXTERNAL. - duckdb-sandbox: split the INSTALL/LOAD refusal rows, scope the declared roots list to embedded-SQL builds (acquisition builds are narrower), note cwd-relative SQL paths, and warn that an allowed directory is readable in full by any contract that declares a glob in it.
…nt, OAS ref history, api 1.1 - Release notes: OAS-REF-EXTERNAL history stated per ref kind (file:// and http(s):// were followed; relative refs were already OAS001; a $ref key in an example or x-* payload is the new failure); SET/PRAGMA of a DuckDB setting is a breaking change; contract-tests local actions are confined to the working directory or FLUID_DUCKDB_ALLOWED_DIRS; the unshipped 'configuration has been locked' cascade fix is dropped, kept as a sandbox property (each action's connection is closed even when it fails). - DuckDB sandbox: SET/PRAGMA in the breaking box and the refusal table; both real errors for an https:// URL (with and without a declared s3:// location); contract-tests row names the confinement. - api-stability: fluid_build.api is at 1.1 on 0.18.0, which added the contract-loading API. - contract-refs: the ref/pointer/file triple is scoped to confinement refusals. - contract-loading-api: real loaded.files value and env refusal message. - Threat model stated generically on every page.
…failing cases, print(err) fluid contract-tests never opens DuckDB, so the migration row and section for its local actions described a change no user can hit. Mention the confinement only as hardening of the legacy fluid_build.contract_tests module. Name every case where a previously valid bundle now fails OAS-REF-EXTERNAL, and show the env-refusal message via print(err) so the backslash is literal.
…act_tests module, not fluid contract-tests
This was referenced Oct 2, 2026
fas89
added a commit
to Agenticstiger/forge-cli
that referenced
this pull request
Oct 2, 2026
Adds fluid_build.api.load_contract (a contract file or a `fluid bundle` .tgz) and two in-memory forms, load_contract_from_text and load_contract_from_dict. Each returns a LoadedContract: - contract: the dict `fluid plan` plans, after parsing, $ref composition, the env overlay, alias rewrites and the legacy build: rewrite, in the engine's order. - digest: the plan-digest canonicalisation of that dict. - the files composed into it. Failures raise a typed ContractLoadError with a stable `event`. `env` must be an environment name, never a path: the engine builds overlay paths from it. The in-memory forms follow the engine's overlay-drop rule, and they unshare YAML aliases so that a rewrite cannot leak into aliased nodes. fluid_build.api is now version 1.1. A guard test fails if a new engine step can reach `contract` without going through this module, and it is negative-controlled over the engine's real source. With $ref confinement (#687) on main, a $ref holding a NUL byte is a typed ref error (contract_ref_unresolved), and the tests pin that. Docs: docs/CONTRACT_LOADING_API.md; the companion forge_docs page is in Agenticstiger/forge_docs#139.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
These are the companion docs for forge-cli #687, #688 and #689, which ship in CLI
0.18.0. After 0.18.0, a contract can no longer make the engine read the machine it runs on.$refconfinement (#687). A$refcan compose only files inside the contract's own directory tree. A monorepo can widen the root explicitly withFLUID_REF_ROOT.lock_configuration = true, so contract SQL cannot runSETorPRAGMAFLUID_DUCKDB_ALLOWED_DIRSadds directoriesfluid_build.api.load_contract,load_contract_from_textandload_contract_from_dictload a contract exactly asfluid plansees it. The public API version goes to1.1.These changes matter most to services, CI jobs or shared hosts that run contracts other people wrote. Before 0.18.0, such a contract could make the engine read files and URLs its operator never meant to expose.
Pages
New pages:
docs/concepts/contract-refs.md: composing a contract with$ref. Covers the ref root, what a refused ref looks like, widening the root for a monorepo, catching the error in Python, and OpenAPI fragments inside a bundle.docs/advanced/duckdb-sandbox.md: what SQL is refused, what each kind of SQL can reach, how declared locations stay inside the allowed directories, reading a file from another directory,FLUID_DUCKDB_ALLOWED_DIRS, how it works, and its limits.docs/advanced/contract-loading-api.md:load_contractand its siblings,LoadedContract,ContractLoadError, and how to check whether a contract is the one a plan was made from.docs/RELEASE_NOTES_0.18.0.md: upgrade notes. Covers breaking changes with a migration step for each, Security, and Added.Sidebar:
docs/.vuepress/config.ts: one new sidebar group, "Contract loading & sandboxing", that lists the four pages above. It is a group of its own so it merges cleanly with edits to the other lists.Edit to an existing page (the only one):
docs/advanced/api-stability.md: a version note. The public API is at1.1as of CLI0.18.0, and 1.1 added the contract-loading API. The example output changes from"1.0"to1.1.No page or routed heading is moved, renamed or deleted. The pinned CLI version file is not touched.
Accuracy
Every example output on these pages came from running the example against a build that merges all three forge-cli PRs. None of the output was written by hand.
The "What each kind of SQL can reach" table credits DuckDB scope only to code that runs DuckDB.
fluid contract-testsdoes not run DuckDB. The DuckDB-backed local actions live in the legacy modulefluid_build.contract_tests, and nofluidcommand uses that module. The table and the release notes say the same thing.Tested
npm run docs:buildexits 0 and renders 181 pages.Merge order
Merge after forge-cli #687, #688 and #689 are released as
0.18.0.Merge order
Opened alongside forge-cli Agenticstiger/forge-cli#687, #688, #689 (the CONTRIBUTING companion-docs rule). Merge after data-product-forge 0.18.0 is on PyPI, so the site never documents unreleased behaviour.