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
5 changes: 5 additions & 0 deletions .changeset/app-only-slot-stamping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"everything-dev": patch
---

Hotfix for the rc.26 slot-stamping incident: the deploy train now stamps only `app.*` slots (the base-runtime slots children never author — auth, host) from the release manifest's slots map. Plugin slots were being stamped into the child's leaf payload, which both expanded a declared-plugin child's composed plugin set to the whole parent set (violating the extends contract — a child's declared `plugins` object owns the entire plugin domain via a wholesale replace) and produced schema-invalid `plugins.<id>.ui` records (stamped ui entries carry no required `name`, and the wholesale replace never fills it) — blocking every child with authored plugins from deploying. Children pinned to rc.26 with authored `plugins` are CI-blocked until they run `bos upgrade`; live apps are unaffected (the boot-check refused the publish before any registry write).
20 changes: 14 additions & 6 deletions packages/everything-dev/src/services/deploy/composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ export interface StampOutcome {
}

/**
* Stamp inherited slots: for every slot in the release manifest's `slots` map
* that the authored config does not declare, write the release's recorded
* production URL + pin into the payload. Child-wins deep merge then shadows
* the parent's live config pointer at every consumer boot, so the child
* composes exactly the bytes its pinned release published. Slots the child
* authors stay untouched, and a manifest without `slots` (pre-amendment
* Stamp inherited slots: for every `app.*` slot in the release manifest's
* `slots` map that the authored config does not declare, write the release's
* recorded production URL + pin into the payload. Child-wins deep merge then
* shadows the parent's live config pointer at every consumer boot, so the
* child composes exactly the bytes its pinned release published. Slots the
* child authors stay untouched, and a manifest without `slots` (pre-amendment
* releases) stamps nothing — the composition gate covers those children.
*/
export function stampInheritedSlotsFromRelease(input: {
Expand All @@ -89,6 +89,14 @@ export function stampInheritedSlotsFromRelease(input: {
const stamped: string[] = [];

for (const [slotPath, record] of Object.entries(slots)) {
// KEEP: only app.* slots are stampable — a child's declared `plugins`
// object owns the whole plugin domain in one wholesale replace
// (mergeBosConfigWithExtends), so stamping parent plugin slots would
// both expand the child's composed plugin set and produce
// schema-invalid entries (the stamped ui record alone carries no
// required `name`; the wholesale replace never fills it — the
// feedback.nearbuilders.org rc.26 incident).
if (!/^app\.[^.]+(\.ui)?$/.test(slotPath)) continue;
if (authored.has(slotPath)) continue;
stampSlotRecord(input.payload, slotPath, record);
stamped.push(slotPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ import {
PlatformReleaseManifestSchema,
resolvePlatformManifest,
} from "../../src/services/platform/manifest";
import { resolveBootShape } from "../../src/services/resolution/boot-check";
import {
resolveBootShape,
verifyResolvedBootShape,
} from "../../src/services/resolution/boot-check";
import { applyPlatformPin } from "../../src/services/scaffold/upgrade-migration";
import {
type ExtendedRootSourceOptions,
Expand Down Expand Up @@ -534,6 +537,7 @@ describe("release-scoped inheritance — a pinned child deploy survives the root
);
const authPin = pinFor("/auth/", `versions/auth-${version}.json`, shared);
const authUiPin = pinFor("/auth-ui/", `versions/auth-ui-${version}.json`, shared);
const parentUi = { production: `${base}/auth-ui/`, pin: authUiPin, name: "auth" };
const entry = composePlatformManifestPublishEntry({
configDir: rootDir,
account: ACCOUNT,
Expand All @@ -544,7 +548,18 @@ describe("release-scoped inheritance — a pinned child deploy survives the root
auth: {
production: `${base}/auth/`,
pin: authPin,
ui: { production: `${base}/auth-ui/`, pin: authUiPin },
ui: parentUi,
},
},
plugins: {
votes: {
production: `${base}/votes/`,
pin: pinFor("/votes/", `versions/votes-${version}.json`, shared),
ui: {
production: `${base}/votes-ui/`,
pin: pinFor("/votes-ui/", `versions/votes-ui-${version}.json`, shared),
name: "votes-ui",
},
},
},
},
Expand All @@ -556,23 +571,33 @@ describe("release-scoped inheritance — a pinned child deploy survives the root
account: ACCOUNT,
domain: GATEWAY,
app: {
host: { production: `${base}/host/` },
ui: { production: `${base}/ui/` },
api: { production: `${base}/api/` },
auth: {
production: `${base}/auth/`,
pin: authPin,
ui: { production: `${base}/auth-ui/`, pin: authUiPin },
ui: parentUi,
},
},
});
return entry!.manifest;
}

/** The deploy train's payload assembly, compressed: authored config, the
* train-applied own-ui pin, optional stamps, one boot walk, then the gate. */
* train-applied own-ui pin, optional stamps, one boot walk, the boot-check
* schema parse of the merged shape, then the gate. */
async function simulateDeploy(
authoredConfig: Record<string, unknown>,
ownUiPin: { manifest: string; integrity: string },
stamps: boolean,
): Promise<{ ok: boolean; violations: string[]; merged: Record<string, unknown> }> {
): Promise<{
ok: boolean;
violations: string[];
merged: Record<string, unknown>;
stamped: string[];
bootOk: boolean;
}> {
const resolvedRelease = await resolveReleaseManifestForDeploy({
configDir: childDir,
authoredConfig,
Expand All @@ -582,14 +607,16 @@ describe("release-scoped inheritance — a pinned child deploy survives the root
const app = payload.app as Record<string, Record<string, unknown>>;
app.ui.production = `${base}/child-ui/`;
app.ui.pin = ownUiPin;
let stamped: string[] = [];
if (stamps && resolvedRelease?.slots) {
stampInheritedSlotsFromRelease({
stamped = stampInheritedSlotsFromRelease({
payload,
authoredConfig,
manifest: resolvedRelease,
});
}).stamped;
}
const bootShape = await resolveBootShape(payload, CHILD_SLOT, { io: registryIo });
const bootCheck = verifyResolvedBootShape(payload, bootShape);
const gate = await runCompositionGate({
configDir: childDir,
authoredConfig,
Expand All @@ -601,6 +628,8 @@ describe("release-scoped inheritance — a pinned child deploy survives the root
ok: gate.ok,
violations: gate.violations,
merged: bootShape.config as Record<string, unknown>,
stamped,
bootOk: bootCheck.ok,
};
}

Expand Down Expand Up @@ -655,20 +684,28 @@ describe("release-scoped inheritance — a pinned child deploy survives the root
domain: "child.example",
platform: "2.0.0-rc.1",
app: { ui: { development: "local:ui" } },
plugins: { feedback: { extends: `bos://${ACCOUNT}/${GATEWAY}/votes` } },
};
const { ok, violations, merged } = await simulateDeploy(
const { ok, violations, merged, stamped, bootOk } = await simulateDeploy(
authoredConfig,
{
manifest: "versions/fedcba0987654321.json",
integrity: computeManifestSri(versionManifestText(childUiShared)),
},
true,
);
expect(bootOk).toBe(true);
expect(violations).toEqual([]);

// the release records the parent's plugin slots too — the child's own
// declared plugin set owns that domain, so none of them stamp
expect(stamped).toEqual(["app.auth", "app.auth.ui"]);
const app = merged.app as Record<string, Record<string, Record<string, string>>>;
expect(app.auth.pin.manifest).toBe("versions/auth-2.0.0-rc.1.json");
expect(app.auth.ui.pin.manifest).toBe("versions/auth-ui-2.0.0-rc.1.json");
expect(merged.plugins).toEqual({
feedback: { extends: `bos://${ACCOUNT}/${GATEWAY}/votes` },
});
expect(ok).toBe(true);
});

Expand All @@ -679,15 +716,17 @@ describe("release-scoped inheritance — a pinned child deploy survives the root
domain: "child.example",
platform: "2.0.0-rc.1",
app: { ui: { development: "local:ui" } },
plugins: { feedback: { extends: `bos://${ACCOUNT}/${GATEWAY}/votes` } },
};
const { ok, violations } = await simulateDeploy(
const { ok, violations, bootOk } = await simulateDeploy(
authoredConfig,
{
manifest: "versions/fedcba0987654321.json",
integrity: computeManifestSri(versionManifestText(childUiShared)),
},
false,
);
expect(bootOk).toBe(true);
expect(ok).toBe(false);
const combined = violations.join("\n");
expect(combined).toContain("Shared singleton");
Expand Down
46 changes: 44 additions & 2 deletions packages/everything-dev/tests/unit/composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ describe("stampInheritedSlotsFromRelease", () => {
expect(outcome.stamped).toEqual([]);
});

it("a plugin authored without a ui key stamps its computed ui slot", () => {
it("a plugin authored without a ui key still owns the plugin domain — no stamps", () => {
const authored = {
plugins: { votes: { extends: "bos://root.near/root" } },
};
Expand All @@ -194,7 +194,49 @@ describe("stampInheritedSlotsFromRelease", () => {
},
}),
});
expect(outcome.stamped).toEqual(["plugins.votes.ui"]);
expect(outcome.stamped).toEqual([]);
});

it("never stamps plugin slots — a child's declared plugin set owns the whole domain (the rc.26 incident)", () => {
const authored = {
account: "feedback.near",
app: { ui: { production: "https://feedback.test/ui/" } },
plugins: {
legion: { extends: "bos://root.near/root" },
storage: { extends: "bos://root.near/root" },
},
};
const payload = structuredClone(authored);
const outcome = stampInheritedSlotsFromRelease({
payload,
authoredConfig: authored,
manifest: manifestWithSlots({
"app.auth": {
base: authBase,
pin: { manifest: "versions/0123456789abcdef.json", integrity: "sha384-auth" },
},
"app.auth.ui": {
base: authUiBase,
pin: { manifest: "versions/0123456789abcdef.json", integrity: "sha384-auth-ui" },
},
"plugins.ai": {
base: "https://cdn.test/ai/",
pin: { manifest: "ai.json", integrity: "sha384-ai" },
},
"plugins.ai.ui": {
base: "https://cdn.test/ai-ui/",
pin: { manifest: "ai-ui.json", integrity: "sha384-ai-ui" },
},
"plugins.votes": {
base: "https://cdn.test/votes/",
pin: { manifest: "votes.json", integrity: "sha384-votes" },
},
}),
});
expect(outcome.stamped).toEqual(["app.auth", "app.auth.ui"]);
// the child's plugin set is exactly what it declared — the parent's
// plugins must not appear (and never schema-invalidated ui stamps)
expect(payload.plugins).toEqual(authored.plugins);
});
});

Expand Down
Loading