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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
154 changes: 154 additions & 0 deletions .github/workflows/pack-smoke-optin.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.

name: Pack Smoke (opt-in)

on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7

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

- 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

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-

- name: Setup turbo cache
uses: actions/cache@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-

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

# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
- name: Build
run: pnpm run build

# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,9 @@ Provide both English and Chinese versions:
- [ ] Naming conventions are followed
- [ ] JSDoc comments are complete
- [ ] No unrelated changes included
- [ ] If the PR changes the auth/audience **defaults** or the **accept/reject behaviour** of
the unauthenticated surface, label it `needs:pack-smoke` — that runs the packed-install
smoke on the merge preview before you merge, instead of finding out at release time.

### PR Checklist

Expand Down
28 changes: 28 additions & 0 deletions scripts/pm/ensure-pm-labels.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -282,6 +282,34 @@ done
# a script would be issuing the review verdict, which is 自查放行.
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true

# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
# Its named consumer is a GATE rather than a query: the job guard in
# `.github/workflows/pack-smoke-optin.yml`, which runs the packed-install smoke
# on the PR's merge preview only while this label is on the PR.
#
# ⚠️ It is the first label in this file that is NOT part of the PM state
# machine, and the difference matters to anyone extending it. Every pm:* row
# above is a state a PM seat writes; this one is written by the PR AUTHOR about
# their own diff — the ruling's word is 自声明 — and it names a property of the
# CHANGE, not a position on the board. So it is one-of with nothing, it blocks
# no dispatch, and no sweep in scripts/pm/ reads it. It is declared here anyway
# because this file is the repo's only declared home for a label OBJECT, and an
# undeclared label is the grey / empty-description drift the header describes:
# auto-created by its first application and unrepairable by any rerun.
#
# ⛔ Never auto-applied. Nothing in .github/labeler.yml may grow a rule for it
# (the workflow header argues why: path-derived application is a different
# design from the one that was ruled, and it puts the ~45-minute cost back into
# the default inner loop the ruling kept it out of). Like every other row here,
# this file only ensures the OBJECT — it never hangs the label on a PR.
#
# Main repo only, for the ordinary reason: the workflow that consumes it lives
# here. Colour 006b75 is deliberately outside every family above — this is not a
# lane, not a state, and not a routing seam, and borrowing one of their colours
# would assert a kinship the paragraph above spends its length denying.
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true

# Routing labels exist only on the main backlog repo, and mark SEAM cards only
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
#
Expand Down
Loading