Files
Oleg Isonen 75d710e759 fix: Let Editor links patch Content Block text and props (#6575)
Editor links with API access can edit text and editable props inside
Content Blocks through raw CLI/MCP patches. Builder and Viewer
permissions stay unchanged.

UI and API requests use the same Content-mode policy. The API keeps its
narrower text-and-props scope; page, asset, style, expression, and
structure changes remain blocked for raw Editor patches.

### Implementation

- Use the persistence patch engine for instance, prop, and style state
changes. Remove the separate instance patch interpreter and record
writes from permission validation.
- Check prop changes against the resulting state. Image dimensions work
even when their patches appear before the source patch.
- Move Image source, dimensions, and alt updates into the shared
runtime. Video source changes preserve its existing dimensions. The UI
no longer sends separate follow-up writes. Explicit overrides and
expression bindings are preserved.
- Separate capability discovery from transaction validation and move the
server permission adapter out of the router.
- Reject missing replacement targets and protected or renamed prop-ID
reuse. Keep template-copy protections.
- This refactor removes 19 non-test lines. It removes eager
whole-instance cloning, but the server still loads the project and
validates its content tree. Empty transactions skip that validation.

Closes #6573

### Checklist

- [x] Reproduce the Editor-token denial and add regression coverage
- [x] Keep Editor raw patches limited to text and editable props
- [x] Use the existing patch engine and test missing targets, immutable
input, and permission boundaries
- [x] Share media updates between UI and MCP; test atomic updates, patch
ordering, overrides, and bindings
- [x] Separate capability discovery, permission validation, and API
orchestration
- [x] Verify new regression tests fail with the relevant behavior
disabled, then pass after restoration
- [x] Run runtime tests, API/sync tests, typechecks, lint, formatter,
package-boundary, and generated-API checks
- [x] Run Content-mode E2E checks for templates, assets, page creation,
collaboration, and save/reload
- [x] Add a CLI MCP Editor-token save/reload E2E, including protected
template rejection
- [x] Return a structured forbidden error for rejected Editor MCP
content changes
- [x] Run the complete Luna/low evaluation suite; record failures below
- [x] Review documentation and fixture impact: no new API inputs, schema
changes, migrations, or fixture generation changes; existing guidance
remains applicable
- [x] Correct the project-package permission fixture to include its
owning instance and run all package tests
- [x] Cover repeated media source updates and bound namespace batches in
Editor requests
- [x] Preserve stale retry handling before Editor content validation,
with API regression coverage
- [x] Preserve expression-bound media metadata during source replacement
and test saved Content-mode output
- [x] Reuse batch merging so split prop payloads validate against their
final state; test split and combined forms
- [x] Push the refactor and verify required CI completes
- [ ] Deferred: reproduce the separate Builder UI report on a
session-free Editor link
- [ ] Deferred: confirm image-description editing expectations
separately

### Verification

- Full project-build suite: 2,597 tests passed. Focused permission/prop
run: 121 passed.
- All package tests (`pnpm test:packages`) passed after correcting the
project-package fixture. All 1,297 CLI tests passed.
- Builder API and sync authorization: 54 passed; settings-panel tests:
11 passed.
- Content-mode E2E: 9 passed, including setup, plus the earlier focused
Image replacement run.
- Builder/project-build typechecks, lint, formatting, package
boundaries, and generated-API checks passed. The CLI content-runtime
bundle test passed; no generated fixture output changed.
- Earlier full Luna/low evaluations: **7/9 passed**.
`managed-form-runtime-v1` failed `runtimeFormAction`; `markdown-blog-v1`
failed `blogRouteEvidence`. These checks also failed before this
refactor. MDX editing and Markdown-reference discovery passed. Baselines
for this model/effort were incompatible or missing and were not updated.
This suite is not fully green.

- Latest full Luna/low rerun after review fixes: **6/9 passed**. Form
runtime, blog authoring, and MDX editing failed; the MDX fixture passed
in the earlier run. Baselines were not updated. The evaluation suite
remains incomplete.

- Retry follow-up: all 59 API/router tests and 28 persistence patch
tests passed, along with Builder typecheck and lint. Older retries use
existing persistence handling; current and future versions remain
permission-checked.

- Bound-media follow-up: Image and Video source replacement preserves
expression-bound width/alt and still passes shared Content-mode
validation. Full runtime tests, typecheck, and lint passed after this
fix. The agent evaluation results above precede the final retry and
binding guards; those guards have dedicated regression coverage.

- Split-payload follow-up: reuse the existing batch merge helper before
permission validation. Combined and split media patches pass the same
final-state checks. Full runtime tests, 59 API tests, typechecks, and
lint passed. Agent evaluations were not repeated after this follow-up.

- CI passed on `1242ba8344`: all package, Builder, and CLI test jobs;
all six Builder E2E shards; visual regression; typecheck/lint;
generated-file checks; packaged SSG; size; migrations; and submodule
checks. Development deployment checks passed. All review threads are
resolved.

### Final review follow-up (2fbb30ffe6; CI passed)

- [x] Reject future base versions before saving; test an otherwise
allowed Editor patch
- [x] Preserve sequential patch behavior and test
authorization/persistence parity
- [x] Run focused tests, typechecks, lint, and review
documentation/fixture impact
- [x] Commit and push these follow-up fixes, then verify CI

Local follow-up verification: 2,600 project-build tests, 96 project
tests, and 60 API tests passed. Builder/project-build typechecks,
changed-file lint and formatting, package boundaries, and diff checks
passed. Regression tests failed without the fixes and passed with them.
The shared patch engine uses one Immer draft and supports namespace
replacement without mutating input. Net production change: 6 lines. No
documentation, fixture inputs/outputs, schemas, or generated contracts
changed. Browser E2E and agent evaluations were not rerun for this
follow-up; the race and patch-order cases are covered by deterministic
tests.


CI passed on `2fbb30ffe6`: all six Builder E2E shards,
package/CLI/Builder tests, typecheck/lint/generated checks, visual
regression, migrations, SSG build, size, and submodule checks. No new
blocking findings in the follow-up review. Agent evaluations were not
rerun; earlier limitations remain recorded above.

### Asset review follow-up

- [x] Reproduce and block asset-valued design-only props, retaining
matching Image metadata
- [x] Stop generating image metadata for Video source updates
- [x] Verify regression tests, documentation/fixture impact, and CI;
reply to both review threads

Asset follow-up `2ea28028c0`: shared metadata eligibility now checks
asset values and their matching Image source, including MDX
materialization. Video source edits do not generate Image-only metadata.
New and updated regression tests failed without the fixes and pass with
them. All 2,604 runtime tests, 60 API tests, 30 persistence tests, both
typechecks, lint, formatting, and package boundaries passed. No
documentation, schemas, or fixture data changes are needed. Agent
evaluations were not rerun. Both review threads have replies and are
resolved. CI passed on `2ea28028c0`, including all six E2E shards,
visual regression, package/CLI/Builder tests, typecheck/lint/generated
checks, migrations, SSG build, size, and submodules.

- [x] Reproduce repeated source updates without a saved source, coalesce
before ID assignment, and verify regression tests/CI

Final source follow-up `16c1c5f68e`: repeated instance/property updates
are coalesced before assigning IDs, replacing the later source-only
check. Both new Image/Video regression cases failed before the fix and
pass now. All 2,606 runtime tests, typecheck, and lint passed. CI passed
on this commit, including all six E2E shards and visual regression. No
documentation or fixture-data changes were needed; evaluations were not
rerun.


### Coverage audit

- [x] Audit changed behavior against assertions, including UI/API and
persistence boundaries
- [x] Add test assertions for identified coverage gaps; automatic CI
passed
- [x] Review documentation and fixture impact: no product documentation
change; one new SVG used only by browser E2E

Per request, no local tests or agent evaluations were run for this
audit. Automatic CI passed on `4591940169`, including package/API tests,
all six browser-test shards, visual regression, typechecks, lint, and
generated-file checks.

Coverage additions are pushed in `2371d9796a` and `4591940169`: check
actual text sent to persistence; duplicate ordinary props across
instances; rejected edits leaving capabilities reusable; merged patch
ordering and input immutability; accepted/rejected linked Image
dimensions in MDX templates; returned style/source/selection state; and
UI Image dimension changes followed by reload. The previous image E2E
used equal-sized files and could miss broken dimension synchronization.
Its new SVG has different dimensions. Production code is unchanged. No
tests or evaluations were run for this audit, as requested; no new pass
claim is made.

- [x] Inventory every changed/moved/removed production function and map
it to assertions
- [x] Add missing mixed-child and dependent-transaction coverage
- [x] Push inventory follow-up and inspect CI results

### Function-by-function coverage inventory

This inventories production functions changed, moved, or removed by this
PR. “Covered” means the named test asserts the behavior; it does not
claim every possible input or branch is covered. No local tests or
evaluations were run for this inventory.

| Function / handler | Assertions traced |
| --- | --- |
| `renderProperty` and its `onChange` callback | Content-mode props E2E
checks text, href, Image/Video source, alt, protected controls, and
reload. Asset E2E now changes dimensions from 120×80 to 240×160, then
replaces/reloads to 120×80. |
| `createContentModeCapabilitiesFromBuild` (moved unchanged) | API
“commits content-mode mutations with editor api tokens” and “limits
editor api patches to content instances and props” exercise conversion
from saved build data. |
| `assertContentOrBuildTransactions` | Editor vs Builder, design denial,
transaction/payload limits, all-or-nothing rejection; new exact
10-transaction boundary and dependent append-then-edit transactions. |
| `contentOrBuildMutation` | API semantic text edit asserts version,
authorization, actual saved text, unchanged outside content, and
unchanged input. |
| `build.patch` handler | API tests assert allowed text forms, denied
structure/styles/assets/expressions/outside roots, Viewer/no-API denial,
stale retries, current-version validation, and future-version CONFLICT
before persistence. |
| `getContentModePropNamesByTag` (moved unchanged) | Derives names by
tag and merges names for matching tags. |
| `getDefaultContentRootIds` (moved unchanged) | Default vs explicit
roots, Body selection, template exclusion. |
| `getContentModeFrontmatterBoundTargets` (moved unchanged) | Explicit
Body/frontmatter scope, read vs readwrite expressions, recursive
instances. |
| `getContentModeEditableInstanceIds` (moved unchanged) | Outside roots,
nested templates, missing references/roots, non-id children, cycles. |
| `getContentModeEditableInstanceRootIds` and its recursive `visit`
(moved unchanged) | Same traversal cases; transaction tests reject
moving content between roots and duplicate references. |
| `getInstancePropMetas` (moved unchanged) | Tag-derived props,
unknown/design-only/incompatible props, no generic HTML-attribute
permission bypass. |
| `getContentModeEditableStaticPropNames` (moved unchanged) | MDX
template tests assert exact editable names, primitive conversions, and
retained/ignored design-only props. |
| `getContentModePropEligibility` | Matching/missing/different asset
sources, protected asset props, exact primitive types, UI/transaction
parity, Image vs Video. |
| `isContentModeCopyableProp` (moved unchanged) | Editable/copyable
classification and content-mode copy-policy test. |
| `getContentModeCapabilities` | Editable IDs, roots, frontmatter
bindings, asset metadata; shared UI and transaction eligibility test. |
| `isContentModePropTransition` | Schema/id/owner/name checks, unknown
instance, protected existing props, permitted source type changes,
linked dimensions. |
| `validatePropPatches` | Add/replace/remove, new-then-update, missing
replacement target, protected ID reuse, copied template defaults,
final-state media patches in split/reversed order, failed-edit
immutability and retry. |
| `hasSameInstanceMetadata` | Whole-instance text replacement allowed;
tag/name changes and outside references rejected. |
| `validateStyleSourceSelectionPatch` | New content selection allowed;
existing/token misuse and dangling cleanup rejected; now asserts
resulting selection map. |
| `validateStyleSourcePatch` | New local source allowed; token,
duplicate/unused source and incomplete cleanup rejected; now asserts
resulting source map. |
| `validateStylePatch` | New local declarations allowed; existing design
styles, missing breakpoint and incomplete cleanup rejected; now asserts
resulting declaration map. |
| `validateMapChange` | Above three map paths, invalid patch targets,
resulting state and input immutability. |
| `validatePatchChange` | Instance/prop/style/asset/page paths and
rejected design namespaces through transaction tests. |
| `preservesNonTextChildren` and local `keepNonText` | Text-only changes
allowed; insertion/deletion/reordering of instance references and
expression changes denied; new mixed text/reference positional cases. |
| `applyContentModeTransaction` | Authorization/persistence parity,
split batches, empty transactions, scoped permissions, copy protections,
roots, cleanup, and immutable input on success/failure. |
| `materializeMdxAuthoredContent`, its `visit`, and `overlayChildren` |
MDX-source “round-trips an authored Asset Image through a styled
composite descendant” asserts src/width/height and styles after reload;
“serializes an Asset selected on an inserted Image template” asserts all
four linked props; nested and standalone component-permission tests
reject protected props. |
| `getMdxPropEligibility` | Template tests check allowed/ignored props,
aliases, incompatible types; new matching vs mismatched Image dimension
asset test exercises passed asset values. |
| `materializeMdxTemplates` | New linked-dimension test plus existing
copied-fragment, unchanged-input, authored-prop, diagnostic, and alias
tests. |
| `appendBuilderPatchChanges` | Mutation accumulator test asserts
sequential state and combined payload; merge test asserts ordering and
input preservation. |
| `mergeBuilderPatchChanges` | New repeated/interleaved namespace, empty
input, repeat-call and patch-array immutability assertions; permission
tests exercise split payload integration. |
| `getMediaAssetPropNames` | Image generates width/height/alt; Video and
unrelated components do not; explicit overrides and expression-bound
metadata retained. |
| `getPropIdsToDelete` | Image src removes dimensions; non-Image removes
only requested prop; resource cleanup and missing-instance cases. |
| `updateProps` | Atomic Image/Video updates, repeated sources
with/without saved props, overrides, bindings, original-state
preservation; new ordinary prop deduplication across names and
instances. |
| `getPatchParent` (type-only change) | Mutable nested patch and
missing-path tests. |
| `assertReplacePatchTargetsExistingPath` (type-only change) | Missing
target rejected; preceding add/root replacement makes later replacement
valid; preceding remove makes it invalid. |
| `applyBuilderNamespacePatches` | Sequential/root replacements, append
then edit, remove then replace, immutable state, permission/persistence
parity. |

Removed functions are covered through their replacements:
`shouldSyncMediaAssetProps` → shared `updateProps` plus UI E2E;
`assertContentOrBuildPayload` → transaction adapter;
`isContentModePropForInstance` → eligibility;
`isContentModePropPatchValue`/`validatePropPatch` → transition/batch
validation; `applyValidatedInstancePatch` → shared patch engine. No
behavior test is needed for import-only changes, type declarations,
public-entry exports, or regenerated metadata/docs; their callers and
contracts retain tests.

The original reporter-specific UI reproduction and image-description
product question remain separate limitations. This inventory does not
claim either is resolved.


### Latest review follow-up

- [x] Reject fresh prop IDs that shadow existing instance/property
records; cover protected bindings and valid edits
- [x] Coalesce updates in linear time while preserving last-update order
and input immutability
- [x] Check documentation/fixture impact, push, inspect CI, and reply to
both review threads


Review fixes pushed in `5dd03135e7`: 10 net production lines. Added
binding-shadow and batch-work regression assertions; no local tests or
evaluations were run, per request. Formatting and diff checks passed.
Documentation and generated fixture inputs/outputs are unchanged.
Automatic CI is pending.


- [x] Reject duplicate fresh props within the same transaction and test
split/combined batches
- [x] Preserve resource and parameter media bindings using the existing
dynamic-type check
- [x] Bound total Editor patches before validation; test the boundary
and unchanged Builder access


Review follow-up `d478446eb1`: all package, Builder, and CLI CI test
jobs and typecheck/lint/generated checks passed. Five addressed review
threads are resolved. Browser CI checks are still running. One newly
posted review about non-image assets assigned to Image sources remains
open pending the user's behavior choice. No local tests or agent
evaluations were run; local typechecks passed.


### Shared asset constraints

- [x] Reuse component file-control metadata and the UI MIME matcher in
shared property writes; preserve manually entered URLs and pending
uploads
- [x] Generate linked Image metadata only when the selected asset
supports it; cover legacy file-backed images
- [x] Add shared-runtime coverage for accepted/rejected Image and Video
assets, custom props, URLs, and atomic rejection
- [x] Review documentation, generated contracts, fixture impact, and
diff size; verify typechecks and CI (no local tests or evaluations, as
requested)


Shared asset follow-up `5f0054edd7`: property writes use the UI's
existing file-control `accept` metadata and MIME matcher. Known
incompatible assets are rejected with the approved message. Legacy image
files remain selectable without generating unsupported metadata. URLs,
custom asset props, and unresolved uploads keep their existing behavior.
Asset records are now loaded before CLI/MCP property writes; this may
require an asset-list request when not already loaded. This change does
not add asset-file downloads. Net handwritten production change: 24
lines. Builder/project-build typechecks, changed-file lint, formatting,
and diff checks passed. No local tests or evaluations were run.
Regression and runtime-entry tests were added; CI is pending. Reviewed
Image/Video docs: no guidance change needed. No fixture inputs or
generated fixture bundles changed; regenerated runtime contract metadata
only.

- [x] Keep Editor permission checks when a shared edit operation loads
assets; add an API regression test before final CI verification


Follow-up `d33b70e1c7` preserves Editor authorization when property
writes load assets. The API regression test asserts a valid Image source
reaches persistence, incompatible files are rejected, and
outside-content edits remain forbidden. This reuses the existing asset
loader and permission adapters (3 net production lines). CI found and
corrected an old session namespace expectation and a mistaken test
assumption: Video's existing unsupported `.mpg`/`.mpeg` accept entries
cause the shared matcher to allow all assets. This PR preserves that UI
behavior; it does not tighten Video formats. Total handwritten
production increase for this asset follow-up: 27 lines. Builder
typecheck/lint and server metadata generation passed; metadata output
was unchanged. CI is rerunning.


Final asset follow-up verification (`8b2efd8508`): all package, CLI,
Builder API/unit, and six browser E2E jobs passed, together with
lint/typechecks, generated checks, visual regression, migrations, size,
packaged SSG, and branch deployment. The direct API test now checks the
typed authorization cause, not an HTTP status that this test caller does
not map. All review threads are resolved. No local tests or agent
evaluations were run for this follow-up; CI provided execution coverage.
The separate Video filter limitation noted above remains unchanged.


### Shared write-boundary validation

- [x] Reuse one asset-property validator in UI/MCP property updates and
raw patch persistence, independent of permission level
- [x] Cover whole-property and field patches, component changes, invalid
metadata, final-state validation, unchanged legacy data, and stale
retries
- [x] Review documentation/fixture impact, typecheck and lint, then
verify CI; no local tests or evaluations per the earlier request

Expected change: about 70–90 net production lines. Patch saves load only
referenced asset metadata; no asset-file downloads.

Shared-save follow-up: 133 net production lines, above the initial
estimate because one save can replace an asset file and its references
together. The save validator reuses the asset writer’s canonical file
preparation rather than trusting client-declared asset types. It also
checks asset-only replacements, before folder or Build writes, while
leaving unchanged legacy references alone. Ordinary property writes load
only referenced asset metadata; asset-changing saves with references
additionally prepare the full asset map, then the writer repeats that
metadata preparation. No file contents are downloaded by the new check.
This does not change schemas, migrations, generated contracts,
documentation, or fixture inputs/outputs. Added regression coverage; no
local tests/evaluations were run, per the earlier request. Independent
Astra review found no remaining defect after the projection fix. CI
pending.

- [x] Fix selective UI sync loading to include asset-validation
dependencies; add selection regression coverage and rerun CI

The first browser run caught missing props/instances in selectively
loaded UI saves. Package/API tests passed but their fixtures provided
complete build rows. The development deployment succeeded; its
status-reporting step failed with GitHub HTTP 500.

Final verification on `a22d059717`: all package, CLI, Builder unit/API,
six browser shards, visual regression, typecheck/lint/generated checks,
migrations, size, packaged SSG, development deployment, and
Ubuntu/Windows deployment fixtures passed. Browser shard 3 passed on one
retry after a page-drag ordering assertion; the save/parsing errors were
fixed by loading the required fields. No local tests or evaluations were
run. The added persistence suite has 46 passing tests and runtime props
has 74; selective-sync tests now omit unselected fields. No unresolved
review threads. Total net production increase across this follow-up: 134
lines.


### Deep review follow-up

- [x] Make MDX linked-image validation independent of authored attribute
order; cover changed sources, mismatches, and the affected
materialization paths
- [x] Review DRY and diff size, documentation/fixture impact, and
independent review findings
- [x] Verify typechecks/lint and CI; no local tests or evaluations per
the earlier request

Review found that linked dimensions can be checked against the old
template source before the new source is applied. Fix should reuse the
shared eligibility rule with the final source, without another property
parser. Initial estimate: under 60 added non-test lines; revisit the
approach if it needs more.


Deep-review fix `f7c3d805a8`: apply Image sources before linked metadata
in all three MDX materialization paths, keeping original attribute
indexes for asset references. Reuses the existing eligibility checks and
parser; 23 net production lines. Regression cases cover source order,
changed sources, mismatched dimensions, and nested overlays. Independent
Astra review, project-build typecheck, focused lint, formatting, and
diff checks passed. No schema, documentation, generated contract, or
fixture-data changes. Local tests/evaluations have not run; focused-test
permission was requested. CI pending.


Final deep-review verification on `f7c3d805a8`: all package/CLI/Builder
test jobs, all six browser shards, visual regression,
typechecks/lint/generated checks, migrations, packaged SSG, size,
submodules, and development deployment/fixtures passed. CI ran 60
authored-content and 24 template-materialization tests successfully. No
local tests or evaluations ran; fail-before/pass-after was not executed
locally. All review threads are resolved and the worktree is clean. No
further actionable defect was found in the independent reviews. Known
remaining cost: asset-changing saves can prepare metadata twice;
removing that safely would expand this fix into persistence changes.


### Editor MCP browser E2E follow-up

`0ee978e465` adds a real CLI/MCP Editor-token round trip: insert content
into a Content Block, reload through a fresh CLI process, verify it in
the Builder canvas, and reject a protected template insertion without
changing the saved Build. A direct API denial now returns `FORBIDDEN`
instead of a generic internal error. The focused E2E and API tests,
typecheck, lint, formatting, and diff checks passed locally.
Fail-before/pass-after was verified by temporarily disabling Editor
fragment access, then restoring it. The full Luna/low evaluation run was
attempted but did not pass: the form-runtime fixture received 503
responses. The suite is not claimed green. CI for this push passed on
`0ee978e465`, including all six Builder E2E shards, package/CLI/Builder
tests, visual regression, typecheck/lint/generated checks, migrations,
size, SSG build, and submodules.


Live MCP permission probe against the branch deployment: Builder,
Editor, and Viewer role metadata matched their tokens. Editor content
text, rich text, fragment, link href, and compatible Image source edits
committed and survived a fresh MCP session. Outside-root,
protected-template, design-only prop, mismatched Image width, and
incompatible asset writes were denied without persistence. The updated
deployment returned structured `FORBIDDEN` for protected content writes
even when `contentMode` was false or omitted. Mixed valid/invalid writes
were atomic; stale Editor writes refreshed and retried. Viewer MCP could
not be exercised beyond the API gate because this Viewer token has API
access disabled. MDX frontmatter, token revocation, and UI-only
scenarios were not tested. A wrong-project token was rejected but
surfaced `INTERNAL_SERVER_ERROR`, which is a diagnostic-quality gap, not
an authorization bypass. The isolated probe page
`/codex-permission-probe-6575` remains: cleanup was stopped after a
concurrent-edit `CONFLICT`, to avoid deleting another session's work.


### Fixture-based parity follow-up

- [ ] Verify the full Content-mode UI/MCP parity matrix with an isolated
MDX/template/media/page fixture, excluding publishing and token/plan
changes
- [x] Fix the reproduced Editor MCP template/page-settings gap and cover
fail-before/pass-after routing
- [x] Verify a real CLI Editor-token create-from-template call, fresh
readback, Builder reload, allowed settings, and protected-setting
rejection
- [x] Update MCP guidance, review fixture impact, and verify required CI

The UI already allows an Editor to create a page from a template and
edit allowed page settings. MCP incorrectly required Builder permission,
and a separate MCP-only legacy Webhook Form copy guard blocked the
seeded template. Those restrictions are corrected. The server now
derives Content-mode page copying from Editor permission even when
`contentMode` is omitted or false; Builder copies retain their existing
behavior. Editor semantic commands use the server's shared Content-mode
validator, while raw Editor patches stay limited to text and editable
props. This adds a named server call and refetch for Editor semantic
edits; the existing initial project sync still occurs.

The new isolated E2E passed on `03e7ca6953`, including flag-free
template creation, settings edit, fresh CLI readback, Builder reload,
and a denied protected setting without a saved change. All required CI
passed on that commit: all six Builder E2E shards, package/CLI/Builder
tests, generated checks, typecheck/lint, visual regression, migrations,
packaged SSG, size, and submodules. Focused local runtime/API tests,
typechecks, lint, formatting, and generated-doc checks passed. Browser
E2E and full agent evaluations were not run locally; the requested
browser approval and a fresh evaluation approval were not received. The
full UI/MCP parity matrix remains open; Editor asset operations are a
separately identified gap, not claimed fixed here.


### Exploratory permission coverage follow-up

- [x] Inventory observed Builder/Editor/Viewer UI and MCP permission
scenarios against existing tests
- [x] Add fail-before/pass-after regression tests for confirmed
uncovered failures
- [x] Run focused tests, typechecks, lint, and fixture checks; verify
required CI
- [x] Record unsupported UI/MCP parity as open product work, without
calling the matrix complete

The UI share-link E2E already checks Viewer read-only controls, Editor
text editing, and Builder design controls. Existing MCP E2E checks
Editor content/template writes, page creation, asset upload, and
protected edits. This follow-up adds one real CLI/MCP flow with
API-enabled Viewer and Builder tokens: Viewer can list pages but cannot
create a page or asset folder or upload a file; Builder can change an
HTTP status setting that Editor cannot. The test checks the denied page
writes leave the build unchanged and the Builder setting survives
readback.

The run exposed two diagnostic bugs: local permission denial and REST
asset-upload denial reached MCP as generic failures. Both now retain
`FORBIDDEN`. Focused unit tests failed before these changes and pass
after; the full local browser/MCP test passes. The only non-test change
is three net lines. No fixture data, schemas, generated contracts, or
product docs changed. Local checks passed: 2,654 project-build tests, 48
HTTP-client tests, the focused Builder E2E (2 tests including setup),
affected typechecks, lint, formatting, and package boundaries.
`check:fixture-sizes` could not run locally because the SSG fixture's
built `dist/client` is absent; CI builds that fixture before checking
its size.

The full UI/MCP parity matrix remains open. In particular, Editor UI
asset update/replacement/duplication/deletion and collection-entry
creation still lack equivalent MCP support until the UI's collection
protections are enforced at the shared server boundary. Publishing and
token/plan changes were outside this test scope.

CI passed on `6490efc838`: all six Builder E2E shards,
package/CLI/Builder tests, visual regression, typecheck/lint/generated
checks, migrations, packaged SSG, size, and submodules. Shard 3 first
hit Playwright's 120-second limit under four-worker load; its logic
passed locally. The longer role flow now uses the same 240-second
per-test limit as other MCP E2E tests, and shard 3 passed in the final
run.

### Final UI/MCP parity audit

- [x] Trace Editor UI asset and folder actions through REST, runtime,
and persistence
- [x] Reproduce Editor MCP upload and folder-create denials; add
fail-before/pass-after permission tests
- [x] Add isolated Editor MCP upload/folder and UI folder-reload browser
regressions
- [x] Keep collection-sensitive asset/folder mutations Builder-only
until UI collection protections are shared at the server boundary
- [x] Review docs and fixture impact: MCP guidance is updated; no
fixture input or bundle changes
- [x] Restrict Editor folder saves to new IDs; test overwrite and delete
denials at both UI and API boundaries
- [x] Reject folder-add collisions at persistence and test the
mixed-writer/stale-snapshot case
- [x] Keep exact Editor folder retries authorized after a partial save;
add fail-before/pass-after coverage
- [x] Verify the new browser E2E in required CI
- [ ] Run the full Luna/low agent evaluations after fresh approval (not
granted for this follow-up)
- [x] Verify required CI for `6fd3beefc8`

The safe scope is small: the Assets REST upload path already authorizes
Editor tokens and enforces collection rules, but the MCP catalog
incorrectly required Builder access. Creating an ordinary asset folder
is available in the Editor UI, but the shared Content-mode save check
rejected it. Both now use Editor permission. The isolated browser tests
check fresh MCP readback and Builder reload; they have not run yet.

Further parity work remains open: Editor UI allows updates, replacement,
duplication, deletion, and collection-entry creation in cases MCP cannot
currently perform. These need the UI's collection protections enforced
at the shared server boundary before their MCP permits can be widened.
Raw Editor `apply-patch` stays text-and-props-only. This PR does not
claim the full matrix is complete.


Asset/folder follow-up `6fd3beefc8`: Editor semantic tools now share the
UI's edit permission for uploads and ordinary folder creation. Folder
saves reject same-ID changes, allow identical retries after a lost
response, and use insert for add-only persistence. The retry regression
failed before the fix and passes with JSON-normalized parent
IDs/timestamps. Independent Astra review found no remaining concrete
defect. Focused runtime (86), asset persistence (52), protocol (14), and
API/sync (71) tests passed; Builder/project-build/asset-uploader
typechecks, lint, package boundaries, formatting, generated API, and MCP
docs checks passed. Full project-build, protocol, and CLI suites passed
earlier in this follow-up; Builder core had resource-contention failures
but its affected tests passed in isolation. The new browser E2E and
agent evaluations were not run locally pending fresh approval; CI is
running them automatically.

CI passed on `6fd3beefc8`: all six Builder browser shards,
package/CLI/Builder tests, generated checks, typechecks, lint, visual
regression, migrations, packaged SSG, size, and submodules. The new
Editor MCP asset round trip and UI folder-reload tests ran in the
passing browser shards.
2026-10-23 14:40:26 +02:00
..
2026-04-28 03:38:12 +02:00
2026-07-13 02:32:27 +02:00
…
…
2025-08-09 15:14:22 +02:00