diff --git a/docs/qa/platform-checklist/areas/access-security.json b/docs/qa/platform-checklist/areas/access-security.json index 0136c48bc9..dd2275739e 100644 --- a/docs/qa/platform-checklist/areas/access-security.json +++ b/docs/qa/platform-checklist/areas/access-security.json @@ -2099,7 +2099,7 @@ "title": "Activation-ledger write authority (ADR-0126 §5): the manage_metadata tier refuses first on every posture, and the shared operator gate — INERT on stock `single` — demands the platform_admin POSITION in walled postures, at both the flow and action doors", "since": "v17", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", "personas": [ @@ -2128,7 +2128,7 @@ "as the plain member again: repeat the flow toggle with a deliberately INVALID body (e.g. {\"enabled\": \"false\"}) — the refusal must still be the 403, not a 400 body-validation answer (the gate runs before body validation, automation.ts; activation-gate.ts)", "GET /api/v1/data/sys_metadata_activation (reads are open — sys-metadata-activation.object.ts) and confirm no row for either artifact was written by the refusals", "author the scratch manage_metadata permission set, grant it to a fresh member, and prove server-side that the member's positions do NOT include platform_admin (session row / GET /auth/get-session — never the grant gesture)", - "as that member: POST /api/v1/automation/showcase_urgent_task_alert/toggle {\"enabled\": false} — expect 200; read the ledger row back (metadata_type 'flow', active false, organization_id NULL); then re-enable ({\"enabled\": true}) to restore the fixture", + "as that member: POST /api/v1/automation/showcase_urgent_task_alert/toggle {\"enabled\": false} — expect 200; read the ledger row back (metadata_type 'flow', active false, and NO organization_id key on the row — the ledger carries no tenant column at all, #15024 / ADR-0131 D7); then re-enable ({\"enabled\": true}) to restore the fixture", "as the same member: POST /api/v1/actions/_activation/showcase_task/showcase_mark_done {\"enabled\": false} — expect 200 + the metadata_type 'action' row; re-enable to restore", "walled-posture legs: BLOCKED on stock fixtures (see knownGaps) — the group/isolated matrix (org admin with manage_metadata refused naming the posture and the per-artifact remedy; platform_admin POSITION admitted; no row on refusal) is pinned at unit level by automated.ref" ], @@ -2152,9 +2152,9 @@ "evidence": "the ledger reads + the invalid-body trace" }, { - "clause": "the §5 gate is INERT on `single`, not satisfied: a caller holding manage_metadata but NOT the platform_admin POSITION succeeds at BOTH doors, and each write lands one install-level ledger row (organization_id NULL) — install-level and org-level are the same scope on one logical tenant (activation-gate.ts,141-147)", + "clause": "the §5 gate is INERT on `single`, not satisfied: a caller holding manage_metadata but NOT the platform_admin POSITION succeeds at BOTH doors, and each write lands one deployment-level ledger row — install-level and org-level are the same scope on one logical tenant (activation-gate.ts,141-147), and the ledger carries no tenant column to say otherwise", "oracle": "api", - "verify": "the scratch-set member's positions are proven server-side to exclude platform_admin (BUILTIN_IDENTITY_PLATFORM_ADMIN = 'platform_admin', packages/spec/src/identity/eval-user.zod.ts), then both toggles answer 200 and GET /api/v1/data/sys_metadata_activation shows the metadata_type 'flow' and 'action' rows with active false, organization_id NULL; re-enable restores active true", + "verify": "the scratch-set member's positions are proven server-side to exclude platform_admin (BUILTIN_IDENTITY_PLATFORM_ADMIN = 'platform_admin', packages/spec/src/identity/eval-user.zod.ts), then both toggles answer 200 and GET /api/v1/data/sys_metadata_activation shows the metadata_type 'flow' and 'action' rows with active false and no organization_id key at all; re-enable restores active true. ⛔ Do not score the tenant half from a VALUE read — a check of the shape `row.organization_id ?? null` is `null` for a column that does not exist, so it passes while measuring nothing; read the key set (platform-core.activation-ledger-row-contract owns that probe)", "evidence": "the position proof + both 200 traces + the ledger rows" }, { @@ -2195,8 +2195,8 @@ "packages/runtime/src/domains/automation.ts#FLOW_ENABLEMENT_DENY_MESSAGE (FLOW_ENABLEMENT_DENY_MESSAGE), (isFlowActivationWrite), (gate ordering ahead of service probe and body checks)", "packages/runtime/src/domains/actions.ts (both tiers at the action door)", "packages/spec/src/security/tenancy-posture.ts#TenancyPostureSchema (TenancyPostureSchema — the variants source; postureEnforcesWall; the ADR-0105 D12 entitlement note)", - "docs/adr/0126-packaged-metadata-customization-model.md §5 (D3: install-level rows, operator-gated in multi-org postures)", - "#12438 (the sweep), #12159 (ADR-0126 flow legs), #10243 (the measured incident the gate makes durable), #12157 / #12160 (the two door cards)" + "docs/adr/0126-packaged-metadata-customization-model.md §5 (D3: install-level rows, operator-gated in multi-org postures) — ⚠️ the operator gate this item owns STANDS; only §5's separate 'the org column is reserved' bullet was withdrawn (ADR-0131 D7), which is why the ledger read-backs here name no organization_id", + "#12438 (the sweep), #12159 (ADR-0126 flow legs), #10243 (the measured incident the gate makes durable), #12157 / #12160 (the two door cards), #15154 (the ledger read-back detail re-grounded after #15024 dropped the tenant column — the gate itself and its pins are unchanged)" ], "history": [ { @@ -2204,6 +2204,12 @@ "date": "2026-08-26", "change": "new — ADR-0126 §5 write authority had no checklist coverage. Authored with the stock-posture truth stated up front (the §5 gate is INERT on `single`; the D1 refusal is the manage_metadata tier) so a runner never scores the wrong gate, and with the group/isolated legs blocked(fixture) on the enterprise-posture gap (#9334 precedent) instead of faked. Variants pinned to TenancyPostureSchema (verified extractable by the validator's enumSource extractor)", "ref": "#12438" + }, + { + "revision": 2, + "date": "2026-09-04", + "change": "read-back detail only: #15024 dropped sys_metadata_activation's reserved organization_id column, so the two ledger read-backs (step 7 and the inert-gate clause) asked the runner to see a column that is gone. They now read the row's KEY SET instead — a value check of the shape `row.organization_id ?? null` is `null` for a column that does not exist, so carrying the old spelling forward would have left a leg that passes while measuring nothing. ⚠️ The gate itself is UNCHANGED: ADR-0126 §5's operator-gate half stands and its unit pins pass unmodified; only §5's separate 'the org column is reserved' bullet was withdrawn (ADR-0131 D7). The source citation now says so, so the next reader does not infer the gate moved too", + "ref": "#15154" } ] }, diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 3ef2375469..3cb88d9420 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -1469,10 +1469,10 @@ }, { "id": "platform-core.activation-ledger-row-contract", - "title": "sys_metadata_activation row contract: reads open / generic-data-API writes 405, one NULL-collapsed row per artifact, org-carrying rows skipped on read, re-enable updates never deletes, and rows exist ONLY for metadata_type flow|action (the ADR-0126 scope wall)", + "title": "sys_metadata_activation row contract: reads open / generic-data-API writes 405, one row per (metadata_type, name) with NO tenant column on the table at all, re-enable updates never deletes, and rows exist ONLY for metadata_type flow|action (the ADR-0126 scope wall)", "since": "v17", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "api", "personas": ["seeded admin (admin@objectos.ai / admin123)"], @@ -1484,17 +1484,17 @@ ], "knownGaps": [ "metadata_type is an untyped Field.text (maxLength 100) with exactly two string-literal writers in the tree — 'flow' (packages/services/service-automation/src/flow-activation-store.ts) and 'action' (packages/objectql/src/action-activation.ts). There is NO spec enum to pin: the variants below are enumerated BY HAND and deliberately carry no enumSource, so a third writer (ADR-0126 §8 pre-charts tool/skill/position) lands without failing any ratchet. Re-derive the writer list (grep for METADATA_TYPE literals) before scoring the scope wall, and bump this item's variants when a third writer ships", - "the reserved organization_id column has NO writer by design (ADR-0126 §5 — the per-org dimension is additive-later), so the org-row-skip leg can only be staged by hand-seeding the sqlite file between boots; the hand-seeded row is the run's own artifact and must be cleaned up (or the DB discarded)" + "the tenant-column leg is a SCHEMA probe, not a row probe: there is no organization_id column left to hand-seed (systemFields { tenant: false } — ADR-0131 D7 withdrew ADR-0126 D3's reserved-and-never-written column before it ever shipped), so it is staged by reading the physical column list and by an INSERT that NAMES the column and is refused. Its anti-vacuity control INSERT does land a row — that row is the run's own artifact and must be deleted before the restart (or the DB discarded)" ] }, "steps": [ "boot isolated on the file DB; as admin flip BOTH shipped switches off (flow toggle showcase_task_completed, action flip showcase_mark_done); capture both 200s", - "read the ledger through the open read path: GET /api/v1/data/sys_metadata_activation — capture every row; each carries the five declared columns (metadata_type, name, package_id, organization_id, active) and organization_id is NULL on every row a shipped switch wrote", + "read the ledger through the open read path: GET /api/v1/data/sys_metadata_activation — capture every row; each carries the four declared ledger columns (metadata_type, name, package_id, active) beside the driver's id and the injected audit family, and NO tenant column: organization_id is ABSENT from the row's key set, never present-and-NULL on it. ⛔ Assert the KEY SET, never a value read — a value assertion of the shape `row.organization_id ?? null` is `null` for a column that does not exist, so it passes while measuring nothing (the dogfood pin was inverted into a key-set assertion for exactly this reason)", "scope wall sweep: the distinct metadata_type set over ALL rows is exactly {'flow','action'} — any other value (hook/permission/position/capability/…) is a FAIL of the model, not a coverage discovery: hooks are code-only by ADR-0126 §3 amendment ruling 1 (no ledger row, no disable switch), and packaged permission sets ride their kind's own `active` field per the 2026-08-26 #12159 ruling (cross-ref access-security.packaged-permission-set-lifecycle)", "write-refusal probes, all three verbs: POST /api/v1/data/sys_metadata_activation {metadata_type:'action',name:'qa_probe',package_id:'qa',active:false}, PATCH /api/v1/data/sys_metadata_activation/ {active:true}, DELETE /api/v1/data/sys_metadata_activation/ — each answers 405 naming the refused operation and the allowed set; re-read confirms every row unchanged", "re-enable both switches — the SAME rows update to active true (same ids, row count unchanged, nothing vanished)", - "uniqueness probe (direct DB): stop the server; in the sqlite file INSERT a second NULL-organization row for an existing (metadata_type, name) pair — the COALESCE'd unique index (ADR-0120 D3 NULL-collapse) refuses with a constraint error; capture it. A hand-written composite naming organization_id verbatim would be NULL-distinct and enforce nothing (#5030, measured) — the refusal is the proof the declared 'organization' arm is live", - "org-row-skip probe (direct DB): INSERT one org-CARRYING row (organization_id = any non-null id, metadata_type 'action', name showcase_mark_done, active 0); restart against the SAME DB; POST /api/v1/actions/showcase_task/showcase_mark_done → NOT refused by the switch (the artifact stays ARMED — the install-level read skips org rows, it never merges them); CONTROL, same boot: flip the real install-level switch off through the door → dispatch now 409 ACTION_DISABLED (proves the probe can see the switch, so the armed reading is not vacuous); re-enable and delete the hand-seeded row (or discard the DB)", + "uniqueness probe (direct DB): stop the server; in the sqlite file INSERT a second row for an existing (metadata_type, name) pair — the declared unique:'global' index over exactly those two columns refuses with a UNIQUE constraint error; capture it. Both key parts are required text, so no NULL arm is left to collapse: the earlier unique:'organization' spelling asked the driver to prepend the tenant column in its COALESCE'd form (ADR-0120 D3) against the #5030 NULL-distinct hole, and with the column gone normalizeDeclaredIndex resolves tenantField to null — so the materialized DDL is byte-identical either way and the current spelling is a re-SPELLING, not a change of shape (sys-metadata-activation.object.ts records it)", + "no-tenant-column probe (direct DB, server still stopped): read the physical schema — PRAGMA table_info(sys_metadata_activation) — and record the column list: metadata_type, name, package_id and active are present beside the driver's id and the injected audit family (created_at / created_by / updated_at / updated_by), and NOTHING tenant-shaped is — organization_id is absent from the list entirely. Then attempt INSERT INTO sys_metadata_activation (id, metadata_type, name, package_id, active, organization_id) VALUES ('qa_probe_no_tenant','action','qa_probe_no_tenant','qa',0,'org_qa') — sqlite refuses with `table sys_metadata_activation has no column named organization_id` and no row lands. CONTROL (anti-vacuity, same DB): the SAME statement with the organization_id column and its value dropped SUCCEEDS — so the refusal names the missing column, not a malformed statement and not the unique index (the (metadata_type, name) pair is fresh on purpose, so step 6's index cannot be what answered). DELETE the control row, then restart against the SAME DB and confirm the ledger reads exactly as it did before the probe", "record the operability posture: absence of a row reads as ACTIVE (an empty ledger is the normal stock state, not an error), and the data-API read IS the only 'what is off here' surface today — ObjectQLEngine.listDisabledActions() (packages/objectql/src/engine.ts) has zero route consumers" ], "acceptance": [ @@ -1505,16 +1505,16 @@ "evidence": "the list read + the three refusal bodies + the empty diff" }, { - "clause": "row identity holds and is NULL-collapsed: one row per (metadata_type, name) at install level — organization_id NULL on every row the shipped switches write (§5 reserved, no writer sets it), and a second NULL-org row for the same pair is REFUSED by the declared unique:'organization' index, so one artifact can never carry two contradictory active rows", + "clause": "row identity holds over exactly two key parts: one row per (metadata_type, name), deployment-wide — and a second row for the same pair is REFUSED by the declared unique:'global' index, so one artifact can never carry two contradictory active rows", "oracle": "log", - "verify": "the row dump from step 2 (organization_id NULL throughout) + the sqlite constraint refusal from step 6 (the driver's COALESCE(organization_id,'__global__') NULL-collapse, ADR-0120 D3 — the spelling sys-metadata-activation.object.ts documents against the #5030 NULL-distinct hole)", + "verify": "the row dump from step 2 + the UNIQUE constraint refusal from step 6, whose index names the two key columns and nothing else. ⚠️ The COALESCE(organization_id,'__global__') collapse ADR-0120 D3 provides is NOT reachable here and must not be looked for: with no tenant column normalizeDeclaredIndex has no part to prepend, and both key parts are required, so a plain two-column unique is the whole of the row identity (sys-metadata-activation.object.ts)", "evidence": "the row dump + the refused INSERT with its constraint error" }, { - "clause": "org-carrying rows are SKIPPED on read, never merged: a hand-seeded row with organization_id set and active=0 leaves the artifact ARMED across a restart — reading it as install-level would apply one organization's choice to the whole installation (the #10243 direction arrived at from the read side); the control leg proves the same probe detects the real install-level switch", - "oracle": "api", - "verify": "step 7 both arms: post-restart dispatch NOT 409 with only the org row present (skip at packages/core/src/utils/metadata-activation-store.ts), then 409 once the install-level row is written through the door", - "evidence": "the seeded row + the two dispatch statuses bracketing the control flip" + "clause": "the ledger carries NO tenant column, and cannot be handed a row that has one: PRAGMA table_info lists the four ledger columns beside id and the audit family with nothing tenant-shaped among them, and an INSERT naming organization_id is refused by sqlite while the same INSERT without it lands. A row here is DEPLOYMENT-level state no organization owns (ADR-0131 D7, which WITHDREW ADR-0126 D3's reserved-and-never-written column before 17.3), so there is no per-organization row left for a read to merge OR to skip", + "oracle": "log", + "verify": "step 7 both arms: the PRAGMA column list with organization_id absent, then the refused INSERT quoting sqlite's `has no column named organization_id`, bracketed by the control INSERT that succeeds once the column is dropped from the statement. Ground the DECLARATION side against systemFields { tenant: false } at sys-metadata-activation.object.ts — the opt-out that REMOVES the column rather than one that leaves it unwritten, since resolveInjectedSystemColumns would provision it by injection with no field declared at all. ⛔ Never score this clause from a row-VALUE read (see step 2)", + "evidence": "the PRAGMA column list + the refused INSERT with sqlite's message + the successful control INSERT" }, { "clause": "re-enable UPDATES the row, never deletes it: after flipping both switches back on, the same rows persist with active true — the ledger records the administrator's CHOICE instead of erasing it (ADR-0126 §6 wall 3), and the store's engine slice deliberately has no delete", @@ -1537,9 +1537,9 @@ ], "negative": [ "a 2xx on ANY generic-data-API write to sys_metadata_activation is a FAIL of the engine-owned posture — these rows have exactly one writer class (the ADR-0126 doors)", - "the artifact reading as DISARMED after step 7's restart (the org-carrying row applied install-wide) is the #10243 failure from the read side — a P1-severity finding inside a P2 item, extract it", + "an organization_id column PRESENT on the physical table is the regression step 7 exists for, and it is WORSE than the shape it replaced: the store no longer filters or skips anything on read (there is no column left to filter on), so a resurrected tenant column would let an org-carrying row be merged straight into the deployment-wide answer — one organization's choice applied to the whole installation, #10243 from the read side. A P1-severity finding inside a P2 item, extract it", "a row deleted by re-enable is a FAIL of §6 wall 3 even though every door answered 200 — the wire looks identical either way, only the row read tells them apart", - "two contradictory active rows accepted for one artifact is a FAIL of the declared index (the exact #5030 hole the unique:'organization' spelling exists to close)", + "two contradictory active rows accepted for one artifact is a FAIL of the declared unique:'global' index — and ⛔ do not diagnose it as the #5030 NULL-distinct hole: that hole needed a nullable tenant column inside the key, and this table has none. Both key parts are required text, so a duplicate accepted here means the index is missing or was never materialized, not that a NULL slipped through", "treating this item's variants as a pinned enum is a mis-read: they are hand-enumerated and un-pinned by construction — a stale two-value list over a tree that grew a third writer is a checklist defect, not a platform one; revise the item" ], "variants": [ @@ -1549,16 +1549,19 @@ "traps": ["destructive-in-place", "absence-inference"], "automated": { "kind": "unit", - "ref": "packages/objectql/src/action-activation.test.ts + packages/services/service-automation/src/flow-activation-ledger.test.ts (the store-level pins: org-row skip, 0-reads-as-false, update-not-delete, organization_id never in the write payload); packages/qa/dogfood/test/packaged-activation-ledger-reach.dogfood.test.ts (row shape + update-not-delete on a live boot)" + "ref": "packages/objectql/src/action-activation.test.ts + packages/services/service-automation/src/flow-activation-ledger.test.ts (the store-level pins: the unfiltered deployment-wide read — 'reads EVERY row … with no tenant axis', the pin that REPLACED the org-row-skip one — plus 0-reads-as-false, update-not-delete, and organization_id absent from the write payload asserted as a missing KEY); packages/qa/dogfood/test/packaged-activation-ledger-reach.dogfood.test.ts ('writes ONE deployment-level row, and the TABLE has no tenant column at all' — a key-set reading off the driver's own SELECT on a booted stack, i.e. a DDL reading rather than a re-statement of the declaration; plus update-not-delete on a live boot)" }, "source": [ - "packages/platform-objects/src/system/sys-metadata-activation.object.ts#apiMethods — apiMethods ['get','list']; unique:'organization' NULL-collapsed index + the #5030 rationale; organization_id reserved-NULL; no-lifecycle ruling; what the object is NOT (§4 posture)", - "packages/core/src/utils/metadata-activation-store.ts#setActive — org-row skip on read (the wall); read-then-write setActive, organization_id never in the payload; delete-less engine slice; 0-reads-as-false", + "packages/platform-objects/src/system/sys-metadata-activation.object.ts#apiMethods — apiMethods ['get','list']; the unique:'global' index over (metadata_type, name) and why the earlier 'organization' spelling was a re-spelling rather than a change of shape (the #5030 history it closes out); no-lifecycle ruling; what the object is NOT (§4 posture)", + "packages/platform-objects/src/system/sys-metadata-activation.object.ts#systemFields — systemFields { tenant: false }: the opt-out that REMOVES the tenant column, not one that merely leaves it unwritten (with no field declared the column would still be provisioned by injection), and why it is deliberately not the broader tenancy.enabled posture key", + "packages/core/src/utils/metadata-activation-store.ts#setActive — read-then-write setActive keyed on exactly the index's two columns (so taking the first match is taking the only one); organization_id never in the write payload; delete-less engine slice; 0-reads-as-false", + "packages/core/src/utils/metadata-activation-store.ts#list — list() is the whole read path: scoped by metadata_type and by nothing else, because the ledger is deployment-wide and has no second axis. ⚠️ There is no org-row skip here any more — the module header records that the NULL-organization filter and the org-row skip both went with the column", "packages/runtime/src/api-exposure.ts#checkApiExposure (checkApiExposure — the 405 shape for a whitelisted-methods object)", "packages/services/service-automation/src/flow-activation-store.ts + packages/objectql/src/action-activation.ts (the two metadata_type string literals — the whole live vocabulary)", "packages/objectql/src/engine.ts#listDisabledActions (listDisabledActions — zero route consumers; the data-API read is the only operability surface)", - "docs/adr/0126-packaged-metadata-customization-model.md §3 (scope wall + amendment ruling 1), §4 (row contract), §5 (org column reserved), §6 wall 3 (record the choice, never erase it)", - "#12438 (the scoped sweep), #12419 (registration home — the sibling item), #12159 (the permission-rides-its-own-field ruling, 2026-08-26)" + "docs/adr/0126-packaged-metadata-customization-model.md §3 (scope wall + amendment ruling 1), §4 (row contract), §6 wall 3 (record the choice, never erase it) — ⚠️ §5 D3's 'the org column is reserved' bullet is WITHDRAWN, not merely unimplemented; §5's operator-gate half stands and is owned by access-security.activation-write-operator-gate", + "docs/adr/0131-total-organization-ownership-no-null-organization-id.md D7 (deployment-level state has no organization column — sys_metadata_activation is named there as reverted before 17.3 and not returning). This is what makes 'the ledger has no tenant column' a load-bearing property worth probing, rather than an obsolete assertion to retire alongside the behaviour it tested", + "#12438 (the scoped sweep), #12419 (registration home — the sibling item), #12159 (the permission-rides-its-own-field ruling, 2026-08-26), #15024 (the column drop this item is re-grounded against), #15154 (the re-grounding)" ], "history": [ { @@ -1566,6 +1569,12 @@ "date": "2026-08-26", "change": "new — authored in the #12438 scoped sweep. Grounding decided two load-bearing shapes: (1) metadata_type has NO pinnable enum — an untyped Field.text with two string-literal writers — so the variants are hand-enumerated and explicitly un-pinned, with the re-derivation duty written into the scope-wall clause instead of a false enumSource; (2) the org-row-skip and duplicate-row probes cannot be staged over REST at all (apiMethods get/list), so they ride direct sqlite access to the run's own file DB, the same fixture class the migration-journal item proved", "ref": "#12438" + }, + { + "revision": 2, + "date": "2026-09-04", + "change": "re-grounded against the tenantless table (#15024): sys_metadata_activation dropped its reserved organization_id column before it ever shipped, so three legs were describing a column that is not there. Step 2 now asserts organization_id is ABSENT from the row key set rather than present-and-NULL — the value spelling passes VACUOUSLY once the column is gone (`row.organization_id ?? null` is `null` either way), which is why the dogfood pin was inverted into a key-set assertion rather than carried forward. Step 6 keeps the uniqueness probe and corrects only its stated mechanism: a plain unique:'global' over the two required key parts, with ADR-0120 D3's COALESCE collapse recorded as history that is no longer reachable. Step 7 could not be corrected — it INSERTed a row into a column that no longer exists, so the leg was unrunnable rather than merely mis-described — and is REPLACED, not retired: the org-skip clause becomes a no-tenant-column probe (PRAGMA table_info + an INSERT naming the column, refused, bracketed by a control INSERT that lands without it). Replaced rather than retired because ADR-0131 D7 makes 'this ledger has no organization column' a load-bearing platform property, so dropping the leg would lose coverage instead of retiring an obsolete assertion. The negative entries follow the same correction: a resurrected column is now WORSE than the shape it replaced, because the store no longer filters or skips on read at all", + "ref": "#15154" } ] },