update-feature-config

Implements field-level merge logic for feature config sections in Go.

2.0k|126|Updated Jun 16, 2020
One-click install
npx skills add https://github.com/authgear/authgear-server --skill update-feature-config
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: update-feature-config
Source: https://github.com/authgear/authgear-server/tree/main/.claude/skills/update-feature-config
Command: npx skills add https://github.com/authgear/authgear-server --skill update-feature-config

SYSTEM DOCUMENTATION & REQUIREMENTS

What problem does it solve?

Feature config in authgear-server (authgear.features.yaml) is merged across code default, cluster, plan, and app override layers, and whole-section merge swaps silently reset sibling fields set by lower layers. This Skill guides adding or changing feature config fields and sections in pkg/lib/config so merges stay field-level, schema constraints match runtime semantics, and JSON serialization does not hide resolved values.

Core Features & Use Cases

  • Field-level merge enforcement: Implements nil-safe, per-field Merge methods following the OAuthClientFeatureConfig reference pattern, cascading through single-field wrapper levels to real sibling fields.
  • Merge regression testing: Adds cases to pkg/lib/config/testdata/merge_feature.yaml where one layer sets field A and a later layer sets sibling B, asserting both survive with non-default values.
  • Schema and serialization audits: Verifies JSON schema constraints against actual consumer code behavior and removes omitempty from scalar fields whose zero value is a real default, validated with marshal-based tests.
  • Use Case: When adding a new boolean field to an existing feature config section, use this Skill to write the per-field merge, add the layered merge test fixture, and confirm the resolved section serializes correctly for the Site Admin API.

Quick Start

Add a new disabled field to the messaging feature config section with a field-level merge implementation and a merge test case in merge_feature.yaml.

Frequently Asked Questions about update-feature-config

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I add a new feature config field in authgear-server?

Add the field to the section struct in pkg/lib/config/feature_*.go, extend that section's Merge with a per-field nil check (if layer.X != nil { c.X = layer.X }), and add a layered test case in testdata/merge_feature.yaml proving sibling fields survive partial overrides.

Why does feature config merging reset fields to defaults?

Whole-section merge swaps (return layer.Section) discard sibling fields set by lower layers when a higher layer only sets one field. Plan and app YAML documents are routinely partial, so every multi-field section must merge field-by-field instead of replacing the whole section.

How should I test feature config merge behavior?

Add a case to pkg/lib/config/testdata/merge_feature.yaml where one layer sets field A only and a later layer sets sibling B only, then assert both survive. Use non-default values so a whole-section regression cannot accidentally produce the right result.

When is a whole-section merge acceptable in feature config?

A whole-object replace is fine only when a section genuinely has one leaf field with no siblings at any deeper level. If the single field is itself an object containing siblings, the field-level merge must cascade down to where the real siblings live.

Why does a resolved feature config section serialize as empty JSON?

Scalar fields tagged omitempty are omitted by encoding/json when their zero value is the real default, so a fully resolved section marshals as {}. Drop omitempty on plain bool, string, and numeric fields and verify with a marshal-based test rather than struct comparison.

Should I add minItems or required constraints to feature config schema?

Only after checking how the runtime consumer treats edge-case values like nil, empty, or zero. A constraint stricter than runtime semantics can block meaningful inputs, such as an empty allowlist that the code already interprets as no restriction.