Skip to content

Commit 13d3124

Browse files
committed
test(metadata-protocol,rest): the draft doors naming no package act on the draft the draft save writes
Pins both card states in both storage orders, the package-unbound draft promoted into the item's package, two packages refused 409, an item never published into a package, the draft-save receipt, and the repository's adoptInto. Two pins of the removed first-row pick are replaced. Claude-Session: https://claude.ai/code/session_01GP3comALHRcRBixS2TEnLF Co-authored-by: Claude <noreply@anthropic.com>
1 parent b411a4c commit 13d3124

5 files changed

Lines changed: 475 additions & 14 deletions

‎packages/metadata-protocol/src/protocol-publish-drafts-package-scope.test.ts‎

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -373,24 +373,49 @@ describe('publishMetaItem — the per-item door names a package too (#10350)', (
373373
expect(labelOf(drafts[0])).toBe('FROM_OTHER');
374374
});
375375

376-
it('keeps the historical match-any resolution when the caller states NO package', async () => {
376+
// [#23495] Replaced, not respelled: this case pinned the absent key's
377+
// "match any package" resolution, which promoted this fixture's
378+
// first-scanned draft (`app.other`) — the storage-order coin toss #8907
379+
// removed from the batch door, on the per-item door. Naming no package,
380+
// the publish now promotes the draft a draft save naming no package
381+
// writes, and this item (no active row, two packages' drafts, no
382+
// package-less draft) has no single one. What the case also guarded stays
383+
// pinned: the absent key is not a present `null`, which would pin the
384+
// lookup to the unbound row and answer `no_draft` (the next case).
385+
it('refuses 409 RESOURCE_CONFLICT when the caller states NO package and two packages hold drafts, promoting nothing', async () => {
377386
const { engine, rows } = makeStubEngine();
378387
const protocol = new ObjectStackProtocolImplementation(engine);
379388

380389
await seedTwoPackageDrafts(protocol);
381390

391+
const refusal = await protocol.publishMetaItem({ type: 'object', name: 'shared_ticket' })
392+
.then(() => undefined, (e: any) => e);
393+
394+
expect(refusal).toMatchObject({ code: 'RESOURCE_CONFLICT', status: 409 });
395+
expect(refusal.message).toContain("'app.demo'");
396+
expect(refusal.message).toContain("'app.other'");
397+
expect(activeRowsOf(rows)).toEqual([]);
398+
expect(draftRowsOf(rows).map((r) => r.package_id)).toEqual(['app.other', 'app.demo']);
399+
});
400+
401+
it('promotes the one package\'s draft when the caller states NO package — the absent key is not a present `null`', async () => {
402+
const { engine, rows } = makeStubEngine();
403+
const protocol = new ObjectStackProtocolImplementation(engine);
404+
405+
await protocol.saveMetaItem({
406+
type: 'object',
407+
name: 'shared_ticket',
408+
item: objectBody('shared_ticket', 'FROM_OTHER'),
409+
packageId: 'app.other',
410+
mode: 'draft',
411+
});
412+
382413
await protocol.publishMetaItem({ type: 'object', name: 'shared_ticket' });
383414

384-
// The contract `promoteDraftForPublish` spells as
385-
// `...('packageId' in request ? ... : {})`: an ABSENT key means "match
386-
// any package" (this fixture's first-scanned row), while a
387-
// present-and-`undefined` key would coerce to `null` downstream and pin
388-
// the lookup to UNBOUND rows — finding neither draft and answering
389-
// `no_draft`. Declaring `packageId` optional must not turn the first
390-
// spelling into the second, so the untouched path is pinned here.
391415
const active = activeRowsOf(rows);
392416
expect(active).toHaveLength(1);
393417
expect(active[0].package_id).toBe('app.other');
394418
expect(labelOf(active[0])).toBe('FROM_OTHER');
419+
expect(draftRowsOf(rows)).toEqual([]);
395420
});
396421
});

‎packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,9 @@ describe('a publish promotes only the draft its gate judged', () => {
523523

524524
// The lock a publish consults is the one at the address it promotes: the
525525
// package key the publish resolved (the caller's stated binding, else the
526-
// draft row's own), not only the package the request stated. With two
527-
// packages' env-wide rows of one view both locked, the refusal carries the
528-
// lock of the package whose draft is being promoted.
526+
// draft address it resolves), not only the package the request stated. With
527+
// two packages' env-wide rows of one view both locked, the refusal of a
528+
// publish naming its package carries that package's lock.
529529
describe('a publish consults the lock of the package key it resolved', () => {
530530
const locked = (pkg: string) => ({ ...VIEW, _lock: 'full', _lockReason: `${pkg} keeps this view as shipped` });
531531

@@ -543,10 +543,20 @@ describe('a publish consults the lock of the package key it resolved', () => {
543543
return { protocol, draftsOf };
544544
}
545545

546-
it('a publish that states no package consults the lock of the draft row\'s own package', async () => {
546+
// [#23495] Replaced, not respelled: this case pinned a publish naming no
547+
// package consulting the lock of the draft row a package-agnostic read met
548+
// (`pkg_b`), over an item whose active rows two packages hold. Naming no
549+
// package, the publish now takes the draft save's address, and an item two
550+
// packages hold has none: the publish is judged by the lock the draft save
551+
// naming no package is judged by, and answers what that save answers.
552+
it('a publish that states no package, of an item two packages hold, is judged by the lock the draft save naming none is', async () => {
547553
const { protocol, draftsOf } = await seedLockedRowsAndDraft('pkg_b');
554+
const saveRefusal = await protocol.saveMetaItem({
555+
type: 'view', name: VIEW.name, item: { ...VIEW, label: 'Org grid (edited)' }, mode: 'draft',
556+
}).then(() => undefined, (e: any) => e);
557+
expect(saveRefusal).toMatchObject({ code: 'ITEM_LOCKED', status: 403, lock: 'full' });
548558
await expect(protocol.publishMetaItem({ type: 'view', name: VIEW.name }))
549-
.rejects.toMatchObject({ code: 'ITEM_LOCKED', status: 403, lock: 'full', lockReason: 'pkg_b keeps this view as shipped' });
559+
.rejects.toMatchObject({ code: 'ITEM_LOCKED', status: 403, lock: 'full', lockReason: saveRefusal.lockReason });
550560
expect(draftsOf()).toEqual(['pkg_b']);
551561
});
552562

0 commit comments

Comments
 (0)