Skip to content

docs: 0.18.0 contract confinement — $ref root, DuckDB sandbox, contract-loading API - #139

Merged
fas89 merged 5 commits into
mainfrom
docs/0.18.0-contract-confinement
Oct 3, 2026
Merged

fas89 merged 5 commits into
mainfrom
docs/0.18.0-contract-confinement

Conversation

@fas89

@fas89 fas89 commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

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.

  • $ref confinement (#687). A $ref can compose only files inside the contract's own directory tree. A monorepo can widen the root explicitly with FLUID_REF_ROOT.
  • DuckDB sandbox (#689). Contract SQL runs inside DuckDB's own sandbox:
    • external access is limited to an allowlist of directories
    • autoload and autoinstall are off
    • there are no persistent secrets and no community extensions
    • lock_configuration = true, so contract SQL cannot run SET or PRAGMA
    • FLUID_DUCKDB_ALLOWED_DIRS adds directories
  • Contract-loading API (#688). fluid_build.api.load_contract, load_contract_from_text and load_contract_from_dict load a contract exactly as fluid plan sees it. The public API version goes to 1.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_contract and 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 at 1.1 as of CLI 0.18.0, and 1.1 added the contract-loading API. The example output changes from "1.0" to 1.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-tests does not run DuckDB. The DuckDB-backed local actions live in the legacy module fluid_build.contract_tests, and no fluid command uses that module. The table and the release notes say the same thing.

Tested

  • npm run docs:build exits 0 and renders 181 pages.
  • No broken-link warning names a page this PR adds or edits. The warnings that remain were already there, on pages this PR does not touch.

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.

fas89 added 5 commits October 2, 2026 20:29
…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.
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.
@fas89
fas89 merged commit 123a150 into main Oct 3, 2026
9 checks passed
@fas89
fas89 deleted the docs/0.18.0-contract-confinement branch October 3, 2026 01:01

This branch was successfully deployed

1 active deployment
github-pages — dec7dfad Deployed Oct 3, 2026 by fas89 via deploy #169
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.

1 participant