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
4 changes: 2 additions & 2 deletions .claude/skills/pm-dispatch/references/platform-readings.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,9 +385,9 @@
- 强制档不因临缺授权降档:临缺非退役,退役唯维护者裁;不达档起隔离达档子代理或等档。
- 本车道强制多是过宽的回忆:`dispatch-gates.mjs --tier PATH` 逐路径现推,路径线是下限非放行。
- 该脚本只答自己那棵树:姊妹仓路径回 absent from this tree,姊妹仓的档位与条款②只能手推。
- required checks 的名单是每仓事实,objectstack 九个:
- required checks 的名单是每仓事实,objectstack 十个:
`TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` · `Build Core` · `Lint UI` ·
`Governed Surface Queue Guard` · `Type Check UI` · `Test UI` · `Build & E2E UI`。
`Governed Surface Queue Guard` · `Type Check UI` · `Test UI` · `Build & E2E UI` · `Live E2E UI`。
- ⭐ 本表的 `mustName` 声明 `statesTheSet` ⇒ 必须逐字等于注册表,加注册行不跟改本表即红。
- ⇒ 门绿即名单逐字齐全的读数;⛔ 但数量词未钉:实测 七→六 照绿,只能手跟改。
- ⛔ 聚合命令同样不作判定:`check:type-check-debt` 可在包级 typecheck 绿时红。
Expand Down
184 changes: 182 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ jobs:
# filter below and the `test` job's `if:` (#9829, #10015).
crosspkg: ${{ github.event_name != 'merge_group' && steps.changes.outputs.crosspkg || 'true' }}
# The objectui tree under `ui/` (objectstack#25677). Three outputs, read
# together by the four `* UI` jobs at the bottom of this file:
# together by the five `* UI` jobs at the bottom of this file (Type Check
# UI, Test UI, Lint UI, Build & E2E UI and Live E2E UI):
# ui — a path under ui/, a root file the ui jobs build from
# (the lockfile, the workspace, turbo.json), or a root
# the ui gates read (every root workflow, the prompts,
Expand All @@ -175,7 +176,7 @@ jobs:
# On merge_group `ui` and `ui_consumers` come from the queue diff
# (objectstack#25867): an entry whose merged diff against
# `merge_group.base_sha` touches no ui path and none of the seven
# packages skips the four ui jobs, exactly as the same diff does on its
# packages skips the five ui jobs, exactly as the same diff does on its
# PR head; an entry that touches one runs them. If the step fails (the
# base cannot be fetched, the diff errors), it is `continue-on-error`
# there, its outputs are empty and `|| 'true'` runs every ui job.
Expand Down Expand Up @@ -4448,6 +4449,185 @@ jobs:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

# ── Live E2E UI: the Console against a real backend (#27154) ─────────────
# Row 17 of the objectstack#25697 workflow roster, objectui's `live-e2e.yml`,
# "kept, follow-up port": it has run nowhere since #26058 deleted
# `ui/.github/workflows/`. Ruling 6435339772 Q1 A made it required; ruling
# 6464191239 letter D re-based it on the WORKSPACE backend, so it runs the
# HEAD Console against the HEAD backend and no published version is pinned
# anywhere (`ui/e2e/live/ci/backend.env` says why the pin retired).
#
# What it runs: the four allowlisted live specs, `test:e2e:live:ci`, with
# `--retries 0` (the config's own value, stated again on the command line),
# against `objectstack dev --seed-admin --fresh` serving `examples/app-showcase`
# from this tree's own built CLI (`ui/e2e/live/ci/start-backend.sh`) and the
# Console's production build served by `vite preview`, whose `/api` proxy
# (the Console's `server.proxy`, which preview inherits) points at that
# backend through DEV_PROXY_TARGET. ⛔ No retry wrapper and no
# `continue-on-error` anywhere in this job: a red run is a red check.
#
# Gated as Build & E2E UI is. Not behind Build UI Deps: this job builds a
# larger closure (the CLI and the showcase app's) than the seven packages that
# job hands on, so it restores Build Core's turbo seed instead.
#
# Ceiling: there is no CI population yet, so it is derived from one cold
# local run on 4 shared cores (#27154, 2026-10-28), by the rule above Lint UI:
# install 60 s + closure build 384 s + Console build 119 s + backend boot
# 19 s + specs 26 s + browser provisioning and setup ~120 s ≈ 730 s
# (12.2 min) => max(36.5, 27.2) => 40. Re-derive from the first population.
live-e2e-ui:
name: Live E2E UI
needs: filter
if: ${{ !cancelled() && needs.filter.outputs.ui_tree != 'false' && (needs.filter.outputs.ui != 'false' || needs.filter.outputs.ui_consumers != 'false') }}
runs-on: ubuntu-latest
timeout-minutes: 40
permissions:
contents: read
env:
# The Console's build OOMs at Node's default heap (see Build & E2E UI).
NODE_OPTIONS: --max-old-space-size=4096
# LIVE_BACKEND_DIR is set on the two steps that run the scripts: the
# `runner` context is not available in a job-level `env:`, and a
# workflow that names it there does not validate at all.
LIVE_BACKEND_PORT: '4010'
LIVE_API_URL: http://localhost:4010
LIVE_APP_URL: http://localhost:5190

