Files
gstack/test/ship-apple-gate.test.ts
T
garrytan 8b1e3423a7 test(W7c): ship-apple-gate matches its gates on meaning, not capitals
W7c (t06 #19). One test file.

Removed assertion groups:
- "A genuine blocker may require a safety or named documentation-risk
  decision" and "routine interactions and blocking decisions above" sentence
  pins. Invariant: routine release authorization never waives a blocking
  safety/docs decision. Remaining protection: kept free meaning check
  `/stop for that decision rather than treating release authorization as a waiver/i`
  plus `/named documentation-risk exception before distribution/i`.
- Full-sentence "in `read-only` mode against the selected release source".
  Invariant: the docs preflight is read-only. Remaining protection: kept free
  check on "`read-only` mode".

Loosened (case-insensitive, kept): apply-only-to-repository-landing scope
line, CLASSIFY-before-credentials (credential boundary), never abort a store
release over branch topology (demonstrated failure), two routine interactions
(numeric limit), stop-for-decision (consent/stop line).

Kept unchanged: manifest trigger routing, Apple read before the branch gate,
byte-exact branch gate appearing once, docs preflight before the
authorization moment, fastlane/iris/appPriceSchedules command anchors,
stale-wording bans. No gate-tier E2E covers the Apple adapter, so every
invariant keeps a free check here.

Cosmetic pins dropped: capitals on ONLY / CLASSIFY / STOP.

Mutation log: .context/mutation-ship-apple-gate.md (14 mutations, 0
unexpected: 2 reorders, 1 marker and 6 safety mutations red; 5 rewordings green).
2026-10-03 01:03:14 +00:00

73 lines
3.7 KiB
TypeScript

/**
* R2 pin (fork port wave 2): the Apple release adapter loads BEFORE ship's
* branch gate, and the non-Apple gate is byte-unchanged.
*
* Two failure modes this prevents: a future ship-template refactor that
* re-blocks store releases behind "ship from a feature branch" (the exact
* live failure the fork hit — a solo dev with a clean tree on main shipping
* to TestFlight got aborted over branch topology), and the reverse — the
* Apple path accidentally weakening the branch gate for normal
* repository-landing ships.
*/
import { describe, test, expect } from "bun:test";
import { readFileSync } from "fs";
import { join } from "path";
const ROOT = join(import.meta.dir, "..");
const SKELETON = readFileSync(join(ROOT, "ship", "SKILL.md"), "utf-8");
const GATE_TEXT =
'If on the base branch or the repo\'s default branch, **abort**: "You\'re on the base branch. Ship from a feature branch."';
describe("ship Apple gate ordering (R2)", () => {
test("the section index requires a store-distribution request, not merely an Apple repository", () => {
const manifest = JSON.parse(readFileSync(join(ROOT, "ship", "sections", "manifest.json"), "utf-8"));
const apple = manifest.sections.find((section: { id: string }) => section.id === "apple-release");
expect(apple.trigger).toContain("App Store/TestFlight distribution is requested for an Apple app");
expect(apple.trigger).toContain("an Apple repository-landing request follows the normal pipeline");
expect(SKELETON).toContain("is App Store/TestFlight distribution");
});
test("the Apple adapter read directive precedes the branch gate", () => {
const appleRead = SKELETON.indexOf("sections/apple-release.md");
const gate = SKELETON.indexOf(GATE_TEXT);
expect(appleRead).toBeGreaterThan(-1);
expect(gate).toBeGreaterThan(-1);
expect(appleRead).toBeLessThan(gate);
});
test("store distribution explicitly bypasses the branch/PR ceremony", () => {
expect(SKELETON).toContain("Store distribution proceeds");
expect(SKELETON).toMatch(/branch gate and repository-landing pipeline below apply only to\s+repository-landing asks/i);
});
test("the non-Apple branch gate is byte-unchanged and appears exactly once", () => {
const first = SKELETON.indexOf(GATE_TEXT);
expect(first).toBeGreaterThan(-1);
expect(SKELETON.indexOf(GATE_TEXT, first + 1)).toBe(-1);
});
test("the adapter section exists in the union with its battle-tested spine", () => {
const section = readFileSync(join(ROOT, "ship", "sections", "apple-release.md"), "utf-8");
for (const anchor of ["one authorization moment", "fastlane spaceauth", "iris/v1/apiKeys", "appPriceSchedules"]) {
expect(section).toContain(anchor);
}
expect(section).toMatch(/classify the error before touching credentials/i);
expect(section).toMatch(/never abort an App Store release over branch topology/i);
});
test("routine interaction limits cannot waive blocking documentation or safety decisions", () => {
for (const name of ["apple-release.md.tmpl", "apple-release.md"]) {
const section = readFileSync(join(ROOT, "ship", "sections", name), "utf-8").replace(/\s+/g, " ");
expect(section).toMatch(/two routine interactions/i);
expect(section).toMatch(/stop for that decision rather than treating release authorization as a waiver/i);
expect(section).not.toContain("exactly two interactions, and no others");
expect(section).not.toContain("two permitted interactions");
expect(section.indexOf("**Documentation preflight:**")).toBeLessThan(section.indexOf("## The one authorization moment"));
expect(section).toContain("`read-only` mode");
expect(section).toMatch(/named documentation-risk exception before distribution/i);
}
});
});