Follow-up surfaced while reviewing #747 (unified sync <path>).
Context
In the unified engine, a tree page's canonical pushed body is the composed body — the file's bare body + resolved cross-ref links + the derived blank-line-separated <page> child anchors. Pushing a bare body for a page that has children omits those anchors, and since the wire op is replace_content, Notion trashes every child whose anchor is absent.
Problem
The guard that prevents this (assertNotTreeMember) lives in the CLI layer (cli-program.ts), wired before syncPage for the sync <file> path. It is not enforced in the engine. So the composed-body invariant can be bypassed by:
Either can push a bare body for a tree-member page and trash its children. The guarantee currently depends on the CLI entry point rather than the engine.
Ask
Enforce the composed-body invariant in the engine, not just at the CLI: a tree-member page's push must use the composed body (or the engine refuses with a clear error), so the safety holds for every caller and entry path.
Notes
Refs #747.
Posted on behalf of @schickling
| field |
value |
agent_name |
🦤 cl2-ibis |
agent_session_id |
49ccf2ed-0881-48a7-b504-fa512767a52a |
agent_tool |
Claude Code |
agent_tool_version |
2.1.160 |
agent_runtime |
Claude Code 2.1.160 |
agent_model |
claude-opus-4-8 |
worktree |
effect-utils/main |
machine |
dev3 |
tooling_profile |
dotfiles@unknown-dirty |
Follow-up surfaced while reviewing #747 (unified
sync <path>).Context
In the unified engine, a tree page's canonical pushed body is the composed body — the file's bare body + resolved cross-ref links + the derived blank-line-separated
<page>child anchors. Pushing a bare body for a page that has children omits those anchors, and since the wire op isreplace_content, Notion trashes every child whose anchor is absent.Problem
The guard that prevents this (
assertNotTreeMember) lives in the CLI layer (cli-program.ts), wired beforesyncPagefor thesync <file>path. It is not enforced in the engine. So the composed-body invariant can be bypassed by:pushPage/syncPagecaller (internal API), orsync <dir> --recursiveflat-batch path, which (per the feat(notion-md): unified sync <path> (file|dir) — fold subtree into one engine, nullable page_id, plan verb #747 review) does not callassertNotTreeMember.Either can push a bare body for a tree-member page and trash its children. The guarantee currently depends on the CLI entry point rather than the engine.
Ask
Enforce the composed-body invariant in the engine, not just at the CLI: a tree-member page's push must use the composed body (or the engine refuses with a clear error), so the safety holds for every caller and entry path.
Notes
--recursivepath is relied on.Refs #747.
Posted on behalf of @schickling
agent_nameagent_session_idagent_toolagent_tool_versionagent_runtimeagent_modelworktreemachinetooling_profile