Skip to content

fix(nextly): a redacted view stays redacted inside a group or a repeater row - #1840

Merged
mobeenabdullah merged 1 commit into
mainfrom
fix/a-redacted-view-stays-redacted-inside-a-container
Sep 13, 2026
Merged

mobeenabdullah merged 1 commit into
mainfrom
fix/a-redacted-view-stays-redacted-inside-a-container

Conversation

@mobeenabdullah

Copy link
Copy Markdown
Collaborator

Two post-merge findings on #1821, both measured, both with a control run that fails against main.

A redacted view stays redacted inside a container

The create path judges the field rules on a deep copy of the request and hands that copy to the function defaults, so a default cannot read a value its writer was not allowed to send.

When the rules denied a whole container, they removed it from the copy. The walk into that container then found no counterpart, read that as no copy having been given at all, and fell back to the caller's own row. A nested function default could therefore read a denied sibling and carry it into a child the caller may write, and the pass that decides what is stored removed only the field it came from, so the copy survived.

A container missing from the view is now read as a container the rules emptied, which is the only way one goes missing.

Measured on a repeater whose create rule is satisfied by a top-level default, holding a denied secret and a sibling defaulting from it:

control (main):  items[0] = { echo: "saw:forbidden-value" }
fixed:           items[0] = { echo: "saw:undefined" }

A required nested child is judged by the rule the write validator applies

required: true on the field and validation: { required: true } beside its other rules are both supported spellings. A Single's first read tested only the first, so a group invented for one defaulted child was stored with a required sibling empty, and the next write refused the document that read had just created. isRequired is exported from the validator and shared rather than restated.

control (main):  contact = { label: "Support" }
fixed:           contact = null

Not in here

PRRT_kwDOSYwUJs6h0tbZ, "evaluate each function default only once", is left open on #1821 as a design decision rather than a patch. Three requirements from that review cannot all hold in a pass-based design: a later default seeing an earlier one, no default reading a value the caller may not write, and each default running exactly once. Any two are reachable. Filed as decision:function-defaults-once-versus-reading-a-defaulted-sibling with options and a recommendation.

PRRT_kwDOSYwUJs6h03xa, user callbacks inside the publish transaction, is the accepted cost of the maintainer's decision that the promote gate runs there. Filed as task:a-transaction-bound-query-path-for-user-callbacks, with measuring the hang as its first step.

Verification

  • pnpm --filter nextly check-types clean.
  • 131 integration tests across collections, singles and field-groups; 528 unit tests in the touched areas.
  • Each new test run against main's source first, and each fails there.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-13T00:44:02.618921Z 98099a0 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 12 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: a9c57624-b9ff-41ab-8bd6-ffcbceffe9cd

📥 Commits

Reviewing files that changed from the base of the PR and between 2fa4696 and 98099a0.

