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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: Latch regression checks
on:
push:
branches: [main]
tags: ['v*']
pull_request:
workflow_dispatch:
permissions:
Expand Down Expand Up @@ -57,7 +58,7 @@ jobs:
path: reports/
publish:
needs: handler-and-unit
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the manual tagged release procedure

The checked release documentation at docs/ci.md:39 still says manual dispatches are read-only, main pushes publish, and the workflow creates a missing tag. This condition now makes manual dispatch the only publishing path, while scripts/release.cjs:69-70 requires the exact tag to exist beforehand. An operator following the repository's sole documented procedure will either see no publication after a main push or receive an error from a manual run without a pre-created tag, so the release instructions need to be updated with the new ordering.

Useful? React with 👍 / 👎.

runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.0.1

- Keep project-file reads tied to the checked descriptor and reject a swapped path before generating or applying an integration.
- Keep CI handler requests on the fixed loopback test server even when example fixture data supplies another URL.
- Verify the exact release tag before publishing the six matching package archives.

## 1.0.0

2026-10-06
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The [September compatibility record](docs/compatibility-2026-09-08.md), [Septemb

The local CLI requires **Node 22+** and **pnpm 10.17.1+**. Integration targets an existing React, TypeScript, and Vite application. The [installation guide](docs/installation.md) lists tested versions and browser setup.

Version **1.0.0** uses six prebuilt package tarballs with checksums in the [GitHub release](https://github.com/agammann/latch/releases/tag/v1.0.0). The `@latch-local/*` names are local distribution identifiers; packages have not been published to a registry. The public Studio runs on OpenAI Sites. This repository contains the CLI, runtime, local console, examples, and release evidence; the Studio website source is maintained separately. Studio version 5 runtime downloads were verified against the six published v1.0.0 packages on October 7, 2026 (UTC); see the [delivered-release check](docs/studio.md#delivered-v1-release-check-october-7-2026-utc). The [v1 stability contract](docs/stability.md) covers the CLI, typed runtime contracts and React/Vite integration.
Version **1.0.1** uses six prebuilt package tarballs with checksums in the [GitHub release](https://github.com/agammann/latch/releases/tag/v1.0.1). The `@latch-local/*` names are local distribution identifiers; packages have not been published to a registry. The public Studio runs on OpenAI Sites. This repository contains the CLI, runtime, local console, examples, and release evidence; the Studio website source is maintained separately. Studio version 5 runtime downloads were verified against the six published v1.0.0 packages on October 7, 2026 (UTC); see the [delivered-release check](docs/studio.md#delivered-v1-release-check-october-7-2026-utc). The [v1 stability contract](docs/stability.md) covers the CLI, typed runtime contracts and React/Vite integration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point upgrade users at 1.0.1

This now advertises 1.0.1 as the current package set and links users to the v1 stability contract, but the linked docs/stability.md:15-17 upgrade procedure still tells 0.1.0 users to replace their packages with v1.0.0. Because this release exists specifically to distribute the project-read and CI-loopback security fixes absent from the 1.0.0 downloads, users following that documented upgrade path will install the old package set instead of the security release.

Useful? React with 👍 / 👎.


## Documentation and project status

Expand Down
34 changes: 17 additions & 17 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ The generator compiles against your `tsconfig.json`. The tested setup uses TypeS

Choose one source:

1. **From GitHub:** download all six `latch-local-*-1.0.0.tgz` assets and `SHA256SUMS` from the [v1.0.0 release](https://github.com/agammann/latch/releases/tag/v1.0.0). Verify each tarball against SHA256SUMS before installing it. On PowerShell, use `Get-FileHash <tarball> -Algorithm SHA256`; on Linux, run `sha256sum -c SHA256SUMS` in the download directory.
1. **From GitHub:** download all six `latch-local-*-1.0.1.tgz` assets and `SHA256SUMS` from the [v1.0.1 release](https://github.com/agammann/latch/releases/tag/v1.0.1). Verify each tarball against SHA256SUMS before installing it. On PowerShell, use `Get-FileHash <tarball> -Algorithm SHA256`; on Linux, run `sha256sum -c SHA256SUMS` in the download directory.
2. **From this repository:** follow the checkout and dependency steps in the [example quickstart](quickstart.md), then run `pnpm build` and `pnpm pack:local` from the Latch repository root. The tarballs are written to `artifacts/`.

Studio is maintained separately. Its runtime download is not established as the v1.0.0 package set; obtain v1 packages through GitHub or a v1 source checkout. Studio integration configurations use schema 1 and must still pass your installed CLI checks.
Studio is maintained separately. Its runtime download is not established as the v1.0.1 package set; obtain v1 packages through GitHub or a v1 source checkout. Studio integration configurations use schema 1 and must still pass your installed CLI checks.

In your application, create `vendor/latch/` and copy all six tarballs into it. Keep the `.tgz` files intact; pnpm installs them directly. The following steps assume this directory structure:

Expand All @@ -28,12 +28,12 @@ your-app/
tsconfig.json
src/
vendor/latch/
latch-local-contracts-1.0.0.tgz
latch-local-browser-1.0.0.tgz
latch-local-runtime-1.0.0.tgz
latch-local-react-1.0.0.tgz
latch-local-test-1.0.0.tgz
latch-local-cli-1.0.0.tgz
latch-local-contracts-1.0.1.tgz
latch-local-browser-1.0.1.tgz
latch-local-runtime-1.0.1.tgz
latch-local-react-1.0.1.tgz
latch-local-test-1.0.1.tgz
latch-local-cli-1.0.1.tgz
```

For a pnpm workspace, put the overrides in the workspace root's `pnpm-workspace.yaml` and resolve their paths relative to that root. Run package installation commands in the application package. The example above assumes a single application root.
Expand All @@ -44,25 +44,25 @@ Add these entries to your application's `pnpm-workspace.yaml`. If the file exist

```yaml
overrides:
'@latch-local/contracts': file:./vendor/latch/latch-local-contracts-1.0.0.tgz
'@latch-local/browser': file:./vendor/latch/latch-local-browser-1.0.0.tgz
'@latch-local/runtime': file:./vendor/latch/latch-local-runtime-1.0.0.tgz
'@latch-local/react': file:./vendor/latch/latch-local-react-1.0.0.tgz
'@latch-local/test': file:./vendor/latch/latch-local-test-1.0.0.tgz
'@latch-local/cli': file:./vendor/latch/latch-local-cli-1.0.0.tgz
'@latch-local/contracts': file:./vendor/latch/latch-local-contracts-1.0.1.tgz
'@latch-local/browser': file:./vendor/latch/latch-local-browser-1.0.1.tgz
'@latch-local/runtime': file:./vendor/latch/latch-local-runtime-1.0.1.tgz
'@latch-local/react': file:./vendor/latch/latch-local-react-1.0.1.tgz
'@latch-local/test': file:./vendor/latch/latch-local-test-1.0.1.tgz
'@latch-local/cli': file:./vendor/latch/latch-local-cli-1.0.1.tgz
```

All six overrides are required because the packages depend on one another and are not published to a registry. Do not put these overrides only in `package.json`; pnpm 11 uses the workspace YAML settings. See the [pnpm settings reference](https://pnpm.io/settings).

Now install the browser dependencies and developer tools:

```sh
pnpm add --ignore-scripts ./vendor/latch/latch-local-contracts-1.0.0.tgz ./vendor/latch/latch-local-browser-1.0.0.tgz ./vendor/latch/latch-local-runtime-1.0.0.tgz ./vendor/latch/latch-local-react-1.0.0.tgz
pnpm add -D --ignore-scripts ./vendor/latch/latch-local-test-1.0.0.tgz ./vendor/latch/latch-local-cli-1.0.0.tgz
pnpm add --ignore-scripts ./vendor/latch/latch-local-contracts-1.0.1.tgz ./vendor/latch/latch-local-browser-1.0.1.tgz ./vendor/latch/latch-local-runtime-1.0.1.tgz ./vendor/latch/latch-local-react-1.0.1.tgz
pnpm add -D --ignore-scripts ./vendor/latch/latch-local-test-1.0.1.tgz ./vendor/latch/latch-local-cli-1.0.1.tgz
pnpm exec latch --version
```

The version command should print `1.0.0`. The Latch tarballs are prebuilt and need no installation scripts. These commands skip dependency lifecycle scripts for this installation; keep your existing application's build dependencies configured separately. This also avoids pnpm 11 stopping on an unapproved dependency build script. Retain the tarballs at their configured paths for future installs. Registry lookups for `@latch-local/*` usually mean an override is missing or points at the wrong directory.
The version command should print `1.0.1`. The Latch tarballs are prebuilt and need no installation scripts. These commands skip dependency lifecycle scripts for this installation; keep your existing application's build dependencies configured separately. This also avoids pnpm 11 stopping on an unapproved dependency build script. Retain the tarballs at their configured paths for future installs. Registry lookups for `@latch-local/*` usually mean an override is missing or points at the wrong directory.

## 3. Add your configuration and mount marker

Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Install Git, Node 22 or later, and pnpm 10.17.1 or later. The repository pins pn
Run these commands in a terminal:

```sh
git clone --branch v1.0.0 --depth 1 https://github.com/agammann/latch.git
git clone --branch v1.0.1 --depth 1 https://github.com/agammann/latch.git
cd latch
pnpm install --frozen-lockfile --ignore-scripts
pnpm build
Expand Down
4 changes: 2 additions & 2 deletions examples/catalog/.latch/manifest.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"version": 1,
"generatorVersion": "1.0.0",
"generatorVersion": "1.0.1",
"files": {
"src/latch.generated/integration.ts": {
"before": null,
"after": "113580268f942f385c2285762dec1c41a07718d9d976f6d1eb934670f362c779"
"after": "0821f8af189918d1fdc0fde49b5c8189bf65fc19555bb463d06024bb5f1242d9"
},
"src/latch.generated/env.d.ts": {
"before": null,
Expand Down
2 changes: 1 addition & 1 deletion examples/catalog/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "latch-example-catalog",
"private": true,
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 5173 --strictPort",
Expand Down
2 changes: 1 addition & 1 deletion examples/catalog/src/latch.generated/integration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated by Latch 1.0.0. Edit latch.config.json and application handlers, then regenerate.
// Generated by Latch 1.0.1. Edit latch.config.json and application handlers, then regenerate.
import { useLatch, type ScopeObserver } from '@latch-local/react';
import type { Handler, Contract } from '@latch-local/contracts';
export type Input0 = { "query"?: string; "category"?: "all" | "workspace" | "outdoors"; "sort"?: "name" | "price-asc" | "price-desc" };
Expand Down
4 changes: 2 additions & 2 deletions examples/docs/.latch/manifest.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"version": 1,
"generatorVersion": "1.0.0",
"generatorVersion": "1.0.1",
"files": {
"src/latch.generated/integration.ts": {
"before": null,
"after": "465b4160a555317818dcca8259c6734df98ed2b91ce046f9977aaf2136a3f213"
"after": "acfc48e253c7ba258198d0f12e01094916c9c951208ca4bc63b036ae8a08b0ae"
},
"src/latch.generated/env.d.ts": {
"before": null,
Expand Down
2 changes: 1 addition & 1 deletion examples/docs/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "latch-example-docs",
"private": true,
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 5174 --strictPort",
Expand Down
2 changes: 1 addition & 1 deletion examples/docs/src/latch.generated/integration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated by Latch 1.0.0. Edit latch.config.json and application handlers, then regenerate.
// Generated by Latch 1.0.1. Edit latch.config.json and application handlers, then regenerate.
import { useLatch, type ScopeObserver } from '@latch-local/react';
import type { Handler, Contract } from '@latch-local/contracts';
import { searchDocs as handler0 } from "../library";
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "latch-release-workspace",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"type": "module",
"packageManager": "pnpm@10.17.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @latch-local/browser 1.0.0
# @latch-local/browser 1.0.1

Part of Latch: Connect your site. Verify every action.

Expand Down
2 changes: 1 addition & 1 deletion packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@latch-local/browser",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"description": "Latch browser: Connect your site. Verify every action.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @latch-local/cli 1.0.0
# @latch-local/cli 1.0.1

Part of Latch: Connect your site. Verify every action.

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@latch-local/cli",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"description": "Latch cli: Connect your site. Verify every action.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '1.0.0';
export const VERSION = '1.0.1';
2 changes: 1 addition & 1 deletion packages/console/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@latch-local/console-source",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"type": "module",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @latch-local/contracts 1.0.0
# @latch-local/contracts 1.0.1

Part of Latch: Connect your site. Verify every action.

Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@latch-local/contracts",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"description": "Latch contracts: Connect your site. Verify every action.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/react/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @latch-local/react 1.0.0
# @latch-local/react 1.0.1

Part of Latch: Connect your site. Verify every action.

Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@latch-local/react",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"description": "Latch react: Connect your site. Verify every action.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @latch-local/runtime 1.0.0
# @latch-local/runtime 1.0.1

Part of Latch: Connect your site. Verify every action.

Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@latch-local/runtime",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"description": "Latch runtime: Connect your site. Verify every action.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/test/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @latch-local/test 1.0.0
# @latch-local/test 1.0.1

Part of Latch: Connect your site. Verify every action.

Expand Down
2 changes: 1 addition & 1 deletion packages/test/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@latch-local/test",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"description": "Latch test: Connect your site. Verify every action.",
"license": "MIT",
Expand Down
12 changes: 5 additions & 7 deletions scripts/release.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ const path = require('node:path');
const crypto = require('node:crypto');

module.exports = async ({ github, context, core }) => {
if (context.eventName !== 'push' || context.ref !== 'refs/heads/main' ||
if (context.eventName !== 'workflow_dispatch' || context.ref !== 'refs/heads/main' ||
!/^[0-9a-f]{40}$/.test(context.sha)) {
throw new Error('Publishing requires a main push with an exact commit SHA.');
throw new Error('Publishing requires a manual run on main with an exact commit SHA.');
}
const version = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
if (!/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(version)) {
Expand Down Expand Up @@ -66,7 +66,9 @@ module.exports = async ({ github, context, core }) => {
}
return commit;
};
await requireCorrectTag();
if (await requireCorrectTag() !== context.sha) {
throw new Error('Create and verify the exact version tag before dispatching publication.');
}

const directory = 'release-artifacts';
const filenames = fs.readdirSync(directory).sort();
Expand Down Expand Up @@ -164,11 +166,7 @@ module.exports = async ({ github, context, core }) => {
if (!latest.draft || latest.target_commitish !== context.sha || latest.tag_name !== tag) {
throw new Error('Draft changed during upload; publication stopped.');
}
// Create the tag at the checked commit explicitly; target_commitish cannot move an existing tag.
await requireCurrentHead();
if (await requireCorrectTag() === null) {
await github.rest.git.createRef({ owner, repo, ref: 'refs/tags/' + tag, sha: context.sha });
}
if (await requireCorrectTag() !== context.sha) {
throw new Error('Release tag is missing before publication.');
}
Expand Down
4 changes: 2 additions & 2 deletions tests/generator.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ test('a valid 0.1.0 ownership manifest upgrades and keeps rollback protection',
generate(r);
apply(r);
const output = path.join(r, 'src/latch.generated/integration.ts');
fs.writeFileSync(output, fs.readFileSync(output, 'utf8').replace('Latch 1.0.0.', 'Latch 0.1.0.'));
fs.writeFileSync(output, fs.readFileSync(output, 'utf8').replace('Latch 1.0.1.', 'Latch 0.1.0.'));
const manifestPath = path.join(r, '.latch/manifest.json');
const old = JSON.parse(fs.readFileSync(manifestPath));
old.generatorVersion = '0.1.0';
old.files['src/latch.generated/integration.ts'].after = createHash('sha256').update(fs.readFileSync(output)).digest('hex');
fs.writeFileSync(manifestPath, JSON.stringify(old));
generate(r);
assert.equal(JSON.parse(fs.readFileSync(manifestPath)).generatorVersion, '1.0.0');
assert.equal(JSON.parse(fs.readFileSync(manifestPath)).generatorVersion, '1.0.1');
assert.equal(check(r).installation, 'applied');
rollback(r);
assert.equal(fs.readFileSync(path.join(r, 'src/main.tsx'), 'utf8'), before);
Expand Down
Loading