Files
civitai__civitai/containers
Justin Maier 32239d2d4d feat(licensing): turn the SellMerge write paths on (#4874)
* feat(licensing): turn the SellMerge write paths on

The contract half of yesterday's expand/contract. Every pod has known the label
for eleven hours and prod carries zero rows using it, so the paths that were
held back can open: the schema @default and the upload form's default set go
back to five values, the "Sell merges using this model" option returns, and the
refine that refused the member at the upsert contract is deleted.

Measured before writing this, prod replica: 424,641 models hold Sell and lack
SellMerge, and 0 hold SellMerge. Nothing wrote it while the paths were shut.

The two decision cases are INVERTED rather than deleted, because what they guard
was never really about SellMerge. Restrictions are emitted by ABSENCE, so a
member missing from a default set produces no clause and therefore GRANTS the
permission -- silently, with no badge a creator would notice and no type error.
Both now derive the expected set from the enum (Object.values minus None) rather
than from a hand-written list, so the next member added without wiring reddens.
A list would have needed updating by whoever forgot.

Both cases are renamed to what they now assert; a stale test name is a comment
that survives every refactor. The decision docblock is deleted rather than
reworded, since the decision expires with this PR.

Controls: dropping SellMerge from the schema default, from the form default, or
from the option list each redden, as does re-adding a withholding refine, as
does the field ceasing to validate members at all -- that last one is what stops
the accept-loop passing vacuously. An innocent comment reword stays green.

The backfill does NOT ride this PR. It runs after this is deployed, because
until then the refine rejects a save carrying the member and the edit form
resubmits it untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(licensing): assert the three sites AGREE, not that every member is granted

Five lanes on #4874; two findings were against my own work.

The docblock stated the absence rule backwards -- it claimed a member missing from
the granted array produces no clause and so GRANTS the permission. The emitter
returns '' when the array INCLUDES the member, so absence emits the clause and
RESTRICTS. The real hazard is one file over and about the clause map, not the
default set. The parent's commit message repeated the inversion.

The guard asserted a policy: both defaults equal to every enum member requires the
next CommercialUse addition to be granted to every model by default, which forbids
the expand/contract manoeuvre shipped eleven hours ago. Rewritten as the invariant
-- the two defaults and the option list must AGREE -- so withholding a new member
everywhere at once stays legal, while a member granted by default and missing from
the options is caught.

Three attempts, each caught by a control: a whole-file count broke because Rent is
a substring of RentCivit and Sell of SellMerge; a per-member count misfired on the
cascade's legitimate references.

The render-site guard pins that the list is mapped whole. It does NOT cover the
disabled predicate -- no text guard expresses that without naming members, which
reintroduces the policy. Stated in the test as a gap.

From the other lanes: the local-dev DDL was missing the enum value, so a fresh
checkout would reject the first model create; the migration header and the backfill
endpoint carried claims this PR made false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(seed): seed the five-value permission shape alongside the legacy ones

No seeded row granted SellMerge, so the sell/merge split was unexercisable in
local data. Tracked despite scripts/local-dev being gitignored -- the ignore
rule does not apply to already-tracked files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:10:43 -06:00
..