⛔ Files ignored due to path filters (1)
  • .changeset/a-redacted-view-stays-redacted-inside-a-container.md is excluded by !.changeset/**
📒 Files selected for processing (5)
  • packages/nextly/src/collections/__tests__/default-cannot-read-a-denied-field.integration.test.ts
  • packages/nextly/src/domains/singles/__tests__/single-first-read-nested-defaults.integration.test.ts
  • packages/nextly/src/domains/singles/services/single-query-service.ts
  • packages/nextly/src/shared/lib/entry-validation.ts
  • packages/nextly/src/shared/lib/field-defaults.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Whole-Repository Code Hygiene Summary

Full dead-code, duplication, and complexity report for the PR branch as it stands now. Playground is excluded. Quality gate enforcement on introduced issues is performed by the Changed files job.

🌿 Fallow

Warning

Review needed

⚠️ 73 code issues · ⚠️ 678 clone groups · ⚠️ 1034 health findings

See inline review comments for per-finding details.

Code issues (73)
Category Count
Unused files 2
Unused exports 5
Unused dependencies 19
Unused devDependencies 6
Unresolved imports 2
Unlisted dependencies 1
Circular dependencies 38
Duplication (678 groups · 28473 lines · 4%)
Locations Lines Tokens
schemas/_dialect-bundles/mysql.relations.ts:40-134
schemas/_dialect-bundles/postgres.relations.ts:40-134
schemas/_dialect-bundles/sqlite.relations.ts:40-134
95 593
cli/commands/db-sync-demote.ts:70-75
cli/commands/db-sync-promote.ts:38-43
cli/commands/dev-build.ts:100-105
cli/commands/dev-build.ts:179-184
cli/commands/dev-build.ts:299-304
cli/commands/dev-build.ts:411-416
cli/commands/dev-build.ts:552-557
cli/commands/dev-server.ts:575-580
cli/commands/dev-server.ts:840-845
cli/commands/dev-server.ts:1143-1148
cli/commands/migrate-field-groups.ts:110-115
6 70
entries/EntryList/EntryTableSkeleton.tsx:74-98
collection/components/CollectionTableSkeleton.tsx:94-118
field-group/components/FieldGroupTableSkeleton.tsx:90-114
plugins/components/PluginsTableSkeleton.tsx:86-110
singles/components/SinglesTableSkeleton.tsx:77-101
src/components/table-skeleton.tsx:100-124
25 89
collections/config/validate-config.ts:380-433
field-groups/config/validate-field-group.ts:185-238
singles/config/validate-single.ts:190-243
54 152
dispatcher/handlers/collection-dispatcher.ts:925-967
field-groups/services/field-group-table-provisioning.ts:186-236
singles/services/reconcile-single-companion.ts:110-160
51 149

… and 673 more groups.

Across 423 files.

Complexity (1034 functions above threshold)
File Function Severity Cyclomatic Cognitive CRAP Lines
singles/services/single-mutation-service.ts:950 <arrow> critical 246 ! 308 ! 13317.5 ! 1634
collections/services/collection-mutation-service.ts:6327 <arrow> critical 165 ! 153 ! 6045.6 ! 1260
src/init/reload-config.ts:1417 applyReload critical 143 ! 211 ! 4560 ! 1470
shared/lib/entry-validation.ts:245 validateFieldValue critical 109 ! 157 ! 2675.3 ! 432
blocks-engine/src/measure-bytes.ts:646 surveyDocument critical 102 ! 250 ! 137.1 ! 658

5082 files, 78506 functions analyzed (thresholds: cyclomatic > 20, cognitive > 15, CRAP >= 30)

Codebase health

Metric Value
Maintainability 91.7 / 100
Avg complexity 1.8

Tip

Run fallow fix --dry-run to preview auto-fixes.
Add /** @public */ above exports to preserve them.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98099a09bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!view) return undefined;
// A fresh object per call. A shared one would be written into by the
// caller-side synchronisation a level down.
return isPlainObject(container) ? container : {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve allowed children when container access flips

When a group or repeater is denied by the pre-default access pass but becomes allowed after a sibling default is applied, this fallback replaces the entire missing container view with {}. As a result, nested function defaults cannot read even children that the final access pass allows and stores; for example, a row containing an allowed label and echo: d => String(d.label) persists label but resolves echo from undefined. Preserve the authorized nested children while continuing to redact denied ones rather than treating every child of the temporarily removed container as hidden.

Useful? React with 👍 / 👎.

// the field misses the `validation: { required: true }` spelling, and a
// nullish value check calls a whitespace string or an empty array present:
// either way the group is stored and the next write rejects it.
if (isRequired(child) && isEmptyRequiredValue(value)) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match required checks to the validator's list semantics

When an invented group contains a repeater with validation: { required: true } and defaultValue: [], this new isRequired call deletes the entire group because isEmptyRequiredValue([]) returns true. The write validator explicitly treats an empty repeater as a provided list and accepts it unless minRows rejects it, so the document would not be refused on its next write as this helper assumes; the first read now silently discards a valid declared default that was retained before this change. Reuse the validator's complete required-value decision, including its list exception, rather than combining only these two predicates.

AGENTS.md reference: AGENTS.md:L299-L302

Useful? React with 👍 / 👎.

@pkg-pr-new

pkg-pr-new Bot commented Sep 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

npm i https://pkg.pr.new/@nextlyhq/adapter-drizzle@98099a0

@nextlyhq/adapter-mysql

npm i https://pkg.pr.new/@nextlyhq/adapter-mysql@98099a0

@nextlyhq/adapter-postgres

npm i https://pkg.pr.new/@nextlyhq/adapter-postgres@98099a0

@nextlyhq/adapter-sqlite

npm i https://pkg.pr.new/@nextlyhq/adapter-sqlite@98099a0

@nextlyhq/admin

npm i https://pkg.pr.new/@nextlyhq/admin@98099a0

@nextlyhq/admin-css

npm i https://pkg.pr.new/@nextlyhq/admin-css@98099a0

@nextlyhq/blocks-engine

npm i https://pkg.pr.new/@nextlyhq/blocks-engine@98099a0

@nextlyhq/blocks-react

npm i https://pkg.pr.new/@nextlyhq/blocks-react@98099a0

@nextlyhq/builder

npm i https://pkg.pr.new/@nextlyhq/builder@98099a0

create-nextly-app

npm i https://pkg.pr.new/create-nextly-app@98099a0

@nextlyhq/eslint-plugin

npm i https://pkg.pr.new/@nextlyhq/eslint-plugin@98099a0

nextly

npm i https://pkg.pr.new/nextly@98099a0

@nextlyhq/plugin-form-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-form-builder@98099a0

@nextlyhq/plugin-mcp

npm i https://pkg.pr.new/@nextlyhq/plugin-mcp@98099a0

@nextlyhq/plugin-page-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-page-builder@98099a0

@nextlyhq/plugin-sdk

npm i https://pkg.pr.new/@nextlyhq/plugin-sdk@98099a0

@nextlyhq/plugin-seo

npm i https://pkg.pr.new/@nextlyhq/plugin-seo@98099a0

@nextlyhq/storage-s3

npm i https://pkg.pr.new/@nextlyhq/storage-s3@98099a0

@nextlyhq/storage-uploadthing

npm i https://pkg.pr.new/@nextlyhq/storage-uploadthing@98099a0

@nextlyhq/storage-vercel-blob

npm i https://pkg.pr.new/@nextlyhq/storage-vercel-blob@98099a0

@nextlyhq/ui

npm i https://pkg.pr.new/@nextlyhq/ui@98099a0

commit: 98099a0

@mobeenabdullah
mobeenabdullah merged commit 16efd9c into main Sep 13, 2026
24 checks passed
@github-actions github-actions Bot added scope: core nextly type: docs Documentation only labels Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: core nextly type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant