Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 74 additions & 6 deletions .github/workflows/docs-pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,78 @@ jobs:
- name: Install dependencies
run: npm ci

# The build is the gate. docs/.vuepress/config.ts sets the theme's
# linksCheck plugin to `build: 'error'`, so VuePress throws and exits
# non-zero on a dead internal link. Nothing here greps the log: a
# log-string check passes whenever the message wording changes, and it
# passed for exactly that reason before.
- name: Build docs (fails on broken internal links)
# The build catches ONE class of dead link, not all of them. See the
# coverage map on the next step before concluding anything is redundant.
#
# docs/.vuepress/config.ts sets the theme's linksCheck plugin to
# `build: 'error'`, so VuePress throws and exits non-zero on a dead
# internal link *that is written as a markdown link to a .md target*. The
# plugin filters candidates on /\.md(?:[?#]|$)/, which is about 861 of
# roughly 1331 internal links; it cannot see links in config.ts, in
# frontmatter, in raw HTML, or written with a .html / no extension.
#
# Nothing here greps the log: a log-string check passes whenever the
# message wording changes, and it passed for exactly that reason before.
- name: Build docs (fails on broken internal markdown links)
run: npm run docs:build

# ---------------------------------------------------------------------
# THE ARTIFACT GATE. Every other link gate in this repo reads SOURCE
# MARKDOWN. This one reads the bytes that get deployed.
#
# WHICH GATE COVERS WHICH CLASS — read this before deleting anything.
# The comment above this step used to call the build "the gate" while it
# covered about 65% of internal links, and a maintainer could reasonably
# have deleted link-check.yml believing it redundant. It is not.
#
# link class | build | base-prefix | lychee-source | THIS
# --------------------------------------------|-------|-------------|---------------|-----
# [x](./y.md) dead target | YES | - | YES | YES
# [x](/cli/y) missing /forge_docs/ base | - | YES | YES | YES
# navbar / sidebar entry in config.ts | - | - | - | YES
# docs/README.md frontmatter hero actions | - | - | - | YES
# raw <a href="..."> in markdown | - | - | YES | YES
# .html-suffixed and extensionless targets | - | YES | YES | YES
# theme-generated links (prev/next, edit) | - | - | - | YES
# a page that renders but is never emitted | - | - | - | YES
# asset <script src> / <img src> in dist | - | - | - | YES
# external URL reachability | - | - | - | - (link-check.yml `external`, weekly)
# #fragment / anchor targets | - | - | - | - (unguarded; see the script header)
# relative ./x refs in built HTML | - | - | partly | YES
#
# The lychee-source cell for the raw-HTML row is measured, not inferred:
# appending <a href="/cli/init.html"> to a page makes lychee report
# ".linkcheck-root/cli/init.html (at 3:10) | File not found". An earlier
# draft of this table put a dash there and so argued for keeping a sibling
# gate on a premise that was wrong — the same kind of misleading note this
# change set out to remove. Re-measure a cell before trusting it.
#
# So: THIS step is the only one that sees the config.ts, frontmatter and
# raw-HTML classes, and the only one that checks what actually shipped.
# The source gates in link-check.yml are the only ones that run on a
# source-only change without a build, and the only ones that name a
# REPOSITORY path rather than a dist path when they fail — which is what a
# contributor can act on. Keep both.
#
# The script refuses to report a clean result unless it can prove, in the
# same run, that it inspected the artifact: in-process canaries (one link
# that must resolve, one that must not, one missing the base prefix) run
# before the scan, and floors on pages walked, references examined and
# distinct targets resolved fail the job if any is implausibly low. The
# canaries are in-process rather than a separate step on purpose — a
# separate step can be deleted while the gate keeps reporting green.
#
# It prints its census (counts per resolution rule and per exclusion
# class), because "passed" with no numbers is indistinguishable from
# "inspected nothing". Read that output when reviewing this job.
#
# Verified on 92925e5: a one-character typo in the config.ts navbar link
# (/why -> /whyy) leaves `npm run docs:build` exiting 0 while shipping 424
# dead references across 212 of 219 pages. This step fails on it.
#
# No `continue-on-error` and no `|| true`. Deliberately NOT `if: always()`
# either: a failed build leaves no artifact, and measuring an absent or
# stale dist is the exact failure mode this exists to prevent. The script
# refuses to pass when dist is missing, so both directions are covered.
- name: Check the built artifact for dead internal links
run: node scripts/check-dist-links.mjs
60 changes: 60 additions & 0 deletions docs/.vuepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,66 @@ export default defineUserConfig({
// and the branded NotFound layout overrides the theme's default 404.
clientConfigFile: resolve(__dirname, './client.ts'),

// Monaco's CSS ships to every page. That is a RECORDED DECISION to leave it
// alone, not an oversight.
//
// Measured on a fresh build, 2026-09-15, monaco-editor as installed: the
// site emits exactly ONE stylesheet, assets/style-<hash>.css, 251,769 bytes,
// linked render-blocking from all 213 pages. Monaco is 162,004 of those
// bytes (64.3%), about 24 KB of the file's 41 KB gzipped. The editor is used
// on ONE page: `<Playground>` appears only in docs/playground/README.md.
//
// Monaco's JAVASCRIPT is split correctly, and that contrast is the whole
// point. Playground.vue reaches it through `await import('monaco-editor')`,
// so editor.api, vs and the four language workers land in async-only chunks
// totalling 13.1 MB that NO prerendered page references or prefetches (the
// shouldPrefetch filter below skips them by name). A visitor who never opens
// /playground/ fetches none of it. Only the CSS leaks.
//
// Cause: @vuepress/bundler-vite 2.0.0-rc.31 hard-sets `cssCodeSplit: false`
// inside its `build` block at
// node_modules/@vuepress/bundler-vite/dist/index.js:108. That is not a Vite
// default and not exposed as a bundler option. With CSS splitting off Vite
// has nowhere else to put style reachable only through an async-only chunk,
// so Monaco's sheet is concatenated into the single entry stylesheet even
// though its JS never is.
//
// Two fixes were considered and both declined:
//
// 1. Override it — viteBundler({ viteOptions: { build: { cssCodeSplit: true } } }).
// This does work mechanically: the bundler's own `vuepress:user-config`
// plugin is `enforce: 'post'`, so viteOptions win over line 108. Declined
// because it fights a deliberate framework choice about style ordering
// under static rendering. With one sheet, the prerendered HTML carries the
// <link> and cascade order is fixed at build time. Split per chunk, an
// async chunk's CSS is injected by the chunk loader after hydration and is
// referenced from no prerendered HTML, so theme-vs-editor precedence
// becomes load-order dependent and /playground/ shows an unstyled editor
// until its chunk lands.
//
// 2. Load Monaco's CSS at runtime — keep it out of the bundle and inject a
// <link> from Playground.vue. Declined because it means hand-managing what
// the bundler currently guarantees: emitting the file, hashing it for
// cache-busting, and ordering it after the theme.
//
// Before changing this, RE-MEASURE rather than trusting the numbers above.
// They move with every monaco-editor bump: earlier passes recorded 58.6% and
// 65.9% for what is 64.3% today.
//
// rm -rf docs/.vuepress/dist && npm run docs:build
// # one stylesheet, on every page -> 213
// grep -rl 'assets/style-.*\.css' --include='*.html' docs/.vuepress/dist | wc -l
// # Monaco's JS still async-only -> 0 pages reference a Monaco chunk
// grep -rlE 'assets/(editor\.api-|[a-z]+\.worker-)' --include='*.html' docs/.vuepress/dist | wc -l
// # Monaco's share of the sheet -> total, Monaco bytes, percent
// python3 -c "import glob;p=glob.glob('docs/.vuepress/dist/assets/*.css')[0];s=open(p,'rb').read();b=s.index(b'.monaco-aria-container');print(len(s),len(s)-b,round(100*(len(s)-b)/len(s),1))"
//
// That last command assumes Monaco's block is CONTIGUOUS and runs to EOF,
// which holds today: nothing after byte 89,765 contains `--vp-` and nothing
// before it contains `--vscode-`. Verify that assumption before believing
// the percentage, because an interleaved bundle would make it silently
// undercount and read as an improvement. Then confirm /playground/ still
// paints a styled editor with no flash.
bundler: viteBundler(),

// Both of these default to TRUE in VuePress. They were set to false in the
Expand Down
47 changes: 45 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,53 @@ These are the groups `fluid --help` prints on `0.15.0`. Run it yourself to confi

`--help` promotes a short surface, not the whole one. Commands such as `bundle`, `diff`, `verify`, `publish`, `runs`, `stats`, `ship` and [`mission`](/forge_docs/cli/mission.html) are real and documented, and `--help` itself names the production path as `bundle` → `validate` → `generate artifacts` → `diff` → `plan` → `apply` → `verify` → `publish`. See the [CLI Reference](/forge_docs/cli/) for everything.

::: tip Current release — `0.15.0`, schema **0.7.5** stable (GA)
`0.15.0` is the current release ([release notes](/forge_docs/RELEASE_NOTES_0.15.0.html)). Its headline is **sovereignty enforcement**: data-residency controls that previously reported clean while checking nothing now actually block, with `sovereignty.enforcementMode` driving severity in both directions, engine defaults realigned to the schema's, and the region→jurisdiction table derived from vendor data. A contract that passed [`fluid validate`](/forge_docs/cli/validate.html) on `0.14.1` can fail here. Recent releases below it: [`0.14.0`](/forge_docs/RELEASE_NOTES_0.14.0.html) was the **live-verification hardening** release (the dbt Iceberg loop reaching all three cloud warehouses); [`0.13.0`](/forge_docs/RELEASE_NOTES_0.13.0.html) brought **verifiable autonomy + declarative packaging** with the new [`fluid mission`](/forge_docs/cli/mission.html) command; [`0.12.0`](/forge_docs/RELEASE_NOTES_0.12.0.html) landed **dbt integration + schema GA**, promoting contract schema `0.7.5` to stable as the default for untagged contracts and shipping the brownfield [`fluid import dbt`](/forge_docs/cli/import.html) importer, the MetricFlow bridge and `fluid verify --reconcile-lineage`; `0.11.0` brought the **AI-ready / RAG** surface (vector output port preview, semantic-drift guard, `ai_ready` agent, LocalStack-compatible AWS apply); `0.10.0` matured the **plugin platform** (operator trust boundary via `FLUID_PLUGINS_ALLOWLIST` / `FLUID_PLUGINS_BLOCKLIST`, [`fluid plugins`](/forge_docs/cli/plugins.html) + [`fluid exporters`](/forge_docs/cli/exporters.html), and the `odps` / `odcs` provider→exporter reclassification); the streaming **Kafka → Iceberg sink** shipped in `0.9.0`; and the **MCP output-port gateway** — runtime `agentPolicy` enforcement with JWT-bearer + mTLS identity — arrived in `0.8.7` ([`fluid mcp`](/forge_docs/cli/mcp.html)). The platform builds on the **SDP / ADP / CDP** Data Mesh vocabulary alongside the medallion `Bronze / Silver / Gold` layers, **six ingestion engines** (`duckdb`, `dlt`, `meltano`, `airbyte`, `kafka-connect`, `debezium`), the guided `fluid forge` UX (mode picker, welcome scan, slash commands, preview panel), and a companion **SDK** (`data-product-forge-sdk`). See [SDK & Plugins](/forge_docs/sdk-and-plugins/), [Source-Aligned Acquisition](/forge_docs/advanced/source-aligned-acquisition.html), and [Product Types](/forge_docs/data-products/product-type.html) for the full picture.
:::: tip Current release — `0.15.1`, schema **0.7.5** stable (GA)
`pip install data-product-forge` gives you `0.15.1`. The `0.15.x` changes landed in `0.15.0`, which
documents them together with `0.14.1` in one baseline (there is no separate `0.15.1` page):
[`0.15.0` release notes](/forge_docs/RELEASE_NOTES_0.15.0.html).

**Coming from `0.14.1` or earlier? One thing can break you.** A contract that passed
[`fluid validate`](/forge_docs/cli/validate.html) on `0.14.1` can fail now, with no edit of yours.
`0.15.0` is the **sovereignty enforcement** release: residency controls that reported clean while
checking nothing now actually block.

- **`strict` blocks, `advisory` warns, `audit` informs.** `sovereignty.enforcementMode` drives
severity in both directions, and the engine's own defaults — previously the permissive inverse of
the schema's — now match the schema, where `strict` is the default.
- **Three regions changed jurisdiction.** The region→jurisdiction table is derived from the vendors'
own data instead of typed by hand, which is how it had placed London in the EU and treated
Singapore and Seoul as pass-anything wildcards. Re-validate any contract bound there.
- **A jurisdiction-pinned MCP output port refuses to start** on the default stdio transport, or on
HTTP with no auth mode configured: caller jurisdiction is enforced at query time, fail-closed, and
neither of those can prove where the caller is.

The notes carry the thirteen-step upgrade checklist and three further risks outside sovereignty.
`0.14.1`, in the same baseline, closed two HIGH authorisation bypasses in the MCP output port.

::: details Before that, each release keeping its own work
- [`0.14.0`](/forge_docs/RELEASE_NOTES_0.14.0.html) — live-verification hardening: the dbt Iceberg
loop reaching all three cloud warehouses.
- [`0.13.0`](/forge_docs/RELEASE_NOTES_0.13.0.html) — verifiable autonomy and declarative packaging,
with the [`fluid mission`](/forge_docs/cli/mission.html) command.
- [`0.12.0`](/forge_docs/RELEASE_NOTES_0.12.0.html) — dbt integration and schema GA: `0.7.5` promoted
to stable as the default for untagged contracts, plus the brownfield
[`fluid import dbt`](/forge_docs/cli/import.html) importer.
- [`0.11.0`](/forge_docs/RELEASE_NOTES_0.11.0.html) — the AI-ready / RAG surface: vector output port,
semantic-drift guard, `ai_ready` agent.
- [`0.10.0`](/forge_docs/RELEASE_NOTES_0.10.0.html) — plugin governance: an operator trust boundary
over plugins, with [`fluid plugins`](/forge_docs/cli/plugins.html) and
[`fluid exporters`](/forge_docs/cli/exporters.html).
- [`0.9.0`](/forge_docs/RELEASE_NOTES_0.9.0.html) — the streaming Kafka → Iceberg sink.
- [`0.8.6`](/forge_docs/RELEASE_NOTES_0.8.6.html) — the [`fluid mcp`](/forge_docs/cli/mcp.html)
output-port gateway: `agentPolicy` enforced at runtime, with JWT-bearer and mTLS identity.
:::

The vocabulary and the product types behind all of it:
[SDK & Plugins](/forge_docs/sdk-and-plugins/),
[Source-Aligned Acquisition](/forge_docs/advanced/source-aligned-acquisition.html),
[Product Types](/forge_docs/data-products/product-type.html).
::::

## Where to go next

- [Getting Started](/forge_docs/getting-started/) for the local-first path
Expand Down
2 changes: 1 addition & 1 deletion docs/RELEASE_NOTES_0.7.11.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## What changed in the docs

- Pinned the supported CLI version in [`docs/.vuepress/cli-version.json`](./.vuepress/cli-version.json). One file to bump per CLI release.
- Pinned the supported CLI version in [`docs/.vuepress/cli-version.json`](https://github.com/Agenticstiger/forge_docs/blob/main/docs/.vuepress/cli-version.json). One file to bump per CLI release.
- New CI workflow `cli-consistency.yml` installs the pinned CLI from PyPI and fails on docs ↔ CLI drift (version, command list, provider list).
- Documented every command registered by the CLI's `fluid_build/cli/bootstrap.py`. New pages:
- `demo`, `skills`, `ai`
Expand Down
Loading
Loading