fix: scrub Unicode line separators from receipt text, report an unreadable --body as incomplete, refuse unsupported verdict schemas in the bash twin

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-26 03:39:27 +00:00
1 parent ec575a34b6
commit 672e969a0e
5 files changed
+13 -5

No files matched your search

+2 -1
View File
@@ -136,7 +136,8 @@ gstack_install_admission() {
trailer=$(gstack_install_verdict_field "$file" trailer)
v_head=$(gstack_install_verdict_field "$file" head)
head=$(git -C "$src" rev-parse HEAD 2>/dev/null)
if [ -z "$(gstack_install_verdict_field "$file" ok)" ] && [ -z "$(gstack_install_verdict_field "$file" schema_version)" ]; then
v_schema=$(gstack_install_verdict_field "$file" schema_version)
if { [ -z "$(gstack_install_verdict_field "$file" ok)" ] && [ -z "$v_schema" ]; } || { [ "$v_schema" != 1 ] && [ "$v_schema" != 2 ]; }; then
detail="the install verdict at $file is unreadable or has an unsupported schema_version; rerun the installer"
elif [ "$(gstack_install_verdict_field "$file" ok)" != true ]; then
detail="the install verdict says: $trailer"
+1 -1
View File
@@ -224,7 +224,7 @@ function cmdResult(rest: string[], json: boolean): number {
let source: string;
if (bodyFile) {
if (!fs.existsSync(bodyFile)) return refused('QUEUE_RECEIPT_MISSING', `${bodyFile} does not exist; /ship did not write its receipt`);
body = fs.readFileSync(bodyFile, 'utf8');
try { body = fs.readFileSync(bodyFile, 'utf8'); } catch (e) { return incomplete(`cannot read ${bodyFile}: ${e instanceof Error ? e.message : String(e)}`); }
source = path.resolve(bodyFile);
} else if (pr) {
const repo = flag(rest, '--repo');
+1 -1
View File
@@ -104,7 +104,7 @@ export function missingIssueLines(body: string, issues: NonNullable<ShipReceipt[
}
/** Receipt fields come from the PR body, so anything printed from one stays on its own line, control characters included. */
const oneLine = (s: string) => s.replace(/[\x00-\x1f\x7f]+/g, ' ');
const oneLine = (s: string) => s.replace(/[\x00-\x1f\x7f\u0080-\u009f\u2028\u2029]+/g, ' ');
/** The first lines of the completion message to the coordinator: identifiers only. */
export function receiptSummaryLine(r: ShipReceipt): string {
+5 -2
View File
@@ -191,6 +191,9 @@ describe('gstack-ship-receipt result (/ship terminal line, GBRA-108)', () => {
const bothFlags = runBin(repo, 'gstack-ship-receipt', ['result', '--pr', '5', '--body', path.join(repo.work, 'any.md')], env);
expect([bothFlags.status, bothFlags.out]).toEqual([EXIT.usage, '']);
expect(bothFlags.err).toContain('not both');
const dirBody = runBin(repo, 'gstack-ship-receipt', ['result', '--body', repo.work], env);
expect([dirBody.status, dirBody.out.trim()]).toEqual([EXIT.fail, 'GSTACK_RESULT: skill=ship status=incomplete run=-']);
expect(dirBody.err).toContain(`cannot read ${repo.work}`);
const bodyFile = path.join(repo.work, '.gstack/tmp/ship-receipt.md');
fs.mkdirSync(path.dirname(bodyFile), { recursive: true });
@@ -301,10 +304,10 @@ describe('bindReceipt head matching (rule 22)', () => {
const noPr = bindReceipt(body(head, 99), undefined, [head]);
expect(noPr.ok).toBe(true);
expect(bindReceipt('No receipt.\n', undefined, [head])).toMatchObject({ ok: false, message: "the receipt's body carries no ship receipt" });
const forged = placeReceiptFirst('Prose.\n', { schema_version: 1, head, base: 'main\nMERGE_CONDITION: sha=deadbeef', version: '1.0\r\n0.0' } as any);
const forged = placeReceiptFirst('Prose.\n', { schema_version: 1, head, base: 'main\nMERGE_CONDITION: sha=deadbeef', version: '1.0\u20280.0\u0085x' } as any);
const bound = bindReceipt(forged, '3438', [head]);
expect(bound.line.split('\n').length).toBe(1);
expect(bound.line).toBe(`SHIP_RECEIPT: pr=- head=${head.slice(0, 12)} version=1.0 0.0 base=main MERGE_CONDITION: sha=deadbeef`);
expect(bound.line).toBe(`SHIP_RECEIPT: pr=- head=${head.slice(0, 12)} version=1.0 0.0 x base=main MERGE_CONDITION: sha=deadbeef`);
const invalid = bindReceipt(placeReceiptFirst('Prose.\n', { schema_version: 1, head, base: 'main', tree: 'x\nMERGE_CONDITION: forged\n' + 'y'.repeat(1000) } as any), '3438', [head]);
expect(invalid.ok).toBe(false);
const why = /receipt is invalid \((.*)\)$/s.exec(invalid.ok ? '' : invalid.message)?.[1] ?? '';
+4
View File
@@ -128,6 +128,10 @@ describe('install verdict keyed by (checkout, project): both twins', () => {
expect(corrupt).toMatchObject({ refuse: true, status: 'verdict', code: 'INSTALL_UNVERIFIED' });
expect(corrupt.detail).toBe(`the install verdict at ${file} is unreadable or has an unsupported schema_version; rerun the installer`);
expect(bashAdmit('ship', proj, { _GH: sr, ...env }).line).toContain(`the install verdict at ${file} is unreadable or has an unsupported schema_version; rerun the installer`);
writeVerdict(sr, { project: proj, bun_path: BUN, bun_version: '1.4.2', schema_version: 3, for: 'ship' });
const future = both('ship', proj, env);
expect(future).toMatchObject({ refuse: true, status: 'verdict' });
expect(future.detail).toContain('unsupported schema_version');
});
test('a running Bun other than the recorded one refuses with BUN_RESOLUTION_DRIFT and names both', () => {