DESIGN: Projdoc Autopublish¶
Status: DRAFT
Created: 2026-05-17
Scope: publish Fleet project design docs from local rendered HTML into the personal website under private /projdoc/* URLs.
1. Goal¶
Build a local autopublish path for project documentation so Fleet design docs can be read from https://fengshen.dev/projdoc/... without manually copying files between repositories.
The v1 target is deliberately narrow:
- Source docs live under
/Users/pinkbear/projects/fleet/docs. - v1 publishes HTML only.
- v1 source match is
DESIGN-*.html. - The website repo is
/Users/pinkbear/projects/fengshen-site. - The website is Astro, deployed by GitHub Actions to Cloudflare Pages at
https://fengshen.dev. - The website default branch is
master. - Generated canonical URLs use
/projdoc/<collection>/<slug>/. - Fleet docs publish as collection
fleet, for example/projdoc/fleet/dispatch-lifecycle/. /projdoc/is a clickable directory landing page.- Local sync runs as a macOS
launchdservice polling every 60s while the Mac user session is active. - Publishing uses one standing GitHub branch and PR, for example
auto/projdoc-sync, with checks, auto-merge, and the existing GitHub Actions deploy to Cloudflare. /projdoc/*is protected by Cloudflare Access.noindex,nofollowis backup only.
The implementation should make publishing boring: after scripts/render-design-doc.py docs/DESIGN-foo.md writes docs/DESIGN-foo.html, the local service notices the changed HTML, copies it into the Astro site, updates generated metadata, pushes to the standing branch, and lets GitHub checks plus auto-merge carry it to production.
2. Non-goals¶
- Do not publish Markdown in v1.
- Do not publish arbitrary docs in v1; only
DESIGN-*.html. - Do not rewrite the existing design-doc renderer as part of this work.
- Do not create public docs.
/projdoc/*is private behind Cloudflare Access. - Do not migrate or modify existing legacy website paths in v1.
- Do not touch
/projdoc/design/. - Do not touch
/projdoc/rc-listener-lifecycle/. - Do not build a web editor, comment system, search index, RSS feed, or analytics path.
- Do not introduce a new deploy provider. Deployment remains GitHub Actions to Cloudflare.
- Do not require a long-running server process. A launchd-driven polling command is enough.
- Do not use direct Cloudflare API publishing from the local machine in v1.
3. Current baseline¶
Fleet already has a local Markdown-to-HTML design-doc renderer:
cd /Users/pinkbear/projects/fleet
python3 scripts/render-design-doc.py docs/DESIGN-dispatch-lifecycle.md
The renderer writes a sibling .html file with inline CSS and no external assets:
/Users/pinkbear/projects/fleet/docs/DESIGN-dispatch-lifecycle.md
/Users/pinkbear/projects/fleet/docs/DESIGN-dispatch-lifecycle.html
Current Fleet source docs relevant to v1 are:
/Users/pinkbear/projects/fleet/docs/DESIGN-*.html
The site repo is:
/Users/pinkbear/projects/fengshen-site
The site is Astro. The deployment target is:
https://fengshen.dev
The site default branch is:
master
The expected deploy flow is:
local Fleet docs
-> local sync into fengshen-site
-> push auto/projdoc-sync
-> standing GitHub PR
-> checks
-> auto-merge to master
-> GitHub Actions deploy
-> Cloudflare Pages
The baseline has at least two existing legacy paths that must remain untouched in v1:
/projdoc/design/
/projdoc/rc-listener-lifecycle/
These paths may be backed by existing files or Astro routes in fengshen-site; the sync service must avoid deleting, moving, or overwriting them.
4. Published URL model¶
Canonical generated URLs:
/projdoc/<collection>/<slug>/
For Fleet:
/projdoc/fleet/<slug>/
Slug derivation for v1:
DESIGN-dispatch-lifecycle.html -> dispatch-lifecycle
DESIGN-rc-listener-lifecycle.html -> rc-listener-lifecycle
DESIGN-projdoc-autopublish.html -> projdoc-autopublish
Generated output path in the Astro repo:
/Users/pinkbear/projects/fengshen-site/src/pages/projdoc/fleet/<slug>/index.astro
Examples:
/Users/pinkbear/projects/fengshen-site/src/pages/projdoc/fleet/dispatch-lifecycle/index.astro
-> https://fengshen.dev/projdoc/fleet/dispatch-lifecycle/
/Users/pinkbear/projects/fengshen-site/src/pages/projdoc/fleet/projdoc-autopublish/index.astro
-> https://fengshen.dev/projdoc/fleet/projdoc-autopublish/
Each generated Astro page should embed the rendered design-doc HTML body as the primary page content. Because the renderer produces a complete HTML document, the sync service has two viable options:
- Store the full rendered HTML as an inert static asset and generate an Astro wrapper that renders it in an iframe.
- Parse the generated HTML, extract the document body content and inlined style block, and generate a first-class Astro page.
v1 should use option 2. It avoids iframe keyboard, height, and access-control sharp edges and keeps /projdoc/fleet/<slug>/ as the real page.
The importer should extract:
<title>for page title fallback.<style>...</style>from the rendered HTML.- The contents of
<body>...</body>.
The generated Astro page should include:
---
export const prerender = true;
const title = "DESIGN: Dispatch Lifecycle Primitive";
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex,nofollow" />
<title>{title}</title>
<style is:inline set:html={styleText} />
</head>
<body set:html={bodyHtml} />
</html>
Implementation note: the exact Astro syntax can be adjusted to the site's current conventions, but generated pages must be static, dependency-light, and not require client JavaScript.
The legacy paths stay outside the generated namespace:
/projdoc/design/ # legacy, untouched
/projdoc/rc-listener-lifecycle/ # legacy, untouched
/projdoc/fleet/... # v1 generated namespace
5. Local config¶
Add a local config file in the Fleet repo:
/Users/pinkbear/projects/fleet/.projdoc-sync.json
Recommended v1 schema:
{
"version": 1,
"collection": "fleet",
"source_dir": "/Users/pinkbear/projects/fleet/docs",
"source_glob": "DESIGN-*.html",
"site_repo": "/Users/pinkbear/projects/fengshen-site",
"site_branch": "master",
"sync_branch": "auto/projdoc-sync",
"poll_seconds": 60,
"generated_root": "src/pages/projdoc/fleet",
"directory_page": "src/pages/projdoc/index.astro",
"manifest_path": "src/content/projdoc/generated/fleet.manifest.json",
"preserve_paths": [
"src/pages/projdoc/design",
"src/pages/projdoc/rc-listener-lifecycle"
]
}
Config rules:
- Paths may be absolute or relative to the Fleet repo, except
site_repo, which should be absolute in v1. collectionmust be URL-safe: lowercase letters, numbers, and hyphens.source_globmust not allow path traversal.generated_rootmust resolve inside the website repo.directory_pagemust resolve inside the website repo.manifest_pathmust resolve inside the website repo.preserve_pathsare never deleted by the sync service.
If the config file is missing, the sync command can use the v1 defaults listed above. The launchd plist should pass the config path explicitly so future repos can reuse the command without recompilation.
6. Generated manifest¶
The sync service writes a generated manifest in the website repo:
/Users/pinkbear/projects/fengshen-site/src/content/projdoc/generated/fleet.manifest.json
Recommended schema:
{
"version": 1,
"collection": "fleet",
"source_root": "/Users/pinkbear/projects/fleet/docs",
"generated_at": "2026-05-17T18:00:00Z",
"items": [
{
"slug": "dispatch-lifecycle",
"title": "DESIGN: Dispatch Lifecycle Primitive",
"source_path": "/Users/pinkbear/projects/fleet/docs/DESIGN-dispatch-lifecycle.html",
"source_sha256": "abc123...",
"source_mtime": "2026-05-15T23:55:12Z",
"url_path": "/projdoc/fleet/dispatch-lifecycle/",
"generated_path": "src/pages/projdoc/fleet/dispatch-lifecycle/index.astro"
}
]
}
The manifest has three jobs:
- Drive the
/projdoc/directory page. - Give the sync service a precise prior state for deletion of removed generated docs.
- Make review diffs understandable when the standing PR updates.
Manifest rules:
- Sort
itemsbyslug. - Use SHA-256 of the source HTML content, not only mtime.
- Use absolute
source_pathbecause the source repo is outside the website repo. - Use repo-relative
generated_pathfor portable website diffs. - Write atomically: temporary file in the same directory, fsync, rename.
- Add a generated-file header comment only where the target format supports comments. JSON does not, so the manifest should rely on its
versionand path.
The sync service should also keep a local state file outside the website repo:
~/.fleet/projdoc-sync/state.json
Recommended local state:
{
"version": 1,
"last_success_at": "2026-05-17T18:00:05Z",
"last_pushed_head": "b9f3...",
"last_source_hashes": {
"/Users/pinkbear/projects/fleet/docs/DESIGN-dispatch-lifecycle.html": "abc123..."
}
}
This state is an optimization only. The generated manifest in the website repo remains the reviewable source of what the service published.
7. /projdoc/ directory page¶
/projdoc/ must be a clickable landing page, not a blank route or redirect.
Recommended generated path:
/Users/pinkbear/projects/fengshen-site/src/pages/projdoc/index.astro
Page behavior:
- Shows a "Project Docs" heading.
- Groups docs by collection.
- For v1, contains at least one collection:
Fleet. - Lists each generated doc as a link to
/projdoc/fleet/<slug>/. - Shows a small generated timestamp from the manifest.
- Includes
<meta name="robots" content="noindex,nofollow" />. - Does not link to legacy paths unless they are intentionally added later.
Directory data source:
src/content/projdoc/generated/*.manifest.json
The directory page should be generated or updated by the sync service in v1. That keeps the landing page stable even if the Astro site does not yet have a content-collection setup.
Minimum directory rendering:
---
import fleetManifest from "../../content/projdoc/generated/fleet.manifest.json";
const collections = [
{ name: "Fleet", manifest: fleetManifest },
];
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex,nofollow" />
<title>Project Docs</title>
</head>
<body>
<main>
<h1>Project Docs</h1>
{collections.map(({ name, manifest }) => (
<section>
<h2>{name}</h2>
<ul>
{manifest.items.map((item) => (
<li><a href={item.url_path}>{item.title}</a></li>
))}
</ul>
</section>
))}
</main>
</body>
</html>
The final implementation should match the existing site's visual system, but the information architecture is fixed: /projdoc/ is the directory, /projdoc/fleet/<slug>/ is the generated Fleet namespace.
8. Sync service¶
The sync service is a local command plus a macOS launchd plist.
Recommended command name:
projdoc-sync
Recommended command location, if implemented inside Fleet:
/Users/pinkbear/projects/fleet/cmd/projdoc-sync
Recommended local invocation:
/Users/pinkbear/projects/fleet/bin/projdoc-sync \
--config /Users/pinkbear/projects/fleet/.projdoc-sync.json \
--once
launchd should run a polling loop while the Mac user session is active. Use a user agent, not a system daemon:
~/Library/LaunchAgents/dev.fengshen.projdoc-sync.plist
Recommended plist shape:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.fengshen.projdoc-sync</string>
<key>ProgramArguments</key>
<array>
<string>/Users/pinkbear/projects/fleet/bin/projdoc-sync</string>
<string>--config</string>
<string>/Users/pinkbear/projects/fleet/.projdoc-sync.json</string>
<string>--once</string>
</array>
<key>StartInterval</key>
<integer>60</integer>
<key>RunAtLoad</key>
<true/>
<key>WorkingDirectory</key>
<string>/Users/pinkbear/projects/fleet</string>
<key>StandardOutPath</key>
<string>/Users/pinkbear/Library/Logs/projdoc-sync.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/pinkbear/Library/Logs/projdoc-sync.err.log</string>
</dict>
</plist>
Start/stop commands:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.fengshen.projdoc-sync.plist
launchctl kickstart -k gui/$(id -u)/dev.fengshen.projdoc-sync
launchctl bootout gui/$(id -u)/dev.fengshen.projdoc-sync
Service requirements:
- Exit quickly in
--oncemode. - Use a lock file to prevent overlapping runs:
~/.fleet/projdoc-sync/sync.lock
- If another run holds the lock, log and exit 0.
- Never prompt for credentials.
- Assume local GitHub authentication is already configured.
- Write structured logs as newline-delimited JSON when practical.
- Treat Git operations as best-effort local publishing; never block doc rendering in the Fleet repo.
9. Sync algorithm¶
High-level algorithm:
load config
acquire local lock
scan Fleet docs/DESIGN-*.html
derive item list
read website generated manifest
compare source hashes
if no changes:
exit 0
prepare website worktree
generate Astro doc pages
generate manifest
generate /projdoc/ directory
delete stale generated Fleet pages only
run website checks
commit on auto/projdoc-sync
push branch
ensure standing PR exists
ensure auto-merge is enabled
release lock
Detailed steps:
-
Load and validate config. - Resolve absolute source and site paths. - Confirm source dir exists. - Confirm site repo exists and is a Git repo. - Confirm generated paths resolve inside site repo.
-
Acquire lock. - Use
flockor an atomic create of~/.fleet/projdoc-sync/sync.lock. - Include PID, start time, and hostname in lock metadata. - Stale lock cleanup is allowed only when the PID no longer exists. -
Scan sources. - Glob
DESIGN-*.htmlunder/Users/pinkbear/projects/fleet/docs. - Ignore directories. - Ignore files that do not match exactly. - Compute SHA-256 content hash. - Extract title from<title>or first<h1>. - Derive slug by strippingDESIGN-and.html, lowercasing, and validating URL-safe characters. -
Read prior manifest. - If missing, treat as empty. - If malformed, fail the run before modifying site files.
-
Detect changes. - Compare sorted source hashes plus generated metadata. - If unchanged, exit 0 without Git operations.
-
Prepare site branch. - Fetch
origin. - Ensure localmastertracksorigin/master. - Create or reset localauto/projdoc-syncfromorigin/masterunless the branch has unpublished local changes from a previous failed run. - If the branch exists remotely, prefer rebasing the generated commit on currentorigin/masterso the standing PR stays current. -
Generate files. - For each source item, write:
src/pages/projdoc/fleet/<slug>/index.astro
- Write:
src/content/projdoc/generated/fleet.manifest.json
src/pages/projdoc/index.astro
- Do not write outside these generated paths.
- Do not write
src/pages/projdoc/design. - Do not write
src/pages/projdoc/rc-listener-lifecycle.
- Delete stale generated Fleet pages. - Only delete directories under:
src/pages/projdoc/fleet/
- A directory is stale when it existed in the prior manifest but no longer appears in the new manifest.
- Never delete paths not present in the prior generated manifest.
- Never delete configured
preserve_paths.
- Run website checks. - Use the site's existing package manager and scripts. - Minimum expected checks:
npm run build
- If the site has lint/typecheck scripts, run those too.
- On failure, leave the website working tree with generated files for inspection, do not commit or push, and log the failure.
- Commit.
- If no Git diff remains, exit 0.
- Commit message:
sync projdoc
- Include generated source count and source hashes in the commit body if useful.
-
Push and PR.
- Push
auto/projdoc-sync. - If the standing PR does not exist, create it against
master. - Enable auto-merge.
- Do not merge locally.
- Push
-
Persist state.
- Write
~/.fleet/projdoc-sync/state.jsonafter successful push. - Record PR URL if available.
- Write
The command should be idempotent. Running it twice with the same inputs should produce no second commit.
10. GitHub + deploy¶
Branch model:
base: master
head: auto/projdoc-sync
PR model:
- One standing PR.
- Title:
sync projdoc - Body includes:
- Source collection:
fleet - Source dir:
/Users/pinkbear/projects/fleet/docs - Published namespace:
/projdoc/fleet/ - Generated item count
- Statement that
/projdoc/*is Cloudflare Access protected - Test/check command output summary
Auto-merge requirements:
- Enable auto-merge on the standing PR.
- Let repository branch protection and GitHub Actions decide when the PR lands.
- Do not bypass checks.
- Do not push to
master. - Do not force-push a branch that a human has edited unless the sync service can prove all branch commits are generated sync commits.
Deploy model:
auto/projdoc-sync PR
-> checks pass
-> auto-merge to master
-> GitHub Actions deploy
-> Cloudflare Pages
-> https://fengshen.dev/projdoc/fleet/<slug>/
The local service does not need Cloudflare credentials in v1. Cloudflare deployment remains centralized in GitHub Actions.
Operational guardrails:
- If the standing PR has non-generated human edits, stop and log a hard error.
- If the branch diverges in a way the service cannot rebase cleanly, stop and log a hard error.
- If
ghis unavailable or unauthenticated, commit locally but do not push; log clear remediation. - If GitHub auto-merge cannot be enabled, still leave the PR open and log the issue.
11. Privacy¶
Primary privacy control:
Cloudflare Access policy for /projdoc/*
Requirements:
/projdoc/*must require Cloudflare Access authentication before launch.- Access policy should cover all generated paths, including
/projdoc/. - The policy should be configured at Cloudflare, not in generated Astro pages.
- Cloudflare Access is the privacy boundary.
Backup controls:
- Generated
/projdoc/and/projdoc/fleet/<slug>/pages include:
<meta name="robots" content="noindex,nofollow">
- Optionally add an HTTP header through the site config:
X-Robots-Tag: noindex, nofollow
Important limitation:
noindex,nofollow is not access control. It only reduces indexing risk if a private URL becomes reachable. It must not be treated as sufficient privacy.
Content rules:
- Do not publish secrets from local docs.
- Do not publish local-only credentials, tokens, keys, or private customer data.
- Do not publish raw WIP checkpoints.
- Do not publish Markdown comments or hidden source snippets that the HTML renderer would not normally expose.
Review implication:
The standing PR is the last reviewable boundary before production. Diffs should make it easy to see exactly which documents changed.
12. Failure modes¶
| Failure | Expected behavior |
|---|---|
| Source dir missing | Exit non-zero, log path, make no site changes. |
No DESIGN-*.html files |
Generate empty Fleet collection only if explicitly allowed; default should fail to avoid accidental wipe. |
| Duplicate slug | Exit non-zero, list both source files, make no site changes. |
| Malformed source HTML | Exit non-zero before writing generated page. |
| Existing generated manifest malformed | Exit non-zero before modifying site files. |
| Website repo dirty with unrelated files | Exit non-zero, list dirty paths, make no changes. |
| Website generated files dirty from prior failed sync | Allow overwrite only under generated paths. |
| Branch checkout/rebase conflict | Abort rebase, leave branch unchanged if possible, log remediation. |
| Website build fails | Leave generated files for inspection, do not commit or push. |
| GitHub push fails | Keep local commit, log failure, retry next launchd run. |
| PR creation fails | Keep branch pushed if push succeeded, log failure, retry next run. |
| Auto-merge enable fails | Keep PR open, log failure, retry next run. |
| Cloudflare deploy fails | Do not handle locally; GitHub/Cloudflare status is the source of truth. |
| Cloudflare Access missing | Treat as launch blocker for production exposure; do not rely on noindex. |
| launchd overlaps runs | Second run exits 0 after seeing lock. |
| Mac asleep/offline | No sync occurs; next active session run catches up. |
Dirty working tree policy in fengshen-site:
- If dirty paths are outside generated projdoc paths, stop.
- If dirty paths are only generated projdoc paths and match the service's prior failed run, the service may replace them.
- If dirty generated paths include human edits, stop. Human edits should not happen under generated paths.
Stale deletion policy:
- Delete only stale pages recorded in the prior manifest.
- Delete only under
src/pages/projdoc/fleet/. - Never delete the legacy paths named in
preserve_paths.
13. Tests¶
Unit tests for slug and scan behavior:
DESIGN-dispatch-lifecycle.htmlmaps todispatch-lifecycle.DESIGN-projdoc-autopublish.htmlmaps toprojdoc-autopublish.- Non-matching files are ignored.
- Duplicate slugs fail.
- Slugs outside
[a-z0-9-]+fail unless normalized deterministically.
Unit tests for HTML import:
- Extracts
<title>. - Falls back to first
<h1>when<title>is missing. - Extracts body content.
- Extracts style content.
- Fails on missing
<body>. - Does not execute or fetch external resources.
Unit tests for manifest generation:
- Items sort by slug.
source_sha256changes when source content changes.- Generated paths are repo-relative.
- URL paths have leading and trailing slash.
- Manifest output is deterministic across runs with the same inputs.
Unit tests for path safety:
- Generated root must be inside website repo.
- Directory page must be inside website repo.
- Manifest path must be inside website repo.
- Source glob cannot traverse out of source dir.
- Preserve paths are honored.
Integration tests with temporary repos:
- Initial sync writes
src/pages/projdoc/fleet/<slug>/index.astro, manifest, and directory page. - Second sync with unchanged sources creates no commit.
- Source edit updates one page and manifest hash.
- Source deletion removes only the stale generated page recorded in prior manifest.
- Legacy paths
/projdoc/design/and/projdoc/rc-listener-lifecycle/are untouched. - Dirty unrelated website file blocks sync.
- Dirty generated file can be replaced only when it is recognized as generated output.
Git/PR tests can use fakes or a local bare repo:
- Creates or updates
auto/projdoc-sync. - Never commits to
master. - Push failure leaves local state retryable.
- PR creation command receives base
masterand headauto/projdoc-sync. - Auto-merge failure does not erase the branch.
Manual verification before enabling launchd:
cd /Users/pinkbear/projects/fleet
python3 scripts/render-design-doc.py docs/DESIGN-projdoc-autopublish.md
/Users/pinkbear/projects/fleet/bin/projdoc-sync \
--config /Users/pinkbear/projects/fleet/.projdoc-sync.json \
--once
cd /Users/pinkbear/projects/fengshen-site
npm run build
git diff --stat
Manual production verification after the first merge:
https://fengshen.dev/projdoc/requires Cloudflare Access.https://fengshen.dev/projdoc/fleet/dispatch-lifecycle/requires Cloudflare Access.- Directory links work after authentication.
- Generated pages include
noindex,nofollow. - Legacy paths still resolve exactly as before.
14. Coordinator task split¶
Recommended implementation split:
-
P1: Website generated namespace scaffold - Repo:
/Users/pinkbear/projects/fengshen-site- Addsrc/pages/projdoc/fleet/.gitkeepif needed. - Add or reservesrc/content/projdoc/generated/. - Confirm existing/projdoc/design/and/projdoc/rc-listener-lifecycle/files/routes and mark them as preserve paths. - Add Cloudflare Access configuration task if not already configured outside repo. - Tests: site build. -
P1: Sync command core - Repo:
/Users/pinkbear/projects/fleet- Implement config load, scan, slugging, HTML extraction, manifest generation, and Astro page generation. - Keep GitHub operations behind an interface so unit/integration tests can run without network. - Tests: unit tests plus temp-dir integration tests. -
P1: Git branch and PR publisher - Repo:
/Users/pinkbear/projects/fleet- Implement website repo cleanliness checks, branch update, commit, push, PR create/update, and auto-merge enable. - Useghor GitHub API consistently; preferghfor local operator environment if already standard. - Tests: local bare repo or command-fake tests. -
P1: Directory page generation - Repo:
/Users/pinkbear/projects/fleetplus generated output in/Users/pinkbear/projects/fengshen-site- Generate/projdoc/from manifest data. - Ensure Fleet collection links target/projdoc/fleet/<slug>/. - Tests: generated Astro snapshot and site build. -
P1: launchd packaging - Repo:
/Users/pinkbear/projects/fleet- Add plist template, install/uninstall commands or documentation, lock handling, and logs. - Poll every 60s while the Mac user session is active. - Tests: plist render test; manuallaunchctl bootstrapverification. -
P1: First production dry run - Run the sync once manually. - Confirm generated diff. - Push
auto/projdoc-sync. - Open standing PR. - Confirm checks, auto-merge, deploy, and Cloudflare Access. - Verify legacy paths unchanged. -
P2: Polish after v1 - Add optional docs index sorting by updated time. - Add multiple collections. - Add a search page if the private doc set grows. - Add deploy status notifications. - Add stale source warnings when Markdown is newer than rendered HTML.
Non-goal for all tasks:
- Do not publish or rewrite legacy
/projdoc/design/or/projdoc/rc-listener-lifecycle/in v1.