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
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Changelog

## v0.11.0 - 2026-08-17

OpenTag 0.11.0 fixes a fail-open edge in zero-config governed completion. A
GitHub-backed pull-request run now remains incomplete until authoritative API
evidence proves that the current-head check rollup is both non-empty and
complete. All 18 public packages are coordinated at `0.11.0`.

### Fixed

- Empty check maps no longer satisfy the default observed-checks gate.
- Truncated GitHub check-run or commit-status pages no longer appear fully
passing; reconciliation compares each returned page with its provider
`total_count` and verifies every entry belongs to the current head.
- Missing rollup-completeness evidence defaults to incomplete at the dispatcher
ingestion boundary, preserving fail-closed behavior across mixed deployments.

### Changed

- `GitHubCompletionApi.listCheckRunsForRef` now returns
`{ totalCount, checkRuns }`, and `getCombinedStatusForRef` returns
`{ totalCount, statuses }`.
- `GitHubVerifiedPullRequestSnapshot` now requires `checksComplete`, and that
value participates in semantic evidence digests and completion assessment.
- The OpenTag skill pins executable install paths to the reviewed 0.11.0 CLI,
clarifies Hosted Control V1 registration and relay trust, and corrects the
Microsoft Teams conversation-binding source.

### Compatibility and migration

- This pre-1.0 minor release intentionally changes exported TypeScript
contracts. Custom `GitHubCompletionApi` implementations must return the new
page objects, and custom snapshot producers must provide `checksComplete`.
- Existing explicit repository completion policies remain authoritative, and
runs without a pull request retain executor-success semantics.

## v0.10.0 - 2026-08-17

Source manifests are prepared as the coordinated `0.10.0` release for all 18
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,10 @@ opentag-dev setup

## Packages

Package source release: `v0.10.0`. This source state is prepared for
Package source release: `v0.11.0`. This source state is prepared for
local release validation only; it is not evidence that the release was
published. npm dist-tags remain authoritative for public channel versions, and
`0.9.0` remains the documented stable release until registry evidence says
`0.10.0` remains the documented stable release until registry evidence says
otherwise. The coordinated package family contains 18 public packages under
the `@opentag` scope.

Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,9 @@ opentag-dev setup

## 软件包

当前源码发布版本:`v0.10.0`。此源码状态只表示已准备本地发布验证,
当前源码发布版本:`v0.11.0`。此源码状态只表示已准备本地发布验证,
不代表该版本已经发布;npm dist-tag 仍是公开通道版本的权威来源,在注册表
证据表明发生变化前,`0.9.0` 仍是文档所述的稳定版本。OpenTag 在
证据表明发生变化前,`0.10.0` 仍是文档所述的稳定版本。OpenTag 在
`@opentag` scope 下协调发布 18 个公开软件包。

| 包 | 用途 |
Expand Down
2 changes: 1 addition & 1 deletion docs/live-e2e-smoke-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ smoke_root="$(mktemp -d)"
set -euo pipefail
cd "$smoke_root"
npm init -y >/dev/null
npm install --no-audit --no-fund @opentag/cli@0.10.0
npm install --no-audit --no-fund @opentag/cli@0.11.0
./node_modules/.bin/opentag --version
)
```
Expand Down
216 changes: 83 additions & 133 deletions docs/npm-release.md

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions docs/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ Private runnable apps are not published:

## Pre-1.0 Policy

The source package family is prepared as the coordinated `0.10.0` release.
The source package family is prepared as the coordinated `0.11.0` release.
Source manifests are preparation evidence only: npm dist-tags remain
authoritative for published channel versions, and `0.9.0` remains the
authoritative for published channel versions, and `0.10.0` remains the
documented stable release until registry evidence says otherwise. The public
API is still settling, so all releases remain in the `0.x` line until the
package contracts are stable enough for `1.0.0`.
Expand All @@ -59,8 +59,12 @@ and introduces an optional self-hosted Control Plane distributed as a private
OCI image. Its release procedure is documented in
[`npm-release.md`](npm-release.md); the earlier `0.10.0-next.0` candidate
procedure in [`npm-prerelease.md`](npm-prerelease.md) was superseded before
publication. Preparing this source state does not assert that npm publication,
provider-live validation, or stable promotion has occurred.
publication.
The `0.11.0` release makes zero-config governed completion fail closed until
GitHub proves a non-empty, complete current-head check rollup. It also updates
the exported `GitHubCompletionApi` page-return contracts and adds the required
`checksComplete` field to `GitHubVerifiedPullRequestSnapshot`; these pre-1.0
TypeScript contract changes are documented in the changelog.

For each npm release:

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": "@opentag/cli",
"version": "0.10.0",
"version": "0.11.0",
"description": "OpenTag command line interface.",
"type": "module",
"engines": {
Expand Down
60 changes: 35 additions & 25 deletions packages/cli/test/docs-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,22 @@ describe("platform setup docs contract", () => {
expect(normalizedPrereleaseGuide).toContain(
"The `0.10.0-next.0` candidate described here was never published"
);
expect(versioningGuide).toContain("coordinated `0.10.0` release");
expect(versioningGuide).toContain("The `0.10.0` release");
expect(readme).toContain("[npm prerelease candidate guide](docs/npm-prerelease.md)");
expect(readmeZh).toContain("[npm prerelease 候选发布指南](docs/npm-prerelease.md)");
});

it("keeps the 0.10.0 release procedure explicit and concurrency-safe", () => {
it("keeps the 0.11.0 release procedure explicit and concurrency-safe", () => {
const releaseGuide = repoFile("docs/npm-release.md");
const liveGuide = repoFile("docs/live-e2e-smoke-harness.md");
const versioningGuide = repoFile("docs/versioning.md");
const changelog = repoFile("CHANGELOG.md");

expect(liveGuide).toContain("npm install --no-audit --no-fund @opentag/cli@0.10.0");
expect(liveGuide).toContain("npm install --no-audit --no-fund @opentag/cli@0.11.0");
expect(versioningGuide).toContain("coordinated `0.11.0` release");
expect(changelog).toContain("## v0.11.0 - 2026-08-17");
expect(changelog).toContain("GitHubCompletionApi");
expect(changelog).toContain("checksComplete");
expect(liveGuide).toContain('smoke_root="$(mktemp -d)"\n(\n set -euo pipefail');
expect(releaseGuide).toContain("refs/heads/release-lock/npm-dist-tags");
expect(releaseGuide).toContain("npm-dist-tags.lock-sha");
Expand All @@ -69,42 +75,45 @@ describe("platform setup docs contract", () => {
expect(releaseGuide).toContain('= "$lock_commit"');
expect(releaseGuide).toContain('current_latest="$(jq -er');
expect(releaseGuide).toContain('current_next="$(jq -er');
expect(releaseGuide).toContain('test "$current_next" = "0.10.0"');
expect(releaseGuide).toContain("First-publication exception");
expect(
releaseGuide.match(/"@opentag\/control-protocol"\|"@opentag\/delivery-contract"\)/gu)
).toHaveLength(3);
expect(releaseGuide).toContain("no previous `latest` to restore");
expect(releaseGuide.match(/test "\$previous_latest" = "0\.9\.0"/gu)).toHaveLength(3);
expect(releaseGuide).toContain('test "$current_next" = "0.11.0"');
expect(releaseGuide).not.toContain("First-publication exception");
expect(releaseGuide).not.toContain('"@opentag/control-protocol"|"@opentag/delivery-contract")');
expect(releaseGuide).not.toContain("no previous `latest` to restore");
expect(releaseGuide.match(/test "\$previous_latest" = "0\.10\.0"/gu)).toHaveLength(3);
expect(
releaseGuide.match(
/awk -F '\\t' '\$1 != "@opentag\/control-protocol" && \$1 != "@opentag\/delivery-contract" \{ print \$2 \}' "\$(?:snapshot_tmp|rollback_file)" \| sort -u\)" = "0\.9\.0"/gu
/test "\$\(cut -f2 "\$(?:snapshot_tmp|rollback_file)" \| sort -u\)" = "0\.10\.0"/gu
)
).toHaveLength(3);
expect(releaseGuide).not.toContain('"0.8.0"');
expect(releaseGuide.match(/test "\$\("\$smoke_root\/node_modules\/\.bin\/opentag" --version\)" = "0\.10\.0"/gu))
expect(releaseGuide).not.toContain('"0.9.0"');
expect(releaseGuide.match(/test "\$\("\$smoke_root\/node_modules\/\.bin\/opentag" --version\)" = "0\.11\.0"/gu))
.toHaveLength(2);
expect(releaseGuide).toContain('smoke_root="$(mktemp -d)"\n(\n set -euo pipefail');
expect(releaseGuide).toContain(
'Also verify every package and its canary tag before promotion:\n\n```bash\n(\n set -euo pipefail'
);
expect(releaseGuide).toContain('git tag -a v0.10.0 "$release_commit" -m "OpenTag v0.10.0"');
expect(releaseGuide).toContain('git tag -a v0.11.0 "$release_commit" -m "OpenTag v0.11.0"');
expect(releaseGuide).toContain(
"if ! gh api --include --silent repos/amplifthq/opentag/git/ref/tags/v0.10.0"
"if ! gh api --include --silent repos/amplifthq/opentag/git/ref/tags/v0.11.0"
);
expect(releaseGuide).toContain("only a confirmed 404 permits tag creation");
expect(releaseGuide).toContain("release_tag_status=\"$(awk 'toupper($1) ~ /^HTTP\\//");
expect(releaseGuide).toContain('case "$release_tag_status" in\n 200)');
expect(releaseGuide).toContain(" 404)\n if git show-ref --verify --quiet refs/tags/v0.10.0; then");
expect(releaseGuide).toContain(" 404)\n if git show-ref --verify --quiet refs/tags/v0.11.0; then");
expect(releaseGuide).toContain("Refusing tag creation after upstream lookup returned HTTP");
expect(releaseGuide).toContain("git show-ref --verify --quiet refs/tags/v0.10.0");
expect(releaseGuide).toContain("git cat-file -t refs/tags/v0.10.0");
expect(releaseGuide).toContain('git rev-parse \'v0.10.0^{}\'');
expect(releaseGuide).toContain("git push origin refs/tags/v0.10.0");
expect(releaseGuide).toContain("git show-ref --verify --quiet refs/tags/v0.11.0");
expect(releaseGuide).toContain("git cat-file -t refs/tags/v0.11.0");
expect(releaseGuide).toContain('git rev-parse \'v0.11.0^{}\'');
expect(releaseGuide).toContain("git push origin refs/tags/v0.11.0");
expect(releaseGuide).toContain('git/tags/$release_tag_object');
expect(releaseGuide).toContain("gh api --paginate 'repos/amplifthq/opentag/releases?per_page=100'");
expect(releaseGuide).toContain('case "$existing_release_state" in');
expect(releaseGuide).toContain("$'v0.10.0\\tfalse\\tfalse\\ttrue'");
expect(releaseGuide).toContain("$'v0.11.0\\tfalse\\tfalse\\ttrue'");
expect(releaseGuide).toContain('release_notes_file="$(mktemp)"');
expect(releaseGuide).toContain('test -s "$release_notes_file"');
expect(releaseGuide).toContain("trap 'rm -f -- \"$release_notes_file\"");
expect(releaseGuide).toContain('--notes-file "$release_notes_file"');
expect(releaseGuide).not.toContain("/tmp/opentag-v0.11.0-release-notes.md");
expect(releaseGuide).toContain("'.draft'");
expect(releaseGuide).toContain("'.prerelease'");
expect(releaseGuide).toContain('.published_at | select(type == "string" and length > 0)');
Expand Down Expand Up @@ -145,12 +154,13 @@ describe("platform setup docs contract", () => {
const controlPlane = repoFile("skills/opentag/references/control-plane.md");
const completion = repoFile("skills/opentag/references/completion-governance.md");
expect(skillDocs).not.toContain("@opentag/cli@latest");
expect(skillDocs).not.toContain("@opentag/cli@0.10.0");
expect(skillDocs).not.toMatch(/\bnpx(?: --yes)? @opentag\/cli(?:\s|$)/u);
expect(skillDocs).not.toMatch(/\bnpm install -g @opentag\/cli(?:\s|$)/u);
expect(skill).toContain("npm install -g @opentag/cli@0.10.0");
expect(skill).toContain("npx @opentag/cli@0.10.0 setup");
expect(skill).toContain("npm install -g @opentag/cli@0.11.0");
expect(skill).toContain("npx @opentag/cli@0.11.0 setup");
expect(skill).toContain("For a global install, verify with `opentag --version`");
expect(skill).toContain("For the no-global path, verify with `npx @opentag/cli@0.10.0 --version`");
expect(skill).toContain("For the no-global path, verify with `npx @opentag/cli@0.11.0 --version`");
expect(skill).toContain("request_user_input");
expect(skill).toContain("askhuman");
expect(skill).toContain("Codex Plan mode");
Expand Down Expand Up @@ -182,7 +192,7 @@ describe("platform setup docs contract", () => {
'HTTPS_PROXY="<proxy-url>" HTTP_PROXY="<proxy-url>" npm view @opentag/cli version --fetch-timeout=15000'
);
expect(skill).toContain("Only after npm registry metadata is reachable");
expect(skill).toContain("npx --yes @opentag/cli@0.10.0 --help");
expect(skill).toContain("npx --yes @opentag/cli@0.11.0 --help");
expect(skill).toContain("do not permanently change `npm config` without explicit user confirmation");
expect(skill).toContain("Only use a proxy URL the user provides or that is already active in the environment");
expect(skill).toContain("npm cache metadata exists");
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/client",
"version": "0.10.0",
"version": "0.11.0",
"description": "HTTP client SDK for creating, claiming, and updating OpenTag dispatcher runs.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/control-protocol/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/control-protocol",
"version": "0.10.0",
"version": "0.11.0",
"description": "Canonical OpenTag Control V1 schemas, types, and digest helpers.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/core",
"version": "0.10.0",
"version": "0.11.0",
"description": "Core OpenTag protocol schemas, types, JSON Schema, and mention parsing.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/delivery-contract/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/delivery-contract",
"version": "0.10.0",
"version": "0.11.0",
"description": "Canonical OpenTag side-effect intent and delivery observation contracts.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/discord/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/discord",
"version": "0.10.0",
"version": "0.11.0",
"description": "Discord interactions normalization and callback rendering for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/dispatcher/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/dispatcher",
"version": "0.10.0",
"version": "0.11.0",
"description": "Embeddable OpenTag dispatcher Hono app and durable delivery coordination.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/github/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/github",
"version": "0.10.0",
"version": "0.11.0",
"description": "GitHub event normalization and callback rendering for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/gitlab/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/gitlab",
"version": "0.10.0",
"version": "0.11.0",
"description": "GitLab webhook normalization and callback rendering for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/governance/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/governance",
"version": "0.10.0",
"version": "0.11.0",
"description": "Deterministic execution governance for OpenTag work loops.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/lark/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/lark",
"version": "0.10.0",
"version": "0.11.0",
"description": "Lark/Feishu message normalization and callback helpers for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/linear/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/linear",
"version": "0.10.0",
"version": "0.11.0",
"description": "Linear webhook normalization, callback rendering, and issue mutation helpers for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/local-runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/local-runtime",
"version": "0.10.0",
"version": "0.11.0",
"description": "Local OpenTag dispatcher, daemon, and diagnostics runtime helpers.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/runner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/runner",
"version": "0.10.0",
"version": "0.11.0",
"description": "Executor contracts and built-in runner adapters for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/slack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/slack",
"version": "0.10.0",
"version": "0.11.0",
"description": "Slack app mention normalization and callback helpers for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/store/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/store",
"version": "0.10.0",
"version": "0.11.0",
"description": "SQLite and Drizzle persistence primitives for OpenTag runs and leases.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/teams/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/teams",
"version": "0.10.0",
"version": "0.11.0",
"description": "Microsoft Teams activity normalization and callback rendering for OpenTag.",
"type": "module",
"engines": { "node": ">=20" },
Expand Down
2 changes: 1 addition & 1 deletion packages/telegram/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentag/telegram",
"version": "0.10.0",
"version": "0.11.0",
"description": "Telegram message normalization and callback helpers for OpenTag.",
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion scripts/dev/run-slack-linear-backlog-live-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ async function waitForSlackReply(input: {

async function main(): Promise<void> {
const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const expectedVersion = optionalEnv("OPENTAG_SLACK_LINEAR_EXPECTED_CLI_VERSION") ?? "0.10.0";
const expectedVersion = optionalEnv("OPENTAG_SLACK_LINEAR_EXPECTED_CLI_VERSION") ?? "0.11.0";
const configSourcePath = resolve(requiredEnv("OPENTAG_CONFIG_PATH"));
const botToken = requiredEnv("OPENTAG_SLACK_BOT_TOKEN");
const appToken = requiredEnv("OPENTAG_SLACK_APP_TOKEN");
Expand Down
Loading