@@ -12895,6 +12895,36 @@ const step18: MigrationStep = {
1289512895 + 'restore or purge grant — an erasure-request runbook, an access review, an audit control — '
1289612896 + 'names the mechanism it actually uses instead.',
1289712897 },
12898+ // #20321 (ADR-0049 enforce-or-remove) — the D3 entry of the
12899+ // `permission-rls-tags-removed` family (ruling B on #17152: one D3 entry per
12900+ // retirement family, even when D2 is lossless). The strip changes no access
12901+ // decision; what it leaves is whatever process was built on the belief that a
12902+ // policy's tags were read.
12903+ {
12904+ id: 'permission-rls-tags-retired',
12905+ surface: 'permission.rowLevelSecurity[].tags — the free-form categorization tags on a row-level '
12906+ + 'security policy',
12907+ replacement: '(removed — no mainstream platform tags a row-level policy, and nothing here ever '
12908+ + 'read one.) A policy is identified by its `name` and its `object`, and reported by those and '
12909+ + 'its predicate; its purpose belongs in `description`. Whom a policy applies to is decided by '
12910+ + '`positions`, never by a tag.',
12911+ reason: 'The D2 conversion `permission-rls-tags-removed` deletes `tags` from every row-level '
12912+ + 'security policy in author sources and in stored permission rows, and the delete is '
12913+ + 'lossless: the RLS compiler never consulted the key and nothing else acted on it — no '
12914+ + 'report, audit filter or review queue selected on it — so no access decision changes. '
12915+ + 'The judgment is about what people '
12916+ + 'believed. An admin who tagged a policy `gdpr` or `pci` may have expected a compliance '
12917+ + 'report, an audit filter or a review queue to pick it up; none ever did. An author who '
12918+ + 'wrote a tag such as `managers_only` may have believed it scoped the policy; it never did '
12919+ + '— only `positions` narrows whom a policy applies to. Any report, runbook or control that '
12920+ + 'relies on either belief needs another path, and choosing that path is a governance '
12921+ + 'decision no conversion can make.',
12922+ acceptanceCriteria: 'No authored or stored row-level security policy carries `tags`; the parse '
12923+ + 'refuses the key with the prescription. Access decisions are unchanged: every policy admits '
12924+ + 'and refuses exactly the rows it did before the upgrade. Every policy whose tag expressed an '
12925+ + 'audience has that audience in `positions`, and every compliance report, audit filter or '
12926+ + 'review process that assumed policy tags names the mechanism it actually uses instead.',
12927+ },
1289812928 {
1289912929 id: 'platform-timezone-columns-iana-domain-refused',
1290012930 surface:
@@ -19091,6 +19121,20 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly<Record<number, readonly string[]>>
1909119121 // D2 conversion `permission-allow-restore-purge-removed`, which strips the
1909219122 // key from every object grant in `permissions[].objects`.
1909319123 'security/ObjectPermission:allowRestore',
19124+ // #20321 (ADR-0049 enforce-or-remove; graded RETIRE by the maintainer's
19125+ // criterion for declared-but-unenforced families — does a mainstream platform
19126+ // have the capability?). `RowLevelSecurityPolicy.tags` promised categorization
19127+ // and reporting for governance and compliance, and nothing ever read it: the
19128+ // RLS compiler never consults it, objectui's permission preview renders only
19129+ // the policy count and its policy editor neither seeds nor reads the key, and
19130+ // cloud has no reader. No mainstream platform tags a row-level policy. The
19131+ // policy shape is `strictObject`, but the def is reachable from the
19132+ // `permission` metadata root, so the route is the `retiredKey()` tombstone
19133+ // (the `rls.priority` posture one key over): the key stays in the walked shape
19134+ // as `[RETIRED]`, and authoring it is a tsc error and a parse error carrying
19135+ // the prescription. D2: `permission-rls-tags-removed`; D3:
19136+ // `permission-rls-tags-retired`.
19137+ 'security/RowLevelSecurityPolicy:tags',
1909419138 // #15679 (stack card 4/6 of #14478) — ruling B. `AccessControlConfig.maxAge` said
1909519139 // "CORS preflight cache duration in seconds" in prose and nothing else.
1909619140 // ⚠️ This key is deliberately a RENAME and not an `externalVocabulary` marker,
0 commit comments