Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 38 additions & 17 deletions src/genie-commands/codex-delivery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,21 @@ describe('buildDeliveryPublication — publish facts only for pending', () => {
}),
).toEqual({ targetVersion: N, canonicalPayloadSha256: DIGEST, channel: 'stable', downgradeFrom: T });
});
test('absent and current publish nothing', () => {
test('absent-N publishes the delivery facts (fresh host: setup gate needs the record); no downgrade binding', () => {
// Group E live-QA regression: without this, a fresh codex host required a
// SECOND `genie update` (already-current repair) before setup could pass
// the Decision-9 record gate.
expect(
buildDeliveryPublication({
installedVersion: null,
targetVersion: T,
canonicalPayloadSha256: DIGEST,
channel: 'dev',
}),
).toBeNull();
).toEqual({ targetVersion: T, canonicalPayloadSha256: DIGEST, channel: 'dev' });
});

test('current publishes nothing', () => {
expect(
buildDeliveryPublication({
installedVersion: T,
Expand Down Expand Up @@ -159,21 +165,36 @@ describe('publishCodexDelivery — parent publishes facts, nothing else', () =>
expect(forbidden).toEqual([]);
});

test('absent and current publish nothing and never touch activation state', () => {
for (const installed of [null, T]) {
const { store, publishCalls, forbidden } = spyStore();
const result = publishCodexDelivery({
lease: spyLease(),
store,
installedVersion: installed,
targetVersion: T,
canonicalPayloadSha256: DIGEST,
channel: 'dev',
});
expect(result.published).toBe(false);
expect(publishCalls).toHaveLength(0);
expect(forbidden).toEqual([]);
}
test('current publishes nothing and never touches activation state', () => {
const { store, publishCalls, forbidden } = spyStore();
const result = publishCodexDelivery({
lease: spyLease(),
store,
installedVersion: T,
targetVersion: T,
canonicalPayloadSha256: DIGEST,
channel: 'dev',
});
expect(result.published).toBe(false);
expect(publishCalls).toHaveLength(0);
expect(forbidden).toEqual([]);
});

test('absent-N publishes the facts once, with no receipt and no activation-state touch', () => {
const { store, publishCalls, forbidden } = spyStore();
const result = publishCodexDelivery({
lease: spyLease(),
store,
installedVersion: null,
targetVersion: T,
canonicalPayloadSha256: DIGEST,
channel: 'dev',
});
expect(result.published).toBe(true);
expect(result.wroteDowngradeReceipt).toBe(false);
expect(publishCalls).toHaveLength(1);
expect(publishCalls[0]).toMatchObject({ targetVersion: T, canonicalPayloadSha256: DIGEST, channel: 'dev' });
expect(forbidden).toEqual([]);
});
});

Expand Down
18 changes: 13 additions & 5 deletions src/genie-commands/codex-delivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,27 @@ export interface CodexDeliveryFacts {
}

/**
* Build the `publishDelivery` input for a pending delivery, or null when there
* is nothing to publish (absent/current/indeterminate). A downgrade binds
* Build the `publishDelivery` input for a pending OR absent-N delivery, or null
* when there is nothing to publish (current/indeterminate). A downgrade binds
* `downgradeFrom = N` so A writes the one-time downgrade receipt.
*
* Group E: `absent` (no installed plugin generation) publishes too. The record
* binds the DELIVERY facts, not an activation — and setup's Decision-9 gate
* refuses to activate a fresh host without a matching record, so a delivery
* that skipped publication forced a second `genie update` (already-current
* repair) before the first `genie setup --codex` could ever succeed
* (2026-07-23 live-QA finding). Publication remains a pure fact write: no
* journal, activation, or cache mutation.
*/
export function buildDeliveryPublication(facts: CodexDeliveryFacts): PublishDeliveryInput | null {
const state = classifyCodexDelivery(facts.installedVersion, facts.targetVersion);
if (state.kind !== 'pending') return null;
if (state.kind !== 'pending' && state.kind !== 'absent') return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not publish absent records when the installed version is unknown

When publishCodexDeliveryFacts cannot read codex plugin list, it currently passes installedVersion: null via the fallback in src/genie-commands/update.ts; with this new absent branch, that unknown state now publishes a delivery record as if the plugin were truly absent and therefore never sets downgradeFrom. In an explicit downgrade where the list query transiently fails after the binary swap, publishDeliveryImpl writes a matching record and removes/omits the downgrade receipt; later already-current repair sees the record as matching, while setup classifies the still-newer installed plugin as installed-newer, so rerunning update/setup cannot activate the selected downgrade. Please only publish this path for an actually observed absent registration, not for an unclassified query failure.

Useful? React with 👍 / 👎.

const input: PublishDeliveryInput = {
targetVersion: facts.targetVersion,
canonicalPayloadSha256: facts.canonicalPayloadSha256,
channel: facts.channel,
};
if (state.direction === 'downgrade' && facts.installedVersion !== null) {
if (state.kind === 'pending' && state.direction === 'downgrade' && facts.installedVersion !== null) {
input.downgradeFrom = facts.installedVersion;
}
return input;
Expand All @@ -151,7 +159,7 @@ export interface PublishCodexDeliveryInput extends CodexDeliveryFacts {

export interface PublishedCodexDelivery {
state: CodexDeliveryState;
/** True when this call wrote a delivery record (pending only). */
/** True when this call wrote a delivery record (pending or absent-N delivery). */
published: boolean;
/** True when this call wrote a downgrade receipt (explicit-channel downgrade). */
wroteDowngradeReceipt: boolean;
Expand Down
Loading