Skip to content

Commit e50b911

Browse files
committed
test(objectql): pin door 1's type-agnostic scope on the stored object row
F5 of the contract review on PR #16796. `stripDerivedProvenance` runs before `saveMetaItem`'s type branch, so an `object` body loses the same three keys at rest — the criterion-4 case wrote `_packageId` / `_provenance` into the request but read back only the hydrated entry, which door 2's restatement would answer `org` on its own even if the strip had skipped `object`. `_packageVersion` is now sent too, so all three keys the strip names are exercised rather than two. F2 of the same review, as a disclosure in the changeset: door 2 corrects the authorization verdict, not the served document, and the cloud#2069 backfill stays load-bearing for rows already poisoned at rest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
1 parent a35c00f commit e50b911

2 files changed

Lines changed: 19 additions & 1 deletion

File tree

.changeset/derived-provenance-write-door-and-hydration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ Both halves are closed, because they cover different populations:
1212
- `hydrateOverlayIntoRegistry` — the one choke point boot, read-side and write-through hydration already share — now states `_provenance: 'org'` on a copy before merging the artifact envelope, so rows already written become harmless without being rewritten. That also covers the column path: `getMetaItems` re-stamps `_packageId` onto the body from the row's `package_id` column, which the write-door strip cannot reach.
1313

1414
The three keys are read-side derived — `mergeArtifactProtection` recomputes them from the artifact on every read — so nothing an author wrote is lost and no accepted key or value changes. Where a real artifact exists its envelope still wins over both the stored copy and the restatement: ADR-0010 §3.3 precedence is unchanged, and an item genuinely shipped by a code package is still refused `NOT_OVERRIDABLE`.
15+
16+
One residual is deliberately left open: hydration corrects the AUTHORIZATION verdict, not the SERVED document. `getMetaItem` / `getMetaItems` return the overlay row's own body, and `mergeArtifactProtection` only fires where an artifact exists — so a row already poisoned at rest becomes editable again while `GET /meta/app/x` keeps serving `_provenance: 'package'` (and the UI keeps badging it as package-provided) until that row is re-saved through the write door or backfilled. The `objectstack-ai/cloud#2069` backfill stays load-bearing for that population; this change does not retire it.

packages/objectql/src/protocol-derived-provenance-doors.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,25 @@ describe('#16702 criterion 4 — the `object` branch is correct today and STAYS
406406
const s1 = await boot(driver);
407407
await s1.protocol.saveMetaItem({
408408
type: 'object', name: 'pet_visit', packageId: PKG,
409-
item: { ...objectBody('Pet Visit'), _packageId: PKG, _provenance: 'package' },
409+
item: {
410+
...objectBody('Pet Visit'),
411+
_packageId: PKG, _packageVersion: '1.0.0', _provenance: 'package',
412+
},
410413
});
411414

415+
// Door 1's strip runs BEFORE `saveMetaItem`'s type branch, so its scope
416+
// is type-AGNOSTIC: an `object` body loses the same three keys at rest.
417+
// Pinned here rather than implied — the `_provenance: 'org'` read below
418+
// comes from door 2's restatement and would stay green on its own even
419+
// if the strip had skipped `object`.
420+
const seededRows = await s1.engine.find('sys_metadata', { where: { type: 'object', name: 'pet_visit' } });
421+
const seededObject = JSON.parse(String((seededRows[0] as any).metadata));
422+
expect(seededObject).not.toHaveProperty('_packageId');
423+
expect(seededObject).not.toHaveProperty('_packageVersion');
424+
expect(seededObject).not.toHaveProperty('_provenance');
425+
// …and nothing else was taken with them.
426+
expect(seededObject).toMatchObject({ name: 'pet_visit', label: 'Pet Visit' });
427+
412428
const s2 = await boot(driver);
413429
expect(await s2.protocol.loadMetaFromDb()).toMatchObject({ loaded: 1, errors: 0 });
414430
const obj = s2.engine.registry.getObject('pet_visit') as Record<string, unknown> | undefined;

0 commit comments

Comments
 (0)