Skip to content

A skewed framework build is refused by the repo's layout marker (fix #1575) - #1598

Merged
suleimansh merged 1 commit into
mainfrom
issue-1575-layout-gate
Aug 19, 2026
Merged

A skewed framework build is refused by the repo's layout marker (fix #1575)#1598
suleimansh merged 1 commit into
mainfrom
issue-1575-layout-gate

Conversation

@suleimansh

@suleimansh suleimansh commented Aug 19, 2026

Copy link
Copy Markdown
Member

The problem, plainly

Cloud web runs don't use your local build — the cloud environment installs the framework from npm, and the published version can be older than the repo it works on. That happened in #1574: the repo had renamed its archive folder from sessions/ to agents/ weeks earlier, but the published build didn't know, saved a session archive under the old name, and the bad commit sailed into a PR — caught only hours later by main's guard test, with an error about symptoms rather than the cause.

The fix, plainly

The repo now carries a small committed file, .the-framework/LAYOUT — 7 lines saying where the framework keeps its bookkeeping:

framework-dir: .the-framework
archive-dir: agents
branches-dir: branches
events-file: events.jsonl
meta-file: agent.json
tickets-dir: tickets
queue-file: TODO_AGENTS.md

Before starting any session, the framework compares this file against what the running build would write. If they disagree, the session is refused immediately — exit 1, both layouts printed side by side, the fix named — before a single file is written. Same stance as the extension version gate (#1519): refuse, don't warn, because a skewed build doesn't fail loudly on its own — it half-works, and its wrong-layout commits look plausible to a human.

The file's content is generated from the code's own constants, and a lockstep test pins the checked-in file to that generation (the EXPECTED_EXTENSION_VERSIONmanifest.json trick). So nobody has to remember to bump anything: renaming a folder in code breaks the test until the file is regenerated in the same PR.

What changed where

  • src/layout.ts (new) — the marker derivation + checkLayout(). A repo without the marker passes ungated (how test fixtures and pre-gate repos keep working; install writes it, so every newly activated repo is gated).
  • src/cli.ts — the gate runs in driveAgent right after the workspace is known, before any write.
  • src/install.ts — seeds the marker; the seeded .gitignore gains !LAYOUT (its * rule would otherwise keep the marker out of the install commit).
  • src/layout.test.ts — unit coverage + the lockstep test; control.test.ts's committed-files guard now allows LAYOUT.
  • .the-framework/LAYOUT + .the-framework/.gitignore — the repo's own marker, checked in.

How this fits #1595 (data branch)

Compatible — and complementary. #1595 keeps every name this marker derives from (tickets, TODO_AGENTS.md, agents, …) but changes where they live (the the-framework_data branch). Two touchpoints:

  1. When All framework data moves to the tf-data branch (fix #1582) #1595 lands, add one derived line to the marker — data-branch: the-framework_data from its DATA_BRANCH constant — so the placement change itself becomes a layout change the gate catches. One line in layoutMarker(); the lockstep test then forces the checked-in marker to regenerate in that same change.
  2. Ideally this PR merges and gets published before the All framework data moves to the tf-data branch (fix #1582) #1595 migration: then a stale pre-All framework data moves to the tf-data branch (fix #1582) #1595 cloud build refuses to run in the migrated repo instead of writing main-style bookkeeping into it — the gate is exactly the protection that migration wants.

Only textual overlap: both touch .the-framework/.gitignore (!LAYOUT must survive #1595's simplification).

Limits

  • The gate only protects skews after the next npm publish — a build published before this PR contains no gate and can't refuse. Publishing once after merge (e.g. the standing version-packages PR chore: version packages #1285) arms it for good.
  • Tests: full node suite 1496/1496; live E2E in the comment below (install seeds it, skew refused with zero writes, no marker = ungated, fresh clone passes).

…1575)

Cloud web runs execute the published npm build, and a published build that
predates a repo-side rename committed its session archive under the retired
sessions/ name (#1574) — caught hours later by main's guard test instead of
seconds in, with a message about symptoms rather than the cause.

The repo now records the bookkeeping layout it is on in a tracked
.the-framework/LAYOUT marker, and a build whose own layout differs refuses
the session outright before writing anything — both layouts and the fix
named, no degraded mode, the same stance as the extension version gate
(#1519). The marker's content derives from the build's layout constants, so
a rename changes it by itself, and a lockstep test pins the checked-in
marker to the derivation — a rename cannot land without regenerating it.
Install seeds the marker (and un-ignores it), so every newly activated repo
is gated from the start; a repo without one stays ungated.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@suleimansh

Copy link
Copy Markdown
Member Author

Live-tested end to end against a scratch repo, using the built CLI (dist/bin.js --agent, fake driver, isolated XDG_CONFIG_HOME):

  1. Install seeds the gate: installProject on a fresh repo wrote .the-framework/LAYOUT with the derived content, the seeded ignore carries !LAYOUT, and both land tracked in the install commit.
  2. Matching marker → runs: a session in that repo completed normally (exit 0).
  3. Skewed marker → refused: with the marker edited to archive-dir: sessions (the exact tickets: sync from GitHub issues (2026-08-18) #1574 skew), the session exits 1 immediately, printing both layouts side by side and the fix for each direction. Checksummed every file under .the-framework/ before and after: byte-identical — the refused run wrote nothing.
  4. No marker → ungated: with the marker deleted, the session runs (pre-gate repos keep working).
  5. Fresh clone carries the gate: a git clone of this branch (how a cloud VM gets the repo) contains the tracked marker, and a session in the clone passes the gate.

@suleimansh
suleimansh merged commit 26b9915 into main Aug 19, 2026
2 checks passed
@suleimansh
suleimansh deleted the issue-1575-layout-gate branch August 19, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant