Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(api): validate prerequisite parents on feature and experiment writes; block deleting a flag experiments gate on - #6950

Open
bryce-fitzsimons wants to merge 9 commits into
mainfrom
bryce/prereq-write-validation
Open

fix(api): validate prerequisite parents on feature and experiment writes; block deleting a flag experiments gate on#6950
bryce-fitzsimons wants to merge 9 commits into
mainfrom
bryce/prereq-write-validation

Conversation

@bryce-fitzsimons

@bryce-fitzsimons bryce-fitzsimons commented Sep 13, 2026

Copy link
Copy Markdown
Member

Features and Changes

Prerequisites now get the same constraints on the API that the dashboard's prerequisite picker applies. A prerequisite must point at a flag that:

  • exists (and is readable by the caller),
  • is not archived,
  • is a boolean flag,
  • for feature prerequisites, does not itself depend on the feature being written, directly or through a chain.

Previously the API stored any of these and the SDK payload builder then silently dropped the dependent flag or experiment, so it fell back to its code default with no error anywhere.

Features, every write path: per-rule add and patch (v1 and v2), bulk create and update (v1 and v2, including the feature-level prerequisites list, which had no existence check before), and the revision prerequisites endpoint. Cycle detection walks upward from the new parents one query per hop, through an org-wide read so a parent in a project the caller cannot see still counts, rather than loading the whole feature graph on every write. A chain still open after fifty hops is refused.

Experiments, every write path: REST create and update, and the app API create, phase edit, new phase, and targeting writes. Experiments are leaves of the prerequisite graph, so only the parent checks apply.

Only parents that a write introduces are checked. Anything already pointing at a since-archived parent still reads and posts back unchanged. On a full-array experiment update, the served phase is checked against the latest stored phase and earlier phases against anything the stored experiment already references.

Reverse direction

Deleting a flag that an experiment's latest phase lists as a prerequisite used to succeed; the experiment then gated on nothing and was dropped from the SDK payload. The delete guard now counts experiments as well as features, using the same collector as the archive warning.

Testing

prerequisiteParents.test.ts (HTTP) covers each bad-parent case on the v2 rule add endpoint, one case per remaining feature write path, and the stale-reference echo. test/services/prerequisiteParents.test.ts covers the helper directly: no query when nothing is added, a cycle closed through an ancestor the caller cannot read, the depth refusal, and the experiment variant. test/services/featureDeleteGuard.test.ts covers the delete guard. The experiments REST harness gains a wiring case.

A prerequisite may now only point at an existing, unarchived boolean flag
that does not itself depend on the feature being written, matching what the
dashboard's prerequisite picker allows. Previously the API stored a missing,
archived, non-boolean, or cyclic parent and the SDK payload builder then
silently dropped the child flag.

The check runs on per-rule add/patch (v1, v2), bulk create/update (v1, v2,
including the feature-level prerequisites list, which had no existence check
at all), and the revision prerequisites endpoint. Only parents a write
introduces are checked, so a stored reference to a since-archived parent
still posts back unchanged. Cycle detection walks upward from the new
parents one query per hop instead of loading the org's whole feature graph.
@greptile-apps

greptile-apps Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge because there are no accepted new findings or outstanding previous findings.

Reviews (11) · Last reviewed commit: "Check the served phase against the lates..."

Comment thread packages/back-end/src/api/features/validations.ts Outdated
Comment thread packages/back-end/src/api/features/validations.ts Outdated
Comment thread packages/back-end/src/api/features/postFeatureRevisionRuleAdd.ts Outdated
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

The cycle walk now loads ancestors through the org-wide scan context the
delete guard uses, so a parent in a project the caller cannot read still
contributes its edges; the caller's own permissions still decide whether a
direct parent is "not found". A chain still open after fifty hops is refused
instead of silently accepted. The v2 bulk update checked the stored rules
rather than the inbound ones, so rule-level parents there were never
validated. Per-rule handlers share one revision-aware wrapper so the draft's
prerequisites list is the baseline on both sides.
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

The saved-group reference check no longer reads a rule's prerequisites, so
its input type and the PUT handlers' call sites stop mentioning them. Unit
cases cover what the HTTP harness cannot reach: a cycle closed through an
ancestor the caller cannot read, the depth refusal, and the no-query path
when a write adds no prerequisite.
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

The delete guard counted only feature prerequisites, so a flag listed in a
running experiment's phase prerequisites could be deleted and the experiment
then evaluated its gate against nothing and vanished from the SDK payload.
Delete and archive now share one experiment-dependents collector, so both
directions see the same dependents.
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

The guard's REST and dashboard wiring predates this change; what changed is
the service function, so its cases live in a unit test with the loaders
stubbed instead of an HTTP harness.
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

…service

The prerequisite-parent check moves out of the features API directory into
services/prerequisiteParents so experiments can use it. Experiment phase
prerequisites now get the same existence, archived, and boolean checks on
the REST create/update endpoints and the app API create, phase edit, new
phase, and targeting writes. Experiments are leaves of the prerequisite
graph, so no cycle walk runs for them. As with features, only parents a
write adds are checked.
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

Comment thread packages/back-end/src/api/experiments/updateExperiment.ts
REST update and both create paths validate the prerequisites of all inbound
phases against everything the stored experiment already references, so an
earlier phase cannot smuggle in a bad parent.
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

Comment thread packages/back-end/src/api/experiments/updateExperiment.ts Outdated
@bryce-fitzsimons

Copy link
Copy Markdown
Member Author

@greptile review

@bryce-fitzsimons bryce-fitzsimons changed the title fix(api): validate prerequisite parents on REST feature writes fix(api): validate prerequisite parents on feature and experiment writes; block deleting a flag experiments gate on Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant