From d6548214af71d0de298f151ba60d177f0afd2308 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 23 Apr 2026 15:07:42 -0400 Subject: [PATCH 1/2] Add test showing that hash does not update after property deletion --- test/integration/api/datasets.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/integration/api/datasets.js b/test/integration/api/datasets.js index 43fb4cdf5..d0d8df110 100644 --- a/test/integration/api/datasets.js +++ b/test/integration/api/datasets.js @@ -7064,6 +7064,23 @@ describe('datasets and entities', () => { (await getHash(asChelsea)).should.not.equal(originalHash); })); + it('changes hash after a dataset property is deleted', testServiceFullTrx(async (service) => { + const [asAlice, asChelsea] = await service.login(['alice', 'chelsea']); + await createData(asAlice); + await assignToProject(asAlice, asChelsea, 'formfill'); + + // Add an entity property. + await asAlice.post('/v1/projects/1/datasets/people/properties') + .send({ name: 'foo' }) + .expect(200); + const originalHash = await getHash(asChelsea); + + // Delete the property. + await asAlice.delete('/v1/projects/1/datasets/people/properties/foo') + .expect(200); + (await getHash(asChelsea)).should.not.equal(originalHash); + })); + it('computes hash based on timestamps and the entity count', testServiceFullTrx(async (service, container) => { const [asAlice, asChelsea] = await service.login(['alice', 'chelsea']); await createData(asAlice); From 60df0cb8149357df73adb34f9eda32b0052a166e Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 30 Apr 2026 15:20:37 -0400 Subject: [PATCH 2/2] Update OpenRosa hash after deletion of property in ownerOnly entity list --- lib/model/query/form-attachments.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/model/query/form-attachments.js b/lib/model/query/form-attachments.js index db8068991..e63d248b2 100644 --- a/lib/model/query/form-attachments.js +++ b/lib/model/query/form-attachments.js @@ -165,11 +165,11 @@ const _get = (exec, options) => { SELECT datasets.id, MAX("loggedAt") AS "lastAudit" FROM audits JOIN datasets ON audits."acteeId" = datasets."acteeId" ${actorId == null ? sql`` : sql` - -- We're always interested in dataset.create and dataset.update - -- actions, even for ownerOnly datasets. + -- We're always interested in dataset.create and in actions that add + -- or delete entity properties -- even for ownerOnly datasets. WHERE (NOT datasets."ownerOnly") OR - audits.action IN ('dataset.create', 'dataset.update') + audits.action IN ('dataset.create', 'dataset.update', 'dataset.update.property.delete') `} GROUP BY datasets.id ) AS audit_metadata ON audit_metadata.id = datasets.id