Back in May I tried to manage an Okta MFA enrollment policy with Terraform and the API rejected it with “Not one of the allowed values.” The diagnosis at the time, made by a capable AI model looking at a plausible error, was that my Okta plan didn’t support managing that policy type via the API, so I wrote it down as a known limitation, did the policy work in the console by hand, and moved on with my life for two months.
That diagnosis was wrong. The root cause was a single missing boolean in the provider configuration, is_oie = true, and it took about ten minutes to fix on the day somebody finally read the error instead of the summary. A wrong-but-reasonable answer had sat unquestioned for two months by then, inside a repo where I audit everything else.
The setup
The identity plane for my platform is Okta managed entirely through Terraform, groups, apps, sign-on policies, MFA enrollment, all of it in HCL behind CI plan gates. Okta has two engines under the hood, the Classic engine and the newer Identity Engine (OIE), and they expose DIFFERENT schemas for policy factors. Same endpoint, same resource type, different set of allowed values. The Terraform provider handles both, but it has to be told which dialect to speak, and that’s what is_oie = true does in the provider block.
My org is an OIE org. My provider block didn’t say so. So when Terraform submitted the MFA policy, it serialized the Classic-engine factors schema and sent it to an OIE org, and Okta responded with “Not one of the allowed values,” which is technically true and completely unhelpful.
Why the wrong diagnosis was so sticky
The plan-tier explanation was not stupid, which is most of why it lasted. Okta genuinely does gate features by plan, and I later confirmed that creating custom app sign-on policies really does 403 on my tier, so there was a true fact standing about a foot away: custom ACCESS_POLICY creation is plan-gated. The diagnosis picked that fact up and applied it to MFA enrollment policies, which were never gated at all. The wrong answer had a right answer standing about a foot away from it, wearing almost the same words, and every time I walked past that pair I saw the right one and kept going.
It also failed indirectly, which is the worst way a thing can fail if you ever intend to verify it. Nobody ever saw an Okta error that said “upgrade your plan.” The model inferred the plan restriction from a generic validation error plus some documentation about feature tiers, and I took the inference because it fit my prior that vendors gate everything. Once it was written into the docs as a known limitation, every later session picked it up as settled fact, and a settled fact is the one thing nobody in that repo, me very much included, ever went back and checked.
The tell that broke it open
Two months later a different session was doing unrelated MFA work, hit the same error, and read the payload instead of pattern-matching the summary. The API’s error carried an index-tracking key, factors[0], pointing at the FIRST element of the factors array. A plan gate does not do that. A plan gate rejects your operation at the door and never opens the array, let alone complains about element zero. Something on the other end was reading the schema and finding the shape wrong, which means the shape going out was wrong, which turns the question into “what schema is the provider even sending,” which lands on the engine setting about ten minutes later.
One line in the provider block:
provider "okta" {
org_name = var.okta_org_name
base_url = "okta.com"
is_oie = true
}
The fix itself shipped canary-gated behind a variable with an inert default, so the first apply changed nothing and the flag got flipped deliberately after the plan was inspected. After two months of believing the API couldn’t do this, I wasn’t going to let the correction be the thing that broke org authentication.
What I actually took away
An AI model made a plausible misdiagnosis, a later AI session corrected it, and the two months sitting between them belong entirely to my process, because I let an inference get written down carrying the same confidence as an observation. The boolean I can fix in a line. That other thing I am still turning over.
Accepted limitations now get recorded WITH their evidence class, and I am still not sure that is enough. “The API returned a 403 with a plan-upgrade message” is direct evidence. “The error is consistent with documentation about plan tiers” is an inference, and an inference gets a marker on it that says re-litigate this the next time you are in the neighborhood. The second session only found the bug because it treated the error message as data instead of trusting the repo’s own documentation, and the repo’s own documentation was the thing lying to it.
Okta’s error was generic and my explanation for it was specific, and every bit of that specificity had come from me rather than from Okta, which took two months and somebody else’s session to notice.