Uh oh!
There was an error while loading. Please reload this page.
feat(config): add toProjectConfig and the ProjectConfig hosted subset (CLI-2230) - #6339
Conversation
… (CLI-2230) Introduce the hosted-project subset type ProjectConfig and its normalizers on the pure entrypoint of @supabase/config: - fromConfigDocument(config): projection of a CliConfig/EffectiveConfig down to the hosted sections (api, auth, db, realtime, storage, workers, experimental). - fromApiProjectConfig(input): translation of a Management API v2 project-config response (envelope, data object, or bare attributes) via a registry-driven mapping (233 rows mined from the legacy config-sync mappers): renames, boolean inversions, duration/byte-size unit conversions, GoTrue key mapping, x-secret omission (the API reports HMAC digests). The raw attributes are attached as a non-enumerable _apiResponse per ADR 0019, with a registry-derived unmappedApiFields() escape-hatch reader. - toProjectConfig(source): thin dispatcher over both normalizers. The comparison-core operand type BaseCliConfig is replaced by the family-neutral EffectiveConfig = DeepPartial<Omit<CliConfig, "remotes">> so the sparse ProjectConfig is assignable to subtractCliConfig/omitDefaultValues without casts; no runtime change (ruling recorded on CLI-2230 and in ADR 0018's 2026-08-26 addendum). apps/cli gains a type-drift guard pinning the generated V2GetProjectConfigOutput attributes against the package's lenient input schema (assignability plus per-section key-set assertions), and the new ProjectConfigParseError is registered in the error-actionability table as a platform-response failure.
#6339 Correctness: - fromConfigDocument deep-copies hosted sections and omits every x-secret leaf (schema-derived via the collector extracted to lib/secret-paths.ts) — decoded documents hold plaintext credentials, and the API arm omits secrets, so the subtract composition rendered them as drift. - Registry-driven document-side canonicalization (new normalizeDocument row column): duration spellings ("24h" -> "24h0m0s") and byte-size spellings converge with the API arm so one logical value diffs clean. - Mirror schema now types every never-mapped field Schema.Unknown — a platform type change on a field nothing reads no longer fails every decode (ADR 0019 rule 2 held only for unknown keys before). - ProjectConfigParseError carries message/detail/suggestion (rendered by normalize-error), schema-issue paths lift into apiPath, actionability adds fingerprint_suffix api_response + has_suggestion. - Type-mismatch handling unified: smtp_host/port and string rows throw via expect* instead of fabricating enabled:false or vanishing; CIDR entries that fail to classify throw instead of silently emptying a security allowlist; expectNumber rejects non-finite; numeric passthrough rows narrow. - _apiResponse is cloned + deep-frozen at attach (no caller aliasing); attachApiResponse export restores it across spread/clone round-trips; invisibility claim narrowed (serializers yes, debug inspectors no). - unmappedApiFields: depth cap, unmappedSecretApiPaths deny list (external_figma_secret — the one genuine orphan digest), consumed-set semantics documented. - New exports comparableProjectConfigPaths/isComparableProjectConfigPath so diff consumers never hand-maintain field lists. - New registry-integrity unit test: all 233 rows' paths resolve against the CliConfigSchema and mirror ASTs (470 generated cases); drift guard gains the missing key-set levels; inverse implementations dropped until the push mapper derives them. Docs updated (README example, loading doc, ADR 0018 consequence note).
…0-create-a-toprojectconfig-function-that-is-exported-from-the # Conflicts: # packages/config/README.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:93b4679b71
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- unmappedSecretApiPaths now lists all four orphan digests, sourced from the
generated V1GetAuthServiceConfigOutput contract (external_figma_secret,
external_slack_secret, hook_after_user_created_secrets,
nimbus_oauth_client_secret) — the legacy interface was a subset, not the
authority. A new apps/cli contract-guard test cross-checks every auth
apiPath and every secret-suffixed contract key against the registry, which
also closes the open-Record vacuity in the integrity test's auth checks.
- attachFrozenApiResponse no longer escapes untyped: a depth-capped pre-walk
(shared with unmappedApiFields' cap) bounds depth and cycles before
structuredClone, clone failures wrap into ProjectConfigParseError, and
deepFreeze gains a cycle guard.
- fromConfigDocument prunes containers emptied by secret-stripping (and
genuinely empty ones) instead of leaving {} litter that subtraction reports
as drift.
- README example rewritten and executed: subtract local-vs-remote and filter
LEAF paths through isComparableProjectConfigPath (the previous snippet
self-subtracted and filtered section names — always empty).
- Real phantom-drift pin replaces the agreeing-case test;
comparableProjectConfigPaths' docstring narrowed to the section-level
claim it actually delivers.
- Exhaustive schema-derived secret-strip guard (all 39 hosted-reachable
x-secret patterns); attachApiResponse throws typed on non-object input;
quantization rationale documented on the byte canonicalization; ADR 0019
addendum records the attach helper, structural verbatim, and the
debug-inspector caveat.
Registry rows, the row type, and unmappedSecretApiPaths are now exported from
the package root to serve the apps/cli contract guard (and future push
mappers).Supabase CLI previewnpx --yes https://pkg.pr.new/supabase/cli/supabase@1aa5919ac6963c2cca9306a2fc3520884d9f4569Preview package for commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:0424ac7edd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Replace the two size-parser index as-casts with charAt() access (repo
no-cast rule; review thread P1).
- expectInteger narrows integer-typed mapped fields (max_rows, db settings
uints, pooler sizes, storage limits/maxes, major_version, uintRow) — a
fractional value on an int-contract field now throws
ProjectConfigParseError instead of landing in ProjectConfig; duration
numbers (session hours, frequency seconds) deliberately stay float-tolerant.
- Apple/Google client_id combine throws on non-string main/additional values
instead of silently omitting consumed-but-malformed input.
- parseDuration rejects digit-less components ("s", ".h") the way Go's
ParseDuration does — the legacy port reads them as zero, which would let
canonicalizeDurationString rewrite a malformed document value to "0s";
malformed values now stay verbatim.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:6af9880506
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…path encoding - copyHostedValueWithoutSecrets deep-copies array elements (a hosted array can hold objects, e.g. experimental.inspect.rules — slice() aliased them back to the possibly-frozen input). - walkUnmapped preserves an unknown EMPTY object instead of pruning it — a newly introduced, not-yet-populated API section is drift signal, not noise. - pathKey JSON-encodes the segment array — no join delimiter is collision-free against arbitrary raw keys. - password_required_characters throws on a present non-string (unrecognized strings still omit — enum-ahead skew; null still means absent), matching every other mapped auth field. - The apps/cli contract guard now also asserts every auth alsoConsumes path (the apple/google additional_client_ids fold) against V1GetAuthServiceConfigOutput.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:adfdca9b4e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ng, bounded walks
- Map the three hosted OAuth-server settings the original mining missed:
auth.oauth_server.{enabled, allow_dynamic_registration,
authorization_url_path} <- oauth_server_* (note the authorization_path ->
authorization_url_path rename; no sync precedent, name-matched against the
generated contract).
- unmappedApiFields prunes KNOWN containers that are empty
(postgres_settings: {}, auth: {}) while still preserving empty objects at
unknown paths — the previous blanket preservation fabricated drift for
ordinary responses.
- Raw-attributes validation bounds total node VISITS (100k cap, typed error)
in addition to depth: a ~40-level shared-reference DAG stays inside the
depth bound but costs ~2^41 tree paths, hanging both the validator and the
path-dependent walkUnmapped (which cannot memoize by node). Real network
JSON never shares references, so rejection is the honest semantics.
- sms_test_otp and security_captcha_provider throw on present non-strings
(null still omits; unrecognized provider strings still omit as enum-ahead
skew), matching every other mapped auth field.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:f21f7e40d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…se taxonomy, readonly metadata - Depth/work-bound the raw attributes BEFORE schema decode: the mirror's auth record is Schema.Json, whose decode recurses arbitrarily, so a deep auth value overflowed with a raw RangeError ahead of the attach-time bound. - ProjectConfigParseError gains a closed reason union: caller misuse (a toProjectConfig source with neither/both keys or a non-object, non-object attachApiResponse operands) is a programming error — plain message, no upgrade suggestion, and the apps/cli actionability adapter buckets it as invalid_input/request_input instead of corrupting the external api_status KPI. toProjectConfig also guards non-object sources before Object.hasOwn so JS callers get the typed error, not a native TypeError. - _apiResponse is readonly at the type level — the runtime value is deep- frozen, so compile-permitted mutation would throw in this ESM package; nested values stay unknown (consumer narrowing owns anything deeper). - README example scoped honestly: it computes one direction of a drift check; remote-only settings need the reverse subtraction intersected with document-declared paths, and the complete computation is CLI-2156's job.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:0df89a29b4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
…uencies, provider narrowing - _apiResponse values are typed as a recursive ReadonlyJsonValue, so nested record mutation no longer compiles. One caveat no user-space type closes: the lib's Array.isArray guard (arg is any[]) yields a mutable view (microsoft/TypeScript#17002) — documented, with a readonly-preserving guard shown and pinned in tests; runtime deep-freeze backstops. - The three *_max_frequency seconds fields narrow via expectInteger (the contract types them isInt); only the genuinely fractional session-hour rows stay on expectNumber. - sms_provider throws on a present non-string before the five === comparisons (null/empty still omit all five).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:51d9793bca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ion, duration bounds
- fromConfigDocument guards non-object operands (null/undefined/array) with
the typed caller-misuse error instead of a native TypeError or a silent {}.
- The empty-container prune now removes only containers the copy itself
EMPTIED via secret stripping — an originally-empty object is declared
data: record entries whose values are empty structs by schema design
(storage.analytics.buckets, storage.vector.buckets) keep their entries,
and a genuinely-empty section (default workers) survives as declared.
- Session-hour durations narrow through a bounded validator (0..1e6 hours):
the contract only requires them finite, but huge values overflow the ns
conversion ("InfinityhNaNmNaNs") or stringify in exponent notation no
duration parser reads.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:e2db9a610d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…flow, own-key lookups - expectNumber is module-private again (knip: its last external caller moved to expectInteger/expectNumberBetween) — fixes the Check code quality CI failure on the previous push. - Apple/Google client_id validates the additional_client_ids sibling BEFORE the null-anchor early return — a malformed sibling behind a null main id was silently consumed. - parseDuration caps at Go's max int64 nanoseconds (matching Go's value-out-of-range error), so a document duration like 1e21h stays verbatim instead of canonicalizing into exponent notation or InfinityhNaNmNaNs; ramInBytes likewise rejects a non-finite suffix product (1e308KiB no longer canonicalizes to InfinityYiB). - The password charset lookup answers own entries only — an API-controlled key like "constructor" resolved the inherited function instead of omitting the unrecognized charset.
…guard 9_223_372_036_854_775_807 is not representable in float64 and trips oxlint's no-loss-of-precision (CI denies warnings); 2 ** 63 is exact and one nanosecond above Go's max int64 — the guard is approximate by design.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:b0c66bba02
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…s, freeze translation - *_max_frequency seconds bound to Go's representable range (an integer past int64-ns seconds formatted a duration this package's own parser rejects). - storage.file_size_limit rejects negative API byte counts — "-1B" persists a value ramInBytes cannot read back. - Session hours map faithfully (deliberate divergence from the legacy apply's Math.round): 1.5 → "1h30m0s", preserving push-side round-trips. - durationString prints fractional seconds in the hours/minutes branches like Go's Duration.String() — the legacy port truncates them there, which would let canonicalization silently change "1h0.5s" into "1h0m0s". - deepFreeze failures on structured-cloneable non-JSON values (typed arrays) translate into the typed caller-misuse error instead of a native TypeError. Also reorders ReadonlyJsonValue above ProjectConfig's docstring (round 4 had orphaned it from its type).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:645be1f3ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…s, non-plain rejection
- The mapping engine runs a row whose anchor key is absent when one of its
alsoConsumes siblings IS present, so a malformed sibling behind an absent
Apple/Google client_id is validated instead of silently consumed.
- durationString formats fractional seconds with toFixed(9) — a
sub-microsecond remainder under toPrecision stringified in exponent
notation ("1h1ns" -> "1h0m1.000000000e-9s") that Go duration syntax
rejects; Go prints "1h0m0.000000001s".
- The pre-clone validation walk rejects non-plain objects (Map/Set/Date/
typed arrays/class instances) with the typed caller-misuse error:
Object.freeze only freezes their wrappers, so they'd punch live mutators
through the deep-frozen metadata. Parsed JSON never produces one.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:576490531c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…source type, secret validation
- structuredClone failures carry the caller-misuse reason (a function-valued
attribute is programmatic input, not a platform response) so telemetry
stops classifying it as an external api_status failure.
- parseDuration's range bound tightens from Go's int64 to
Number.MAX_SAFE_INTEGER nanoseconds: past it, float accumulation silently
rounds small components away ("2502h1ns" would canonicalize to
"2502h0m0s", changing the value). The API-side frequency/session-hour
bounds move inside the same canonical domain so every duration the API arm
can emit is one the document arm can re-parse.
- Envelope unwrap enforces the type discriminator when present: a response
for a different resource throws instead of partially mapping wherever
attribute names overlap; a typeless envelope stays tolerated.
- Secret rows validate a present value is a string (HMAC digest) before
omitting it — a malformed smtp_pass: 123 no longer vanishes without a
parse error or drift signal.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:22e3406a1c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…loor, envelope accessor boundary - sms.test_otp canonicalizes document records through the push serialize (mapToEnv, auth.sync.ts:2603-2609) + pull envToMap re-parse, so keys or values holding literal commas converge on the post-push hosted record — same convergence rule as the CSV-backed arrays. - The session-hour floor is asymmetric like int64 itself: -2^63 ns is the valid Go minimum and its hours spelling rounds back to exactly -2^63 through magnitude-then-sign, while the ceiling still stops short of +2^63. - Envelope properties (data, attributes, type) are read exactly once each through a boundary that translates throwing accessors into ProjectConfigParseError caller_misuse — a getter can no longer pass the shape check and then leak a raw Error on a second read.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:c5dbc95353
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ty, empty test_otp absence, +2^63 document rejection
- isComparableProjectConfigPath accepts descendants of mapped container
paths (sms.test_otp record entries from leaf-path traversals), while bare
prefixes still read as sections, not mapped values.
- An empty (or round-trip-emptied) test_otp map normalizes to unmanaged
absence: the push wrapper omits sms_test_otp when the serialized map is
empty (auth.sync.ts:2487-2495), so projecting {} fabricated permanent
drift. normalizeDocument gains undefined→remove semantics with
emptied-ancestor pruning, consistent with the copy's self-emptied rule.
- parseDuration rejects a positive total landing exactly on 2^63 (one past
Go's max) while the in-loop strict bound still admits the valid -2^63
endpoint — document and API arms now agree at both endpoints.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:11e6da0ff2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
… uint clamps, dispatcher accessor guard - Multiple enabled SMS providers in a document converge on the push switch's first-enabled precedence (auth.sync.ts:2498-2539): later enabled flags flip to false before the sentinel sweep prunes their siblings, matching the API arm's report of the post-push hosted state. - Every uint-clamped row (auth uintRow, api.max_rows, db settings, storage feature limits) clamps document-side negatives too: the push mapper sends them unchanged while the pull direction clamps, so a pushed -1 projects back as 0 and the document spelling must converge on that reading. - toProjectConfig reads its selected source property through a guarded boundary — a throwing apiResponse/cliConfig accessor now surfaces as ProjectConfigParseError caller_misuse instead of a raw Error.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:05240eb3b5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…empty schemas absence, attach spread guard - Session-duration document canonicalization applies the push pipeline's own h/m sub-second truncation (normalizeDurationStr runs the truncating legacy formatter, auth.sync.ts:986-987 + config-sync.duration.ts:39-45, before durationToHours) — a document "1h0.5s" stores exactly one hour, so its canonical spelling predicts that reading; sub-minute magnitudes keep their fraction, and the API arm's Go-faithful formatter still renders hosted sub-second bits honestly. - An explicitly empty api.schemas array normalizes to unmanaged absence: push sends db_schema only when non-empty (api.sync.ts:137-139) and "" is the disable sentinel, so the API arm can never project []. - attachFrozenApiResponse's enumerable-props spread is guarded — a throwing caller getter surfaces as ProjectConfigParseError caller_misuse.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:74abe4e610
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ction accessor guard - Sub-minute fractional session durations quantize to the push formatter's seconds branch (toPrecision(10), config-sync.duration.ts:47-55) by re-parsing that exact rendering through the push-verbatim parseDuration — "59.123456789s" pushes as "59.12345679s", so the canonical spelling predicts it; below one second the two formatters' branches are identical. - fromConfigDocument wraps each section read AND its recursive copy in a guarded boundary: a throwing getter at any depth surfaces as ProjectConfigParseError caller_misuse instead of a raw error.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:fb00ff3d7f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
…ession canonicalization
Session fields travel as fractional hours (durationToHours is a bare
parseDuration / 3.6e12, auth.sync.ts:2621-2627) and map back through
Math.round(|hours| * 3.6e12) — a trip that is not always exact ('1024h4s'
returns one nanosecond high). Document canonicalization now rides the same
round trip after the formatter truncation, landing on the exact value the
API arm reports after a push; all previously pinned spellings are exact
through the trip and stay unchanged.…(CLI-2230)
Adjudicated fixes from the 2026-08-27 drift audit of the codex review rounds
against CLI-2230's design intent:
- Leniency: the pre-decode walk no longer rejects non-finite numbers —
JSON.parse('{"x":1e400}') yields Infinity, so a real platform payload with
an overflowing literal in an unknown field must decode (ADR 0019 rule 2).
caller_misuse is reserved for values JSON.parse cannot produce
(bigint/undefined/NaN). Non-finite values surface as null from
unmappedApiFields (scalars and inside array leaves, identity preserved for
all-finite arrays).
- SMTP absence semantics: an absent smtp_host says nothing — sibling rows
(user/admin_email/port/sender_name) and rate_limit.email_sent map normally;
only the explicit ""/null legacy sentinel disables. Previously a sparse
response lost these values from both the output and unmappedApiFields.
- ADR 0021 records the previously-undocumented convergence-predictor ruling:
both normalizers emit the post-push convergent form, not a verbatim
representation (SMS provider precedence, disabled-sentinel pruning,
null-gated booleans, canonicalization); docstrings and README carry the
not-verbatim caveat. ADR 0019 gains a dated leniency-boundary addendum;
ADR index gains 0020/0021.
- registry-integrity now resolves the three hand-written sentinel/precedence
tables against the schema ASTs (previously unguarded — a schema rename
silently no-opped them) and pins the SMS precedence order.
- Stale Math.trunc comment above the fraction arithmetic rewritten to match
the round-20 final state (legacy push parser is the fraction authority).
- ReadonlyJsonValue exported (referenced by public types); sentinel tables
exported for the integrity guards; smtpExplicitlyDisabledInAttributes
simplified to mirror smsProviderExplicitlyUnset.
Follow-ups filed: CLI-2266 (registry-derived push mapper, carrying the three
requirements parked on CLI-2230), CLI-2267 (legacy-parser parity fixtures).There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:492ee25c0d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
The document arm preserved a fractional or out-of-range smtp port verbatim (auth.email.smtp.port is an unrestricted Schema.Number), while the push encoding stringifies (auth.sync.ts:2390) and the API-side row parses with parseUint16 and omits on failure — so the two projections disagreed after applying such a document. The port row's new normalizeDocument replays the exact push→pull round trip (parseUint16(String(port)), omit when invalid; non-number values stay untouched per the type-mismatch rule). Schema domain deliberately unchanged.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:5d0fe98239
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…apped granularity, boundary guard
Three adjudicated threads:
- Presence-relativity (document-only): a decoded CliConfig materializes
schema defaults (all 19 providers enabled:false, smtp {enabled:false}),
while legacy push emits only raw-file-present providers
(authSubsetFromConfig, auth.sync.ts:1075-1084) and skips absent smtp
subsets (:1020-1024) — so the convergence prediction is exact only for
fields the input speaks for. ADR 0021 gains a Limits section
(omitDefaultValues composition + intersection rule; residual categories
tracked on CLI-2266). The email_sent sub-claim was verified NOT to
diverge: a decoded document always carries an explicit smtp.enabled, so
the explicit-false-only rule already agrees with push.
- Unmapped granularity (rejected behavior change): consumption is
subtree-wide by design (same ruling family as alsoConsumes); docstring
narrowed to registry apiPath granularity, full fidelity via _apiResponse,
pinned by a cidr comment-key test.
- unmappedApiFields input boundary (accepted): operand now guarded like the
sibling adapters — non-object operands and throwing _apiResponse getters
raise typed caller_misuse instead of leaking native errors.
jgoux
left a comment
There was a problem hiding this comment.
Three reproducible correctness findings from review of the current head.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…m with push emission
Addresses the three review findings on the current head:
- [P1] fromConfigDocument (and toProjectConfig) now accept the loaded pair
({ config, document } — LoadedCliConfig is structurally assignable) and
mask the decoded projection by raw-document presence, mirroring the nine
legacy push gates exactly (push.raw-presence.ts + auth.sync.ts:927/
951-960/1023/1075-1084): raw-absent ssl_enforcement, image_transformation,
s3_protocol, captcha, per-hook, and smtp (plus its rate_limit_email_sent
coupling) are omitted; auth.external keeps raw-present providers plus
apple always, with decoded values. Presence uses the same !== undefined
predicate as legacyPresenceIn. Without a document, the decoded-input
behavior stands per ADR 0021's Limits section; the loaded pair is now the
documented recommended path (loadCliConfig supplies document,
saveCliConfig does not). A presence-parity test in apps/cli pins the
package mask against legacyPresenceIn key-for-key.
- [P2] api.max_rows on the document arm mirrors push's max_rows > 0 gate as
its exact negation !(value > 0) — 0, negatives, and NaN (TOML nan is a
valid float literal) are omitted rather than projected or clamped. API
arm unchanged; flips when CLI-2266 makes push send the field explicitly.
- [P2] the document arm no longer asserts what push cannot emit: disabled
storage.analytics/storage.vector containers and the entire
auth.oauth_server section (authToUpdateBody has no oauth_server handling)
are omitted; the API arm keeps actual hosted state and the
fields-both-operands-speak-for rule keeps diffs honest.
Review hardening folded in: pair-unwrap reads go through the guarded
accessor pattern (throwing getters surface as typed caller_misuse, nested
dispatcher case included), a present-but-non-object document throws instead
of silently disabling masking, schema-key collision pins for the config/
document shape sniffing, an exhaustive presence-parity key-set check, and
ADR 0021 gains the unmanaged-by-push enumeration + CLI-2267 reference.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:1aa5919ac6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…156) CLI-2230 (#6339) landed the registry-driven ProjectConfig convergence normalizers with config diff as their intended consumer (ADR 0021), which made this branch's self-contained translation tables a parallel implementation of the same mapping. The classifier now takes two ProjectConfig projections — fromConfigDocument({config, document}) locally (raw-presence-masked) and fromApiProjectConfig(response) remotely — walks the union of their leaves filtered by isComparableProjectConfigPath, and keeps the declared-set-driven classes, masked transparency (registry isSecret rows), and env naming. remote_only suppression baselines on the default config's projection, falling back to the raw default value for push-gated containers (network restrictions' allow-all) and then the zero value. Deletes config-diff.{managed,auth,read}.ts (~900 lines); scope reporting moves to the command layer off the raw response attributes; ADR 0022 rewritten to record the consolidation; --target registered in the CLI-1896 value-consuming flag guard; purity-pin allowlists extended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What changed
Implements CLI-2230: the hosted-project subset type
ProjectConfigand its normalizers, exported from@supabase/config's pure (browser-safe) entrypoint so the CLI and Studio share one mapper.fromConfigDocument(config)— projection of aCliConfigdocument (or anyEffectiveConfig) down to the hosted sections (api,auth,db,realtime,storage,workers,experimental).fromApiProjectConfig(input)— translation of a Management API v2 project-config response (full envelope,dataobject, or baredata.attributes). Registry-driven: 233 mapping rows mined from the legacyconfig pushsync mappers (config-sync/*.sync.ts), covering renames (rate_limit_otp→sign_in_sign_ups), boolean inversions (disable_signup,mailer_autoconfirm), unit conversions (seconds/hours → Go duration strings, int64 bytes → BytesSize), the GoTrue key table (19 OAuth providers, 6 hooks, 5 SMS providers), andx-secretomission (the API only reports HMAC digests). Decode is lenient per ADR 0019 — unknown/API-ahead keys never fail; the raw attributes ride along as a non-enumerable_apiResponse(invisible to encodes, spreads, and structural walks; never persisted), with a registry-derivedunmappedApiFields()reader.toProjectConfig(source)— thin dispatcher over both ({ cliConfig }/{ apiResponse }).Operand widening (ruling on CLI-2230)
ProjectConfigis deliberately sparse — an API response never mentions sections it doesn't manage, and flooding in schema defaults would fabricate drift. To make it a first-class operand of the comparison core,BaseCliConfigis replaced by the family-neutralEffectiveConfig = DeepPartial<Omit<CliConfig, "remotes">>onsubtractCliConfig/omitDefaultValues. No runtime change — the subtraction walk already had the right absence semantics. Recorded in ADR 0018's 2026-08-26 addendum; the naming-rule generalization lands separately with CLI-2238 (#6335).apps/cli
project-config-api-drift.unit.test.ts) pins the generatedV2GetProjectConfigOutputattributes against the package's lenient input schema: assignability (catches type widening) plus per-section key-set assertions (catch added/removed/renamed fields) — so OpenAPI drift fails compile before it can silently break the mapping. No runtime dependency onpackages/apiwas added.ProjectConfigParseErrorregistered in the error-actionability table asapiStatus(a malformed platform response, not a user config mistake).Reviewer notes
null→ omit (sparse output has no local document to fall back to),uri_allow_listtrimming,smtp_host: ""treated as disabled,sms_autoconfirmnot inverted (matchesauth.sync.ts:1677/:2485— only the mailer counterpart inverts).db_schema: ""maps to{ api: { enabled: false } }only, mirroringapplyRemoteApiConfig's early return; an absentdb_schemadoesn't gate the sibling fields.apps/clioriginals. (ADR 0019's attach helper did land in the second round below, asattachApiResponse.)Second review round (commit 93b4679) — adjudication record
A two-set review (architect/engineer/security/DX + adversarial execution) ran against bdd607f. The registry mapping itself survived three independent verification passes with zero defects; every accepted finding about the surrounding surface is fixed in 93b4679:
fromConfigDocumentnow deep-copies and omits everyx-secretleaf (schema-derived,lib/secret-paths.ts) — decoded documents hold plaintext credentials, and the subtract composition rendered them as drift.normalizeDocumentcolumn canonicalizes document-side duration and byte-size spellings so both normalizers emit one form for one logical value.Schema.Unknown, so a platform type change on a field nothing reads can no longer fail every decode.ProjectConfigParseErrorgainedmessage/detail/suggestion(upgrade-then-report), schema-issue paths lift intoapiPath, telemetry addsfingerprint_suffix: "api_response"+has_suggestion.smtp_host/smtp_port, and CIDR entries now throw typed errors instead of fabricating values (enabled: false), vanishing silently, or partially filtering a security allowlist;expectNumberrejects non-finite._apiResponse: cloned + deep-frozen at attach (no caller aliasing);attachApiResponseexport restores it across spread/clone round-trips; invisibility claim narrowed (serializers and walks — debug inspectors like Bun'sconsole.logstill print it; never log an API-sourced config).unmappedApiFields: recursion depth cap;unmappedSecretApiPathsdeny list (external_figma_secret— the one genuine orphan digest; the review's other two candidates don't exist in the legacy source).registry-integrity.unit.test.ts(all 233 rows' paths resolve against the schema ASTs, 470 generated cases); drift-guard key-set levels completed;comparableProjectConfigPaths/isComparableProjectConfigPathso diff consumers never hand-maintain field lists;inverseimplementations dropped until the push mapper derives them.Explicit rejections (reviewed, not silently deferred):
alsoConsumesstatic consumed-set: "consumed" means known-to-this-registry-version, not mapped-on-this-run — analsoConsumessibling whose anchor didn't run stays suppressed by design and remains in_apiResponse. Documented inwalkUnmapped._apiResponse: deferred — clone+freeze resolves the aliasing hazard and the narrowed docstring is honest; revisit if CLI-2156 consumption shows inspectors bite (ADR 0019 records the alternative).fromCliConfig/fromApiResponserenaming: the shipped names are already recorded in ADR 0020 (docs(config): document the CliConfig/ProjectConfig/CliSettings naming convention #6335), Linear, and coordinated docs — symmetry isn't worth re-coordinating three artifacts.EffectiveConfig: took the ADR 0018 consequence note instead (widening removes the static every-section guarantee; callers own operand completeness).ProjectConfigApiAttributesplacement and"sideEffects": false: deferred to their owning issues with notes filed — CLI-2234 (export-surface audit) and CLI-2232 (verify the bundler claim against a built artifact).Verification round (commits 5f389c9 + 0424ac7)
An executed verification pass over 93b4679 re-ran all prior attacks (10/10 now pass) and surfaced residuals, fixed in 0424ac7:
V1GetAuthServiceConfigOutputcontract (not the legacy interface) is the authority;external_slack_secret,hook_after_user_created_secrets, andnimbus_oauth_client_secretjoin the deny list, and a new apps/cli contract-guard test cross-checks every auth apiPath + secret-suffixed contract key against the registry (also closing the open-Record vacuity in the integrity test's auth checks). Registry rows are now exported from the package root to serve it.ProjectConfigParseErrorinstead of rawRangeError/DOMException(depth-capped pre-walk, wrapped clone, cycle-guardeddeepFreeze).comparableProjectConfigPaths' docstring narrowed to the section-level claim it delivers; ADR 0019 gained a dated addendum (attach helper, structural "verbatim", debug-inspector caveat).The merge commit resolves the pre-agreed README conflict with #6335 (their
## Namingsection wins, its "in flight" sentence flipped) and de-stales ADR 0020's phrasing, per the coordinated rebase checklist. Known external: rootcheck:allfails on@supabase/cli-go#lint:check(5 gosec findings in Go files byte-identical to develop — develop-side/toolchain, not this PR).Drift-audit round (commit 492ee25)
A 2026-08-27 audit compared the post-codex state against CLI-2230's design intent. All eight structural commitments verified intact (pure entrypoint, no
packages/apidependency, sparse output +EffectiveConfig, ADR 0019 guardrails, registry-driven withinverseunimplemented, purity graph, naming, secret stripping). Three executed repros surfaced semantic drift introduced by the codex rounds; fixed here:JSON.parse('{"x":1e400}')yieldsInfinity, so the round-12 non-finite pre-decode rejection hard-failed a real JSON payload on an unknown field (mis-bucketed ascaller_misuse). The walk now rejects only bigint/undefined/NaN(valuesJSON.parsecannot produce); non-finite values decode and surface asnullfromunmappedApiFields(scalars and inside array leaves, identity preserved for all-finite arrays). ADR 0019 gains a dated leniency-boundary addendum.smtp_hostpreviously counted as the disabled sentinel, silently droppingsmtp_user/smtp_admin_email/rate_limit.email_sentfrom both the output andunmappedApiFields— contradicting the SMS absent-vs-sentinel rule beside it. Absence now says nothing; only the explicit""/nulllegacy sentinel disables (three-state, extended to theemail_sentcross-section rule).null-gated booleans, CSV/uint/duration canonicalization) — defensible (it kills phantom drift for CLI-2156) but never adjudicated or documented. Ruling: accepted, now recorded in ADR 0021, theProjectConfig/fromConfigDocument/fromApiProjectConfigdocstrings, and the README ("not a verbatim representation" caveat).DISABLED_SENTINEL_PRUNES,DISABLED_SENTINEL_ENTRY_SWEEPS,SMS_PROVIDER_PUSH_PRECEDENCE) now resolve every path/key against the schema ASTs inregistry-integrity— previously a schema rename silently no-opped them — and the SMS precedence order is pinned against the legacy push switch (auth.sync.ts:2498-2539).Math.trunccomment above the fraction arithmetic (round-11/14 artifact the round-20 revert missed),ReadonlyJsonValueexported (it appears in public types),smtpExplicitlyDisabledInAttributessimplified to mirrorsmsProviderExplicitlyUnset, ADR index gains 0020/0021.An engineer-review pass over the fix diff verified every change; its findings (the
email_sentasymmetry, stale ADR citations, formatter gate, doc phrasing) are folded in. 984/984 package tests, 461/461 apps/cli guard tests, typechecks clean both packages.Follow-ups filed so they survive CLI-2230 closing: CLI-2266 (registry-derived push mapper — carries the three requirements previously parked as CLI-2230 comments) and CLI-2267 (parity fixtures pinning the replicated legacy parsers against
config-sync/*).Fixes CLI-2230
🤖 Generated with Claude Code