Problem
docs/principal-claims-reference.md repeatedly documents this form for IN-side claim sources:
[schema_forge.authz.principal_claims.client_org_id]
type = "string"
required = false
source = { user_field = "client_org_id" }
This does not parse. PrincipalClaimSourceConfig (crates/schema-forge-acton/src/authz/principal_claims.rs:127) is an externally-tagged enum:
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PrincipalClaimSourceConfig {
UserField { user_field: String },
}
The TOML deserializer rejects the documented inline form with:
Configuration error: invalid type: found string "client_org_id", expected struct variant
The form that actually parses today is the doubled-key nested table:
[schema_forge.authz.principal_claims.client_org_id]
type = "string"
required = false
[schema_forge.authz.principal_claims.client_org_id.source.user_field]
user_field = "client_org_id"
Why the integration tests didn't catch this
The principal-claims login integration test
(crates/schema-forge-acton/tests/principal_claims_login_integration.rs:147)
constructs PrincipalClaimSourceConfig::UserField { ... } directly in Rust and
never exercises the TOML deserialization path. The documented config form has
no test coverage.
Recommended fix
Add #[serde(untagged)] to PrincipalClaimSourceConfig so the documented inline form
(source = { user_field = "..." }) parses correctly. The doubled user_field
nested-table form is hostile UX and should not be the only working syntax.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum PrincipalClaimSourceConfig {
UserField { user_field: String },
}
If a second variant is ever added, swap to a custom Deserialize impl that
disambiguates on a unique inner field name — keep the inline form as the
operator-facing surface.
Acceptance criteria
Context
Split out from #52. Blocks operator deployments that use the documented form (Folio is using the doubled-key workaround today).
Problem
docs/principal-claims-reference.mdrepeatedly documents this form for IN-side claim sources:This does not parse.
PrincipalClaimSourceConfig(crates/schema-forge-acton/src/authz/principal_claims.rs:127) is an externally-tagged enum:The TOML deserializer rejects the documented inline form with:
The form that actually parses today is the doubled-key nested table:
Why the integration tests didn't catch this
The principal-claims login integration test
(
crates/schema-forge-acton/tests/principal_claims_login_integration.rs:147)constructs
PrincipalClaimSourceConfig::UserField { ... }directly in Rust andnever exercises the TOML deserialization path. The documented config form has
no test coverage.
Recommended fix
Add
#[serde(untagged)]toPrincipalClaimSourceConfigso the documented inline form(
source = { user_field = "..." }) parses correctly. The doubleduser_fieldnested-table form is hostile UX and should not be the only working syntax.
If a second variant is ever added, swap to a custom
Deserializeimpl thatdisambiguates on a unique inner field name — keep the inline form as the
operator-facing surface.
Acceptance criteria
source = { user_field = "<f>" }parses from TOML config exactly as documented indocs/principal-claims-reference.mdPrincipalClaimMappinghas the expectedResolvedClaimSourcedocs/principal-claims-reference.md— the documented syntax is the working syntaxContext
Split out from #52. Blocks operator deployments that use the documented form (Folio is using the doubled-key workaround today).