Skip to content

feat(children-v2): v2.0 core — versioned children field (1/3, split of #9678)#9686

Open
tutntut wants to merge 10 commits into
developfrom
children-v2-core
Open

feat(children-v2): v2.0 core — versioned children field (1/3, split of #9678)#9686
tutntut wants to merge 10 commits into
developfrom
children-v2-core

Conversation

@tutntut

@tutntut tutntut commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What this is

Part 1 of 3 splitting #9678 (MyInfo Children v2) into phase-aligned PRs. This
is the v2.0 "clean up the field" slice — the shippable core. Follow-ups:

  • PR-B (v2.1): answerObject v4 storage + the Multi-respondent-default behaviour (platform enabler, stays dark until storage-mode v4 / MRF-with-webhook land)
  • PR-C (v2.2): sponsored-children support (reviewable-but-inert until the MyInfo scope-request is wired)

What's in this PR

"MyInfo Children" lets a parent pull their children's details from Singpass/MyInfo. This PR introduces version 2 of the field and is gated to a pilot:

  • A version discriminator on the existing children fieldnot a new field type (ADR-0001). Version 2 is a single child with Secondary Race and "Allow multiple children" removed and a clearer description.
  • Gated in Storage/Encrypt mode by the new children-v2 GrowthBook flag. Existing forms stay v1 and behave exactly as before.
  • The version is stamped server-side (so a crafted payload can't opt in) on create, and the "no Secondary Race / no Allow-Multiple" invariant is re-enforced on update so it can't be reintroduced by editing. The Mongoose version path is constrained to the known versions.
  • v2 optional sub-fields no longer block submission (an empty optional field MyInfo returns won't fail validation).

Builder (admin)

Before (v1) After (v2)
builder before builder after

Respondent

Before (v1) After (v2)
respondent before respondent after

Anything breaking? No.

Existing (version-1) children forms and their stored responses are untouched and keep working. v2 only applies to new fields created with the children-v2 flag on.

Migration of existing forms — not in this PR

This PR ships the capability; it does not migrate existing v1 forms. The code only sets version: 2 on fresh creates (createFormField) and re-enforces the invariant on edits of an already-v2 field (updateFormField) — it never upgrades a v1 field on its own. Migrating the ~246 whitelisted forms is a separate eng-run datafix (slice 06): an in-place version bump preserving the field _id, which must also strip Secondary Race / allowMultiple (because it bypasses createFormField). Allow-Multiple forms are held on v1 (a datafix can't split a multi-child field). The eventual mechanic is the responseMode encrypt→MRF flip (v2.1). This is intentionally out of scope here.

Tests

TC1: v2 gating (builder)

  • With children-v2 on, a children field added to a Storage/Encrypt form is created as version 2.
  • With the flag off, it stays version 1 (legacy).

TC2: dropped options

  • The v2 builder hides "Secondary race" and the "Allow respondent to add multiple children" toggle.
  • A v2 field's saved data has no ChildSecondaryRace and allowMultiple=false, even via a crafted create or update payload.

TC3: respondent (v2)

  • A v2 field shows a single child with the new description; no "Add another child", no "Secondary race".
  • An empty optional sub-field does not block submission.

TC5: legacy untouched

  • An existing version-1 children form behaves exactly as before, and past responses still decrypt.

🤖 This PR was assembled by Claude Code (split out of #9678). Tests verified on CI; backend Jest / frontend vitest can't run in the authoring sandbox (native-module platform mismatch), so they were validated via type-check + lint locally and the test run on CI.

@tutntut
tutntut requested a review from a team June 30, 2026 13:49
tutntut and others added 8 commits June 30, 2026 14:06
…sary

v2 ships as a version discriminator on the existing children field, not a new field type. ADR-0001 captures the decision and consequences; CONTEXT.md adds the domain glossary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ChildrenFieldVersion (Legacy=1 / V2=2), an optional version field on ChildrenCompoundFieldBase, and the isChildrenV2Field type guard used across layers to branch v2 behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the version path (constrained to the known ChildrenFieldVersion values so a garbage number can't be persisted). Without an explicit path, strict mode drops the stamp on save.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gates v2 in Storage/Encrypt mode for the pilot cohort.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erver-side

The controller reads the children-v2 flag and passes it to createFormField, which stamps the version (authoritative server-side) and strips ChildSecondaryRace + forces allowMultiple=false for v2. updateFormField re-enforces the same invariant so an edit can't reintroduce them; it never downgrades an existing version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A v2 child is identified by the name picker + unified identifier, so empty optional sub-fields returned by MyInfo must not fail required-validation. Applied on both the backend and frontend validators.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EditMyInfoChildren drops the Secondary Race option and the Allow-Multiple toggle and shows the v2 description when the field is v2 (isChildrenV2InBuilder). MyInfoPanel offers the children field in Encrypt mode behind the beta flag (canAddChildrenField).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For a v2 field the respondent shows one child with no 'add another child' affordance; adds a v2 single-local-child Storybook fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tutntut
tutntut force-pushed the children-v2-core branch from 9d8fda0 to 009654e Compare June 30, 2026 14:09
Review follow-up: in v2.0 the builder util isChildrenV2InBuilder is byte-
identical to the shared isChildrenV2Field guard (version-only), so drop it and
call the shared guard directly. isChildrenV2InBuilder is reintroduced in v2.1
(PR-B) where it gains the Multi-respondent-default term (responseMode), which
the shared version-only guard can't express.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-up: the enforceChildrenV2Invariants doc referenced ADR-0002,
which covers sponsored support (not in this PR) and doesn't exist on this
branch yet. The dropped-options invariant is an ADR-0001 decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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