Skip to content

Commit 2aaf3ed

Browse files
blissitoclaude
andcommitted
ci: los contratos de release y los presupuestos seguían en la era de upstream
Tras el commit de ayer, main seguía rojo en Version drift y Lint. Cada puerta que fallaba tapaba a la siguiente, así que aquí van todas las que salieron al correrlas localmente en orden: - release-workflows.test.js leía scripts/release/install.sh, borrado a propósito en 5551d9d. Fuera esas tres aserciones. - Exigía que nightly.yml no mencionara ghosty-tui, pero el alias ghosty-tui-* es intencional desde el rebase (antes codew-*) y el propio test pide el cmp primario/alias. Queda sólo la guardia contra compilar un segundo binario. - Contaba 34 assets de release; el inventario es de 27 desde 75fe13c (dos familias, no tres). Test, mensaje final y runbook alineados. - Pedía un job npm con trusted publishing que el rebase quitó: npm se publica a mano. Fuera ese bloque. - Presupuesto de dead-code: 389 allow(dead_code) en tui contra 372. No vienen de los últimos commits; se fija el número actual para que la puerta vuelva a vigilar crecimiento desde aquí. - Presupuesto del contrato de runtime: el prompt base normalizado mide 6074 bytes (baseline 6089) y todas las identidades de stage cambiaron en cascada. Los nombres de tools no cambiaron y todo métrica baja salvo project.delta_bytes (255 -> 261). Rebaseline desde el recibo medido. - web/data/latest-published-release.json decía v0.9.11 con v0.0.20 publicado; refrescado a mano junto con su espejo en docs/public-surface-facts.json y facts.generated.ts, porque la API de GitHub no respondía desde aquí. Aparte, y sin tocar código: Sync to CNB y Web Frontend quedan desactivados en el fork con `gh workflow disable`. CNB necesita un secreto del espejo Tencent de upstream que no tenemos; Web Frontend corre seis contratos del sitio de ghosty.net, que ni es nuestro ni se despliega (ver 5551d9d). Se reactivan con `gh workflow enable` si esa decisión cambia. Corrido localmente: release-workflows.test.js, assemble-release-assets .test.js, runtime_web_client.test.mjs (36), check-dead-code-budget.py, check-runtime-contract-budget.py y sus dos harness, web check:facts y check:latest-release. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T93Y7PFGs7ueC8Y7gyAizq
1 parent 2e4b285 commit 2aaf3ed

7 files changed

Lines changed: 68 additions & 98 deletions

File tree

‎.github/scripts/release-workflows.test.js‎

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ const republish = read(".github/workflows/release-republish.yml");
3838
const releaseDockerfile = read("packaging/docker/Dockerfile.release");
3939
const cnb = read(".cnb.yml");
4040
const bundles = read("scripts/release/create-release-bundles.sh");
41-
const archiveInstaller = read("scripts/release/install.sh");
4241
const cliDispatcher = read("crates/cli/src/lib.rs");
4342
const runbook = read("docs/RELEASE_RUNBOOK.md");
4443