steps:
- name: Checkout repository
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: .nvmrc

- name: Setup pnpm
uses: ./.github/actions/setup-pnpm

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

# Restore only, here and below: Build UI Deps saves the store and Build
# Core the turbo seed.
- name: Restore pnpm cache
uses: actions/cache/restore@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Restore Turbo cache
uses: actions/cache/restore@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-build-core-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-build-core-${{ github.ref_name }}-
${{ runner.os }}-turbo-build-core-

- name: Install dependencies
run: pnpm install --frozen-lockfile

# The backend's closure (the CLI and the showcase app, the `pkg...` form
# Dogfood Verify CLI uses) plus the seven packages the Console builds
# against, the same filters as Build & E2E UI's build step.
- name: Build the workspace backend and the @objectstack packages objectui consumes
run: |
pnpm exec turbo run build --concurrency=4 \
--filter=@objectstack/cli... \
--filter=@objectstack/example-showcase... \
--filter=@objectstack/spec... \
--filter=@objectstack/client... \
--filter=@objectstack/core... \
--filter=@objectstack/formula... \
--filter=@objectstack/lint... \
--filter=@objectstack/sdui-parser... \
--filter=@objectstack/types...

# Served at the root, where the live specs navigate.
- name: Build the Console for the live lane
env:
VITE_BASE_PATH: /
run: pnpm --dir ui/apps/console exec vite build

- name: Get the Playwright version
id: playwright-version
run: |
set -eo pipefail
if ! version=$(pnpm --dir ui list @playwright/test --depth=0 --json | jq -r '.[0].devDependencies["@playwright/test"].version'); then
echo "::error::Reading the @playwright/test version failed (pnpm list --json | jq). Refusing to write a Playwright browser cache key from it."
exit 1
fi
if [ -z "$version" ] || [ "$version" = "null" ]; then
echo "::error::Could not resolve the @playwright/test version (got: '${version}'). Refusing to write an empty or null version into the Playwright browser cache key."
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"

# Build & E2E UI saves this key; this job only reads it.
- name: Restore Playwright browsers
id: playwright-cache
uses: actions/cache/restore@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm --dir ui exec playwright install chromium

- name: Ensure Chromium can launch
working-directory: ui/scripts
run: bash ensure-chromium-ready.sh

- name: Boot the workspace backend
env:
LIVE_BACKEND_DIR: ${{ runner.temp }}/live-backend
run: bash ui/e2e/live/ci/start-backend.sh

# In the background for the rest of the job; the runner reaps it at job
# end. Ready = the Console's index answers through the preview server.
- name: Serve the Console with vite preview
env:
DEV_PROXY_TARGET: http://localhost:4010
run: |
cd ui/apps/console
nohup pnpm exec vite preview --port 5190 --strictPort > "$RUNNER_TEMP/vite-preview.log" 2>&1 &
deadline=$((SECONDS + 60))
until curl -sf -o /dev/null http://localhost:5190/; do
if [ "$SECONDS" -ge "$deadline" ]; then
echo "::error::vite preview did not answer on :5190 within 60 s."
cat "$RUNNER_TEMP/vite-preview.log"
exit 1
fi
sleep 1
done
echo "vite preview answers on :5190, proxying /api to :4010."

- name: Run the live e2e specs
run: pnpm --dir ui run test:e2e:live:ci --retries 0

- name: Stop the workspace backend
if: ${{ always() }}
env:
LIVE_BACKEND_DIR: ${{ runner.temp }}/live-backend
run: bash ui/e2e/live/ci/stop-backend.sh

- name: Upload failure artefacts
if: ${{ !cancelled() && failure() }}
uses: actions/upload-artifact@v7
with:
name: ui-live-e2e-failure-artifacts
path: |
ui/test-results/
${{ runner.temp }}/live-backend/backend.log
${{ runner.temp }}/vite-preview.log
retention-days: 14

# ── UI Published Artifacts: two objectui gates over a full build (#26717) ─
# Rows 23 and 21 of the objectstack#25697 workflow roster, "kept, follow-up
# port" (objectui's published-dist-gate.yml and node-esm-load-gate.yml), both
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,11 +503,11 @@ Even inside your own worktree, operate defensively:
yet"; `in_progress` is not a pass, and arming a red PR hides it
([why](docs/agents/multi-agent-discipline.md#arming-a-red-pr-hides-it)). Always read *two* things:
the queue branch **and** `origin/main`. And **the queue enforces only the required
set** — nine contexts block: `Lint & Repo Gates` (all `check:*` gates), `TypeScript Type Check`,
`Test Core`, `Build Core`, `Governed Surface Queue Guard`, `Lint UI`, `Type Check UI`, `Test UI`
and `Build & E2E UI`; a check outside those nine is advisory
set** — ten contexts block: `Lint & Repo Gates` (all `check:*` gates), `TypeScript Type Check`,
`Test Core`, `Build Core`, `Governed Surface Queue Guard`, `Lint UI`, `Type Check UI`, `Test UI`,
`Build & E2E UI` and `Live E2E UI`; a check outside those ten is advisory
([why that matters](docs/agents/multi-agent-discipline.md#advisory-checks-ride-through)). A required context is
matched by check-run name, so a rename detaches its gate silently — treat those nine names as contract.
matched by check-run name, so a rename detaches its gate silently — treat those ten names as contract.
**Two speeds** — PR and queue runs carry the fast set; `main`'s scheduled and push runs carry the full
battery ([detail](docs/agents/multi-agent-discipline.md#two-speeds-which-runs-carry-which-battery)).
**Main-red:** a full-battery red is its lane's first work — triage files it under
Expand Down
2 changes: 1 addition & 1 deletion docs/agents/multi-agent-discipline.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Arming a red PR does not queue it, it hides it:

← [`AGENTS.md` › Multi-agent working discipline](../../AGENTS.md#multi-agent-working-discipline)

A check outside those nine is advisory and rides through, and an advisory red
A check outside those ten is advisory and rides through, and an advisory red
that lands rides `main`'s merge ref into every later PR until stanched.

## Two speeds: which runs carry which battery
Expand Down
21 changes: 17 additions & 4 deletions scripts/check-required-contexts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,16 @@ export const REQUIRED_CONTEXTS = [
'after the ui flake reading 6436579765 (0/50); the ruleset entry is the maintainer\'s, in the same sitting as the merge (#9325)',
carries: 'the sharded `ui/` vitest matrix, `Test UI (k/16)`, read through the aggregate job',
},
{
workflow: 'ci.yml',
job: 'live-e2e-ui',
context: 'Live E2E UI',
authorized:
'#25914 ruling 6435339772 Q1 A (the required check), re-based by ruling 6464191239 letter D on the workspace backend, ' +
'maintainer 「其他同意」 2026-10-28T08:22Z — this row is the repo half (#27154); the ruleset entry is the maintainer\'s, ' +
'in the same sitting as the merge (#9325)',
carries: 'the Console against a real workspace backend: the allowlisted `ui/e2e/live` specs',
},
];

/**
Expand Down Expand Up @@ -595,9 +605,10 @@ export const INSTRUCTION_SURFACES = [
// the judge. Then 7→5 by #22998 (two-speed CI: the dogfood and temporal
// contexts moved to the full battery). Then 5→7 by #25853 (the `ui/`
// import's `Lint UI` and `Build & E2E UI`), 7→8 with `Type Check UI`
// once bill item 49 closed (#25856), and 8→9 with the `Test UI` aggregate
// (#26460). The full set is pinned here so the corrected sentence cannot
// rot back — a rename in ANY of the nine reddens this gate instead.
// once bill item 49 closed (#25856), 8→9 with the `Test UI` aggregate
// (#26460), and 9→10 with `Live E2E UI` (#27154). The full set is pinned
// here so the corrected sentence cannot rot back — a rename in ANY of the
// ten reddens this gate instead.
file: 'AGENTS.md',
statesTheSet: true,
mustName: [
Expand All @@ -610,6 +621,7 @@ export const INSTRUCTION_SURFACES = [
'Build & E2E UI',
'Type Check UI',
'Test UI',
'Live E2E UI',
],
},
{
Expand Down Expand Up @@ -642,6 +654,7 @@ export const INSTRUCTION_SURFACES = [
'Build & E2E UI',
'Type Check UI',
'Test UI',
'Live E2E UI',
],
},
{
Expand All @@ -663,7 +676,7 @@ export const INSTRUCTION_SURFACES = [
* of the six surfaces have one; `AGENTS.md` does not, because a repo-root FILE
* has no separator to be found by — so an AGENTS.md card derived this gate not
* at all, while that surface is the one carrying `mustName` for every
* required context (widened 2→6 by #9677, 6→7 by #17798, 7→5 by #22998, 5→7 by #25853, 7→8, 8→9 by #26460). Editing the merge-queue
* required context (widened 2→6 by #9677, 6→7 by #17798, 7→5 by #22998, 5→7 by #25853, 7→8, 8→9 by #26460, 9→10 by #27154). Editing the merge-queue
* paragraph there is precisely how this gate goes red, and it was reachable
* only by judgment.
*
Expand Down
68 changes: 15 additions & 53 deletions ui/e2e/live/ci/backend.env
Original file line number Diff line number Diff line change
@@ -1,55 +1,17 @@
# Pins for the CI live-e2e backend (see start-backend.sh and
# .github/workflows/live-e2e.yml).
# What the live-e2e backend serves (see start-backend.sh and the `Live E2E UI`
# job in .github/workflows/ci.yml).
#
# OBJECTSTACK_VERSION — the published @objectstack/* npm version the backend
# runs on. Until objectui moved into the objectstack repository it had to
# match the @objectstack/spec version this repo's pnpm-lock.yaml resolved,
# and objectui's CI-guide pin test (deleted by objectstack#26058) required the two equal
# (objectui#7689).
# Since the import (objectstack#25803) the console builds against the
# workspace's own @objectstack/spec, a lockfile link with no version, so
# that comparison is retired and this value is the published backend the
# lane runs, nothing more.
# LIVE_BACKEND_APP — the app directory, relative to the repository root, that
# start-backend.sh boots with this tree's own built `@objectstack/cli`. The
# live specs target the showcase's objects and seeded records.
#
# This file declares exactly ONE @objectstack pin, and that is the whole
# design: the commit is derived from it, see below.
OBJECTSTACK_VERSION=18.2.0

# There is deliberately no OBJECTSTACK_REF key here. The objectstack-ai/objectstack
# commit the showcase app metadata is sparse-checked-out from is DERIVED at
# boot by start-backend.sh, from the release tag
# `@objectstack/cli@${OBJECTSTACK_VERSION}` — `git ls-remote --tags`, taking
# the peeled `^{}` sha of the annotated tag — and the script refuses to start
# when that tag does not resolve to a 40-character sha. The app source and the
# published packages it runs on therefore come from the same release BY
# CONSTRUCTION; there is no second value to move, so the two cannot disagree.
# (Mirror image of the framework repo's `.objectui-sha` console pin.)
#
# It used to be a hand-moved sha here, carrying a stated MUST — "always the
# commit the release tag points at" — that nothing could check, on the
# reasoning that resolving the tag needs the objectstack repository and the
# unit lane cannot reach it. True of the unit lane, and beside the point: the
# live lane clones that very repository at boot to get the app, so the lane
# that CONSUMES the value could always resolve it. objectui#7964 retired the
# pin rather than add a check inside an `informational` lane nobody reads.
#
# ⚠️ One consequence, stated rather than papered over. The workflow caches the
# prepared fixture under `live-backend-<os>-<hashFiles(backend.env)>`, and the
# resolved sha is no longer in this file. A version bump still moves the key,
# because the version is what the sha is derived from. But if the
# `@objectstack/cli@${OBJECTSTACK_VERSION}` tag were ever re-pointed at another
# commit after a fixture had been cached, that stale fixture would still be
# served until OBJECTSTACK_VERSION itself moved. start-backend.sh's own stamp
# carries the RESOLVED sha, so a re-point is caught in a reused scratch dir;
# the Actions cache is the one path it is not caught on. Published release tags
# are not expected to move, and moving one is the anomaly, not this lane.

# There is no `better-auth` pin here any more. BETTER_AUTH_VERSION and
# e2e/live/ci/better-auth-pin.mjs held the family at 1.7.2 while the published
# @objectstack/plugin-auth imported an export 1.7.3 removed (objectstack#16186,
# objectui#8084). They were retired together in the 17.5.0 bump, as their own
# text prescribed (objectui#11073, maintainer ruling on objectui#11111):
# plugin-auth 17.5.0 declares the whole family at exactly 1.7.3 and no longer
# imports that export, so the published artifact's own manifest is the pin.
# Holding 1.7.2 on it made the lane test a pair nobody ships, and the seeded
# sign-in never answered.
# There is no published-version pin here any more (objectstack#27154, ruling
# 6464191239 letter D step 2). Until then this file declared
# OBJECTSTACK_VERSION, the published `@objectstack/*` release the backend
# ran, and start-backend.sh derived the showcase commit from that release's
# tag. A HEAD console against a published backend goes red each time the
# console passes the last release (#25914: 0/5 runs green on 18.1.0 against
# the 18.2.0 tree), so a required lane on a pin is a lockstep bump on every
# release. The lane now runs HEAD against HEAD, and the pin, the tag resolve
# and the npm install retired together.
LIVE_BACKEND_APP=examples/app-showcase
Loading
Loading