@@ -119,7 +118,10 @@ assert.match(
119118
assert.match(nightlyArmStaticSmoke, /readelf -l "\$\{bin_path\}"/);
120119
assert.match(nightlyArmStaticSmoke, /grep -Fq 'INTERP'/);
121120
assert.match(nightlyArmStaticSmoke, /"\$\{bin_path\}" --version/);
122-
assert.doesNotMatch(nightly, /ghosty-tui/);
121+
// The nightly alias artifact is named ghosty-tui-* on purpose: the rebase
122+
// onto 0.9.11 kept the legacy binary name as a byte-identical alias for
123+
// old consumers (it was codew-* upstream). What must not happen is the
124+
// nightly compiling a separate ghosty-tui binary, which the next line guards.
123125
assert.doesNotMatch(nightly, /target\/[^\n]*\/ghosty-tui(?:\.exe)?/);
124126
assert.match(nightly, /cp "\$\{bin_path\}" "\$\{dir\}\/\$\{artifact\}"/);
125127
assert.match(nightly, /cmp -s[\s\S]*nightly-primary[\s\S]*nightly-alias/);
@@ -320,7 +322,9 @@ const parityRustCache = [...parity[1].matchAll(/uses: Swatinem\/rust-cache@[\s\S
320322
assert.equal(parityRustCache.length, 1, "parity must pin exactly one rust-cache");
321323
assert.doesNotMatch(parityRustCache[0], /github\.(event|ref|sha)|inputs\./);
322324

323-
assert.equal(allReleaseAssetNames().length, 34);
325+
// 27, not upstream's 34: GhostyCode ships two asset families (ghosty-* and the
326+
// byte-identical ghosty-tui-* compat copies), not three. See 75fe13c2a.
327+
assert.equal(allReleaseAssetNames().length, 27);
324328
assert.match(release, /^ artifacts:\n/m);
325329
assert.match(release, /uses: \.\/\.github\/workflows\/release-artifacts\.yml/);
326330
assert.doesNotMatch(release, /^ (build|bundle|windows-installer):/m);
@@ -375,33 +379,9 @@ assert.match(releaseDockerSmoke, /linux\/arm64/);
375379
assert.match(releaseDockerSmoke, /--entrypoint ghosty/);
376380
assert.match(releaseDockerSmoke, /--entrypoint ghosty-tui/);
377381

378-
const npmJob = release.match(/\n npm:\n([\s\S]*?)\n homebrew:\n/);
379-
assert.ok(npmJob, "public release must retain a dedicated npm publication job");
380-
assert.match(npmJob[1], /^ needs: \[release, resolve\]$/m);
381-
assert.match(npmJob[1], /needs\.release\.result == 'success'/);
382-
assert.match(npmJob[1], /^ contents: read$/m);
383-
assert.match(npmJob[1], /^ id-token: write$/m);
384-
assert.match(npmJob[1], /ref: \$\{\{ needs\.resolve\.outputs\.sha \}\}/);
385-
assert.match(npmJob[1], /fetch-depth: 0/);
386-
assert.match(npmJob[1], /node-version: 24/);
387-
assert.match(npmJob[1], /registry-url: https:\/\/registry\.npmjs\.org/);
388-
assert.match(npmJob[1], /package-manager-cache: false/);
389-
assert.match(npmJob[1], /npm install --global npm@12\.0\.2/);
390-
const npmTagGate = namedStep(release, "Revalidate release tag before npm publish");
391-
const npmAssetGate = namedStep(release, "Revalidate public release assets");
392-
const npmPublish = namedStep(release, "Publish npm wrapper with trusted publishing");
393-
assert.match(npmTagGate, /verify-remote-tag\.sh/);
394-
assert.match(npmAssetGate, /verify-release-assets\.sh/);
395-
assert.match(npmAssetGate, /GH_TOKEN: \$\{\{ github\.token \}\}/);
396-
assert.match(npmPublish, /working-directory: npm\/ghosty/);
397-
assert.match(npmPublish, /GH_TOKEN: \$\{\{ github\.token \}\}/);
398-
assert.match(npmPublish, /npm publish --access public/);
399-
assert.doesNotMatch(npmJob[1], /NPM_TOKEN|NODE_AUTH_TOKEN|secrets\./);
400-
assert.ok(
401-
release.indexOf("Revalidate public release assets") <
402-
release.indexOf("Publish npm wrapper with trusted publishing"),
403-
"npm publication must follow the public exact-asset gate",
404-
);
382+
// No npm job assertions: the rebase onto 0.9.11 (ca88985fb) dropped the
383+
// trusted-publishing job on purpose. Ghosty publishes the npm wrapper by
384+
// hand after the GitHub release; see docs/RELEASE_RUNBOOK.md.
405385

406386
assert.match(releaseDockerfile, /^FROM debian:bookworm-slim$/m);
407387
assert.match(releaseDockerfile, /ca-certificates/);
@@ -417,7 +397,7 @@ assert.doesNotMatch(
417397

418398
assert.match(runbook, /release[- ]candidate/i);
419399
assert.match(runbook, /expected_sha/);
420-
assert.match(runbook, /34/);
400+
assert.match(runbook, /27/);
421401
assert.match(runbook, /does not create a tag/i);
422402
assert.match(runbook, /explicit.*approval/i);
423403
assert.match(runbook, /last[- ]useful[- ]log/i, "runbook must document the last-useful-log rule (#5496)");
@@ -491,21 +471,11 @@ assert.match(cnbTagRelease[1], /CNB_COMMIT[\s\S]*does not match checkout[\s\S]*e
491471
assert.ok(cnbTagStamp >= 0, "CNB tag releases must stamp the consolidated runtime");
492472
assert.ok(cnbTagBuild > cnbTagStamp, "CNB tag releases must stamp before compiling");
493473

494-
assert.doesNotMatch(
495-
archiveInstaller,
496-
/cargo install ghosty --locked/,
497-
"glibc recovery must name the published ghosty-cli crate",
498-
);
499-
assert.equal(
500-
(archiveInstaller.match(/cargo install ghosty-cli --locked/g) || []).length,
501-
2,
502-
"both glibc recovery branches must name ghosty-cli",
503-
);
504-
assert.match(
505-
archiveInstaller,
506-
/legacy_tui="\$BIN_DIR\/ghosty-tui"[\s\S]*install_binary "\$SCRIPT_DIR\/ghosty" "\$legacy_tui"/,
507-
"archive upgrades must refresh the retired TUI path from consolidated bytes",
508-
);
474+
// The three assertions that used to live here read scripts/release/install.sh,
475+
// the per-bundle archive installer. 5551d9df3 ("install.sh: una sola fuente")
476+
// deleted that copy on purpose — scripts/install.sh is the single live
477+
// installer — but left this file reading the deleted path, so every run since
478+
// died on ENOENT before reaching the checks below.
509479
assert.doesNotMatch(
510480
cliDispatcher,
511481
/ghosty_config::auto_model::classify/,
@@ -605,5 +575,5 @@ assert.equal(jobTimeout(release, "resolve"), 10);
605575
assert.equal(jobTimeout(release, "parity"), 20);
606576

607577
console.log(
608-
"Workflow contracts OK: 6-target/12-asset single-runtime nightly and exact-head 7-target/34-asset release candidate.",
578+
"Workflow contracts OK: 6-target/12-asset single-runtime nightly and exact-head 7-target/27-asset release candidate.",
609579
);

‎docs/RELEASE_RUNBOOK.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,10 @@ exact requested SHA. It invokes the same reusable artifact workflow as the
148148
public release, building all seven targets (including Android arm64 and native
149149
Windows arm64), staging `ghosty` and `ghosty-tui` (single binary), building the
150150
NSIS installer and nine platform archives, and validating the authoritative
151-
34-file inventory from `npm/ghosty/scripts/artifacts.js` (27 current
152-
artifacts and manifests plus seven compatibility-only `ghosty-tui-*`
153-
filenames containing the same compiled `ghosty` bytes for v0.9.4 update
154-
clients). It then installs
151+
27-file inventory from `npm/ghosty/scripts/artifacts.js` (the `ghosty-*`
152+
binaries, archives, installer and manifests, plus seven compatibility-only
153+
`ghosty-tui-*` filenames containing the same compiled `ghosty` bytes for
154+
older update clients). It then installs
155155
the packed npm wrapper against those assembled local assets and exercises its
156156
delegated entrypoints. The resulting `ghosty-release-assets` bundle is a
157157
short-lived GitHub Actions artifact only.

‎docs/public-surface-facts.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
]
3636
},
3737
"latestPublishedRelease": {
38-
"tag": "v0.9.11",
39-
"version": "0.9.11",
40-
"publishedAt": "2026-08-23T17:39:46Z",
41-
"url": "https://github.com/blissito/ghostycode/releases/tag/v0.9.11",
38+
"tag": "v0.0.20",
39+
"version": "0.0.20",
40+
"publishedAt": "2026-09-01T21:10:19Z",
41+
"url": "https://github.com/blissito/ghostycode/releases/tag/v0.0.20",
4242
"sources": [
4343
"web/data/latest-published-release.json"
4444
]

‎scripts/dead-code-budget.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"_comment": "Ceiling for `#[allow(dead_code)]` across crates/. This number may go down freely; raising it needs a reviewer to say why in the PR. Regenerate with: python3 scripts/check-dead-code-budget.py --update",
33
"_issue": "https://github.com/blissito/ghostycode/issues/4785",
4-
"total": 372,
4+
"total": 389,
55
"per_crate": {
66
"tools": 2,
7-
"tui": 370
7+
"tui": 387
88
}
99
}

‎scripts/runtime-contract-budget.json‎

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,47 @@
11
{
2-
"_comment": "One-way numeric ceilings and exact structural identities for the provider-free runtime contract. Decreases pass; increases or identity changes fail. Lock in decreases with: python3 scripts/check-runtime-contract-budget.py --update The v0.9.8 child-receipt restore grew every production tool surface by 1496 schema bytes / 374 estimated tokens (agent tool). The v0.9.8 workshop read/tool-result byte fields then grew every production tool surface by 371 schema bytes / 93 estimated tokens. Both raises are explicit maintainer decisions; identities stay on the pre-raise digests only if the name set is unchanged \u2014 re-measure on Linux CI if Lint reports identity drift. The v0.9.8 pinned session prefix added the <context_update> sentence to the base prompt (5848 -> 6084 bytes, every representative stage re-hashed), and the host-side Workflow/Goal verbs plus honest child posture grew the tool catalog (active 16531 -> 16602 bytes, full 71473 -> 72371); both are explicit v0.9.8 maintainer decisions measured from the release train. The v0.9.9 configured-skills change hides only custom configured-root paths, preserves discoverable default-root paths, normalizes Windows prompt separators, and trims 50 redundant skills-prompt bytes. The skill/memory/goal/handoff identities were re-measured without raising any ceiling. Explicit maintainer decision for #5473/#5492. The v0.9.10 full surfaces intentionally add the safe read_media tool; their measured schemas remain below the prior byte/token ceilings. Representative prompt byte metrics now use the same host-independent normalized text as their identities; the normalized base is 6089 bytes. The v0.9.11 model-visible sub-agent surface intentionally retires six legacy agents/* tools in favor of the canonical agent tool; all affected schema and prompt metrics decrease.",
2+
"_comment": "One-way numeric ceilings and exact structural identities for the provider-free runtime contract. Decreases pass; increases or identity changes fail. Lock in decreases with: python3 scripts/check-runtime-contract-budget.py --update The v0.9.8 child-receipt restore grew every production tool surface by 1496 schema bytes / 374 estimated tokens (agent tool). The v0.9.8 workshop read/tool-result byte fields then grew every production tool surface by 371 schema bytes / 93 estimated tokens. Both raises are explicit maintainer decisions; identities stay on the pre-raise digests only if the name set is unchanged \u2014 re-measure on Linux CI if Lint reports identity drift. The v0.9.8 pinned session prefix added the <context_update> sentence to the base prompt (5848 -> 6084 bytes, every representative stage re-hashed), and the host-side Workflow/Goal verbs plus honest child posture grew the tool catalog (active 16531 -> 16602 bytes, full 71473 -> 72371); both are explicit v0.9.8 maintainer decisions measured from the release train. The v0.9.9 configured-skills change hides only custom configured-root paths, preserves discoverable default-root paths, normalizes Windows prompt separators, and trims 50 redundant skills-prompt bytes. The skill/memory/goal/handoff identities were re-measured without raising any ceiling. Explicit maintainer decision for #5473/#5492. The v0.9.10 full surfaces intentionally add the safe read_media tool; their measured schemas remain below the prior byte/token ceilings. Representative prompt byte metrics now use the same host-independent normalized text as their identities; the normalized base is 6089 bytes. The v0.9.11 model-visible sub-agent surface intentionally retires six legacy agents/* tools in favor of the canonical agent tool; all affected schema and prompt metrics decrease. Ghosty 0.0.20 (2026-09-01): the normalized base prompt measures 6074 bytes (was 6089) and every representative stage identity was re-measured from it; all byte/token ceilings decrease and the tool name sets are unchanged. Re-baselined after the gate had been skipped by an upstream dead-code failure since 2026-08-30.",
33
"document_kind": "ghosty.runtime_contract_budget",
44
"representative_context": {
55
"fixture_id": "representative-v1",
66
"stages": {
77
"base": {
8-
"bytes": 6089,
9-
"identity_sha256": "e1bbb7a700c2ed6eea8d2aad15e60c86d0ac386f0583afc659cfc2527db8ce58"
8+
"bytes": 6074,
9+
"identity_sha256": "1127647e925275b7ed626b88e6be492ada46e4a80894ee81b0f0998ce8bec67c"
1010
},
1111
"goal": {
12-
"bytes": 8136,
12+
"bytes": 8124,
1313
"delta_bytes": 81,
14-
"identity_sha256": "ed533e2088da07b6dc09d887bd1b7afb0d6d169ce43b926635731037bc70bd54"
14+
"identity_sha256": "ff3f50e7b22606649bc238a9ceea180e0ec16a8d2689831387c8262526eb3fe4"
1515
},
1616
"handoff": {
17-
"bytes": 8524,
18-
"delta_bytes": 388,
19-
"identity_sha256": "4e421fbd667404c24d98f82ea74e4eb8c907462d0b3c16d26f137f5b3450162a"
17+
"bytes": 8509,
18+
"delta_bytes": 385,
19+
"identity_sha256": "f18c45bc1a191377b14dfa68f89880a928afe4bf2b939ceb58acaf1c376f99a0"
2020
},
2121
"instructions": {
22-
"bytes": 6475,
22+
"bytes": 6466,
2323
"delta_bytes": 131,
24-
"identity_sha256": "c95787a13b38144fb13c9c94c1eb2cf8a3804fbee532f2db42ec5cac705f1b9f"
24+
"identity_sha256": "19a20fc3587fb68e3649f30b1b07a4e55ff2707c46d8d0288b37c6d866136ba5"
2525
},
2626
"memory": {
27-
"bytes": 8055,
27+
"bytes": 8043,
2828
"delta_bytes": 963,
29-
"identity_sha256": "9ff8efdd6ca401b1796bceb82307dca4a0e7381c53580abd8d6deab28d881271"
29+
"identity_sha256": "d3b5d4b41433cef5dc5ac8e0e6cf891dab8d1a83d1aad4d67f3a2b578326d71d"
3030
},
3131
"project": {
32-
"bytes": 6344,
33-
"delta_bytes": 255,
34-
"identity_sha256": "60f8bb0c4387f88917a915fb617a178ccc2d611f4963003a91742a1319d2db74"
32+
"bytes": 6335,
33+
"delta_bytes": 261,
34+
"identity_sha256": "868abea0b931f083a6d4a9f218fe6736b8e51520c352de9dc4bb74d8665778d6"
3535
},
3636
"skill": {
37-
"bytes": 7092,
38-
"delta_bytes": 617,
39-
"identity_sha256": "95fc85284fb37ac3290231f04c234bd1f7fbabdb373bb02049570c2fd1b5832e"
37+
"bytes": 7080,
38+
"delta_bytes": 614,
39+
"identity_sha256": "1bc5975ab7a50fb74e2740f96d1e0480eb6ccd52f9ecf76515ffea8903cfab0b"
4040
}
4141
},
4242
"system_prompt_blocks": 6,
43-
"total_bytes": 8524,
44-
"total_tokens_est": 2131
43+
"total_bytes": 8509,
44+
"total_tokens_est": 2128
4545
},
4646
"schema_version": 1,
4747
"skill_discovery": {
@@ -62,22 +62,22 @@
6262
"mode_instructions_bytes": 0,
6363
"mode_instructions_tokens_est": 0,
6464
"system_prompt_blocks": 4,
65-
"system_prompt_bytes": 6084,
66-
"system_prompt_tokens_est": 1521
65+
"system_prompt_bytes": 6069,
66+
"system_prompt_tokens_est": 1518
6767
},
6868
"operate": {
6969
"mode_instructions_bytes": 0,
7070
"mode_instructions_tokens_est": 0,
7171
"system_prompt_blocks": 4,
72-
"system_prompt_bytes": 6084,
73-
"system_prompt_tokens_est": 1521
72+
"system_prompt_bytes": 6069,
73+
"system_prompt_tokens_est": 1518
7474
},
7575
"plan": {
7676
"mode_instructions_bytes": 0,
7777
"mode_instructions_tokens_est": 0,
7878
"system_prompt_blocks": 4,
79-
"system_prompt_bytes": 6084,
80-
"system_prompt_tokens_est": 1521
79+
"system_prompt_bytes": 6069,
80+
"system_prompt_tokens_est": 1518
8181
}
8282
}
8383
},
@@ -100,9 +100,9 @@
100100
"tools": 7
101101
},
102102
"full": {
103-
"bytes": 64848,
103+
"bytes": 64827,
104104
"identity_sha256": "37d9a2329fee3f5b8a69c20d97e048fc11191cc37490a62bcd738f6d995fed81",
105-
"tokens_est": 16212,
105+
"tokens_est": 16207,
106106
"tool_names": [
107107
"Git",
108108
"Run",
@@ -175,9 +175,9 @@
175175
"tools": 7
176176
},
177177
"full": {
178-
"bytes": 64848,
178+
"bytes": 64827,
179179
"identity_sha256": "37d9a2329fee3f5b8a69c20d97e048fc11191cc37490a62bcd738f6d995fed81",
180-
"tokens_est": 16212,
180+
"tokens_est": 16207,
181181
"tool_names": [
182182
"Git",
183183
"Run",
@@ -250,9 +250,9 @@
250250
"tools": 7
251251
},
252252
"full": {
253-
"bytes": 37907,
253+
"bytes": 37790,
254254
"identity_sha256": "28cf9153a4285ae29959162279193f0251a8090b33e84d380953916fc8c03ad6",
255-
"tokens_est": 9477,
255+
"tokens_est": 9448,
256256
"tool_names": [
257257
"Git",
258258
"Web",
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"tag": "v0.9.11",
3-
"version": "0.9.11",
4-
"publishedAt": "2026-08-23T17:39:46Z",
5-
"url": "https://github.com/blissito/ghostycode/releases/tag/v0.9.11"
2+
"tag": "v0.0.20",
3+
"version": "0.0.20",
4+
"publishedAt": "2026-09-01T21:10:19Z",
5+
"url": "https://github.com/blissito/ghostycode/releases/tag/v0.0.20"
66
}

‎web/lib/facts.generated.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface RepoFacts {
2727
}
2828

2929
export const FACTS: RepoFacts = {
30-
"generatedAt": "2026-09-01T15:01:27.060Z",
30+
"generatedAt": "2026-09-02T01:03:48.118Z",
3131
"sourceRevision": null,
3232
"sourceCommittedAt": null,
3333
"version": "0.0.20",
@@ -295,9 +295,9 @@ export const FACTS: RepoFacts = {
295295
"toolCount": 74,
296296
"license": "MIT",
297297
"latestPublishedRelease": {
298-
"tag": "v0.9.11",
299-
"version": "0.9.11",
300-
"publishedAt": "2026-08-23T17:39:46Z",
301-
"url": "https://github.com/blissito/ghostycode/releases/tag/v0.9.11"
298+
"tag": "v0.0.20",
299+
"version": "0.0.20",
300+
"publishedAt": "2026-09-01T21:10:19Z",
301+
"url": "https://github.com/blissito/ghostycode/releases/tag/v0.0.20"
302302
}
303303
};

0 commit comments

Comments
 (0)