Repository navigation
ci(release): 让发布完整性守卫真的能触发,并把镜像也纳入它守的不变量 (#4900) - #4901
Merged
Merged
Conversation
…t guard the image too (#4900) Two defects in the guard #4899 added, both exposed by the very next release (89d2a4e). npm published all of 17.0.0-rc.2 and pushed its tags, then creating the @objectstack/spec GitHub Release failed on the API's 125k body limit — the spec changelog section for that version is 342,911 characters (#4900). The step went red, `published` stayed false, and the runtime image was silently lost. 1. The guard could not fire. GitHub wraps an `if:` naming no status function in an implicit success(), so `if: steps.changesets.outputs.published != 'true'` was really `success() && …` — skipped for any changesets-step failure, which is one of the two cases it exists for. Now `!cancelled() && …`. 2. Its contract was wrong. "Publish whatever is missing" does not cover a release that published and then died before reporting it: rc.2 was already on npm, so a publish-only step would have no-opped and lost the image anyway. It now guards the invariant the release actually owes — this repo's version must be on npm AND must have a matching runtime image — and reports the second half through the job outputs so `docker` builds it. `docker` gains the same `!cancelled()` treatment. A dependent job under the default implicit success() is skipped for any upstream failure, so the image was lost to a fault that happened after the packages were already public. The `published` output is the real gate; the release job's exit status is not, and the run stays red either way because the GitHub Release genuinely failed. A failed ghcr probe counts as MISSING on purpose: a redundant rebuild costs a few minutes, a wrongly-skipped one leaves a published npm version with no image and nothing to say so. Verified against stubbed npm/pnpm/git/curl — npm present + image present (full no-op, no outputs), npm present + image missing (requests docker, does NOT republish), npm missing (publishes, then requests docker), and ghcr unreachable (treated as missing). The ghcr probe itself was checked against the live registry: it resolves an anonymous pull token and correctly reports 17.0.0-rc.1 present and 17.0.0-rc.2 absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BbNVKv6KgPzuQ5p76nMgnf
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 3, 2026 15:07
This was referenced Aug 4, 2026
os-zhuang
pushed a commit
that referenced
this pull request
Aug 4, 2026
… nothing Empty frontmatter — the repo's sanctioned "this PR releases nothing" declaration, on par with the skip-changeset label (both are named in the Check Changeset gate). The PR changes only .github/workflows/, root scripts/ and one check: entry in the root (private) package.json, so nothing reaches a published package; a non-empty changeset would bump all 69 packages of the fixed group in lockstep and burn an extra rc for no shipped product code. The body records the one caveat that matters here: an empty changeset is the exact input #4898 showed can jam a release, which is now bounded rather than silent by the recovery step (#4899, made reachable by #4901) — and this PR is what extends the GitHub Releases and the ADR-0087 D4 spec-changes.json attachment onto that recovery path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
os-zhuang
pushed a commit
that referenced
this pull request
Aug 4, 2026
…releases nothing" This reverts 1f50271, keeping the `skip-changeset` label as this PR's only "releases nothing" declaration. The empty changeset was redundant with the label — Check Changeset exempts a labelled PR at the job level, and the earlier red run predated the label (the PR was created at 15:54:54, the label applied at ~15:56, so that run's event payload carried no labels at all). Any subsequent synchronize event re-evaluates the job `if:` against current labels. Redundancy is not free when the redundant copy is a known-dangerous shape. An empty changeset is exactly the input #4898 showed can jam a release: changesets/action reaches its publish branch only with ZERO pending changesets, and an empty one still counts as pending. The argument that this is now bounded rests on the recovery step (#4899/#4901) — which is a path THIS PR modifies. A PR whose whole purpose is repairing the release machinery should not plant a known-hazardous input and then lean on the very mechanism it is changing to catch it. One declaration, via the label, and no hazard. The gate text that recommends an empty changeset as the way out is tracked separately as #5292. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 6, 2026
…bjectstack-ai#4900) (objectstack-ai#5290) * ci(release): build GitHub Releases ourselves, with bodies that fit the 125k limit (objectstack-ai#4900) changesets/action's `createGithubReleases` posts each package's raw CHANGELOG section as the Release body. @objectstack/spec's section for a single v17 RC is 342,893 characters against the API's 125,000 limit, so the POST 422'd — inside runPublish, i.e. after `changeset publish` had fully succeeded but BEFORE the action set its `published` output. The step went red, `published` stayed false, and the docker job gated on it was skipped: a published npm version with no runtime image. The section only grows, so this failed identically every release in the window. Measured against the live API: @objectstack/spec has NO Release for 17.0.0-rc.0, rc.1 or rc.2 (all 404 by tag), while 16.0.0 and 16.1.0 — 62,886 and 1,523 characters — have theirs, each carrying the ADR-0087 D4 spec-changes.json asset. That asset uploads ONTO the spec Release, so D4 has been silently unmounted for the whole v17 RC window too, not just the Release. `createGithubReleases: false`, and scripts/release-github-releases.mjs does the job instead. It is faithful to what the action produced — same tag, name, prerelease rule, and a direct port of the action's own getChangelogEntry for the body, which reproduces the real @objectstack/cli@17.0.0-rc.2 release body byte for byte (73,993 chars) — plus the three properties it lacked: - Bounded. An over-limit body is cut on a line boundary, any code fence the cut opened is closed so the notice renders as markdown rather than inside a code block, no surrogate pair is split, and both ends carry a link to the complete entry in CHANGELOG.md at the release commit. Cost is measured in UTF-16 code units, which is >= the code-point count for every string, so it can only over-estimate against whichever definition of "character" the API applies (the failing section is 342,893 characters but 359,636 UTF-8 bytes; the API quoted the former). - Idempotent. Looks the release up by tag and PATCHes when it exists, POSTs when it does not. rc.2 left ~69 of 70 releases created, so recovering over a partial set is the normal case, not the exception. - Isolated per package. The action ran the set through one Promise.all, so the first rejection abandoned the rest. This runs sequentially, collects failures and still exits non-zero, so one bad changelog can no longer cost @objectstack/spec its Release — and D4 its mount point. Turning createGithubReleases off also disables the action's per-tag `git push`, which lives in the same block. That is a bonus: scripts/release-publish.sh already pushes every tag in one atomic `git push origin --tags` precisely because those concurrent per-tag pushes raced GitHub's ref backend (objectstack-ai#2191). Both publish paths are covered. The recovery step (objectstack-ai#4901) now reports `npm-published` separately from `published` — the former means "packages went out and owe Releases", the latter "the docker job must build" — and emits its version unconditionally, since an npm repair whose image happens to exist still owes its Releases. release-spec-changes.sh takes that version as a fallback, so D4 mounts on the recovery path as well, which it never could before. `pnpm check:release-body` runs the script's --self-test in lint.yml: 49 assertions over the real code path, fed the REAL oversized section out of packages/spec/CHANGELOG.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t * chore(changeset): declare that objectstack-ai#4900's release-machinery fix releases nothing Empty frontmatter — the repo's sanctioned "this PR releases nothing" declaration, on par with the skip-changeset label (both are named in the Check Changeset gate). The PR changes only .github/workflows/, root scripts/ and one check: entry in the root (private) package.json, so nothing reaches a published package; a non-empty changeset would bump all 69 packages of the fixed group in lockstep and burn an extra rc for no shipped product code. The body records the one caveat that matters here: an empty changeset is the exact input objectstack-ai#4898 showed can jam a release, which is now bounded rather than silent by the recovery step (objectstack-ai#4899, made reachable by objectstack-ai#4901) — and this PR is what extends the GitHub Releases and the ADR-0087 D4 spec-changes.json attachment onto that recovery path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t * Revert "chore(changeset): declare that objectstack-ai#4900's release-machinery fix releases nothing" This reverts 1f50271, keeping the `skip-changeset` label as this PR's only "releases nothing" declaration. The empty changeset was redundant with the label — Check Changeset exempts a labelled PR at the job level, and the earlier red run predated the label (the PR was created at 15:54:54, the label applied at ~15:56, so that run's event payload carried no labels at all). Any subsequent synchronize event re-evaluates the job `if:` against current labels. Redundancy is not free when the redundant copy is a known-dangerous shape. An empty changeset is exactly the input objectstack-ai#4898 showed can jam a release: changesets/action reaches its publish branch only with ZERO pending changesets, and an empty one still counts as pending. The argument that this is now bounded rests on the recovery step (objectstack-ai#4899/objectstack-ai#4901) — which is a path THIS PR modifies. A PR whose whole purpose is repairing the release machinery should not plant a known-hazardous input and then lean on the very mechanism it is changing to catch it. One declaration, via the label, and no hazard. The gate text that recommends an empty changeset as the way out is tracked separately as objectstack-ai#5292. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t --------- Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Aug 17, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 23, 2026
…ct-blocks tier (objectstack-ai#9392) (objectstack-ai#9728) Catch-up with the registry inputs objectui#4648/objectstack-ai#4901 published on the object-form registration: 14 keys are declared on the react-blocks ObjectForm overlay (descriptions adapted from objectui's registration), and 3 are baselined with recorded reasons per the maintainer's 2026-08-18 mixed-disposition ruling — initialData (alias of initialValues), mobile (internal override), navigateOnSuccess (parked pending the action-success- navigation family; revisit tracked on objectstack-ai#9392). The parity baseline is regenerated via --update, which also snapshots the six SDUI object-* blocks (objectstack-ai#7751) as additive coverage; the clean-baseline test now derives that tail from the committed file. Claude-Session: https://claude.ai/code/session_016D9wdJR14KKCxz1WgdAzcw Co-authored-by: Claude <noreply@anthropic.com>
veigajoao
pushed a commit
to veigajoao/objectstack
that referenced
this pull request
Sep 29, 2026
…mes a known repository, so pre-#N / post-#N are judged and framework#N is this repository (objectstack-ai#20554) Fixes objectstack-ai#20330 Clause-②: no ## What changed `scripts/check-issue-citations.mjs` read ANY `word#N` as a repository reference, so `pre-objectstack-ai#12248`, `post-objectstack-ai#6640`, `Pre-#N`, `POST-#N` and `Framework#N` were classed cross-repo and never judged. Its qualifier is now a **closed set**. A token joined to `#N` names a repository only when it is an `owner/repo` form or a name in the new `KNOWN_REPOSITORIES` table, matched case-insensitively. Any other prefix is prose, and the number after it is this repository's and is judged. One recogniser, `repositoryOf`, is asked in all three places: by extraction, by the board's probe set (`boardWanted`) and by the classifier (`namesThisRepository`). The three can no longer disagree about a token. - `KNOWN_REPOSITORIES` rows: `objectstack` and `framework` (both THIS repository), `objectui`, `ui` (an objectui alias, joined form only), `cloud`, `hotcrm`, `hotcrm-heimao`, `os-tianshun-mtc` and `os-project-titanwind-ehr`. Each row carries the measurement that put it in. - **Prose form.** `objectui PR objectstack-ai#10264`, `objectui objectstack-ai#2670`, `cloud objectstack-ai#2937` and `framework objectstack-ai#2679` read as their repository. The form is a known repository name, whitespace, an optional `PR` or `issue`, then `#N`. The alias `ui` is excluded from it, because `UI #N` is ordinary English. - **No carry across a pair.** In `objectui#6110 + objectstack-ai#6111` the second number stays this repository's. The convention is to qualify each number, and the one live site in the claim's surface is respelled: `packages/spec/src/data/field.zod.ts:370` now reads `objectui#6110 + objectui#6111`. This is comment-only, with a `patch` changeset. - **Ordinal heads.** Closing the set exposes ordinals that were hidden behind a fake qualifier. `NON_CITATION_HEADS` gains `OQ` (`ADR-0076 OQ#10`, 10 sites) and `PKCS` (`PKCS#11`, 1 site). A hyphen joining a head to its `#` is now read as the same head (`Prime-Directive-objectstack-ai#12`, 2 sites). `PD#12` (8 sites) was already covered by the existing `pd` head once its candidate is refused. - **Refusal text.** The `REMEDY` text now states the grammar that judged the author. ### A false red in the same seam, fixed because this change would have widened it `buildBoard` probed only UNQUALIFIED numbers, so a diff adding `objectstack#N` was classified against a board that never asked about N. Reproduced on unmodified `288611e3e5`: I appended `objectstack#20330` (this live card) to a swept file and ran `node scripts/check-issue-citations.mjs`. It exited **2**, reading `board: probed (0 citations)` and `[allocated-but-absent] ... objectstack#20330`. Reading `framework#N` as this repository would have inherited that false red on every site. The probe set is now `boardWanted`, meaning every citation judged here. The self-test pins it through `probeBoard` over a stub. The blocking rule is unchanged: findings still exit 2. ## Measurements the design rests on - **`framework` names this repository.** `git ls-remote https://github.com/objectstack-ai/framework` answered HEAD `288611e3e5`, identical to `objectstack-ai/objectstack`. The controls diverged: a nonexistent name under the same owner exited 128, and `objectstack-ai/objectui` answered its own HEAD `0eb9f36aca`. The REST and web routes to `framework` answered 403 from this session's proxy (bound to configured repositories), so git's rename redirect was the readable instrument. `framework#N` / `Framework#N` therefore read as THIS repository. - **Qualifier census on `288611e3e5`, over the declared surfaces.** There were 27 distinct candidates behind 1,655 sites: - This repository: `framework` 255, `objectstack` 111, `objectstack-ai/objectstack` 9, `Framework` 2. - Siblings: `objectui` 672, `cloud` 213, `hotcrm` 24, `objectstack-ai/objectui` 12, `ui` 11, `objectstack-ai/cloud` 9, `better-auth/better-auth` 3, `os-tianshun-mtc` 2, and 1 each of `hotcrm-heimao`, `os-project-titanwind-ehr`, `objectstack-ai/objectos`, `objectstack-ai/ats` and `objectstack-ai/hotcrm`. - Prose: `pre-` 284, `post-` 12, `Pre-` 4, `Post-` 4, `PRE-` 1 and `POST-` 1. - Ordinals: `OQ` 10, `PD` 8, `Prime-Directive-` 2, `PKCS` 1. - **`ui` is objectui.** `objectstack-ai/ui` does not exist, and `ui#6837`, `ui#6206` and `ui#6207` are objectui's records on its board (`objectstack#6206` answers 404, so it would have been a false death). - **Prose form, 27 sites.** For every objectui number I read objectui's board and this repository's. The objectui record is the one each sentence describes: `objectui objectstack-ai#2670` is "Flow designer: render loop / parallel / try_catch as nested", cited from `loop-node.ts`, and `objectui PR objectstack-ai#4264` diagnoses a path on the right side of `==`, cited beside `PATH_SHAPED_LITERAL`. This repository's same number is unrelated on every site. The `cloud` sites could not be read (private) and follow their context. There were zero false positives. - **Pair carry, 48 sites. The measurement refuses a carry rule.** - `,` and `and`: every cross-repo pair I could judge names THIS repository's second number. `cloud#1013 and objectstack-ai#10645` is this repository's cli `serve` issue (4 sites), `cloud#1020, objectstack-ai#5233` its org gate issue (6 sites), `objectui#2561, objectstack-ai#3021` its lazySchema PR, and `objectui#3136 and objectstack-ai#14492` answers 404 on objectui. - `/`: mostly carries, but not always. `objectui#3226 / objectstack-ai#4827` is this repository's objectstack-ai#4827, a conversion entry handed over from objectui and cited from `conversions/registry.ts`. - `+`: exactly one distinct pair exists in the corpus, which is too thin to establish a convention. ## Census of the newly judged spellings Taken with the gate's own `--census --json` at `a3c14755f8` against an enumerated board (184 pages, frontier objectstack-ai#20551). The per-site transition comes from the gate's `--list` before and after the change. Dead means `allocated-but-absent`. Four of the numbers (14657, 12998, 10194 and 8692) were re-probed directly and answered 404. | spelling | sites now judged | dead | |---|---:|---:| | `pre-#N` | 284 | 26 | | `framework#N` | 255 | 0 | | `post-#N` | 12 | 0 | | `Pre-#N` | 4 | 1 | | `Post-#N` | 4 | 0 | | `Framework#N` | 2 | 0 | | `PRE-#N` | 1 | 0 | | `POST-#N` | 1 | 0 | | **total** | **563** | **27** | Other readings, same run: - **Newly deferred (25 sites):** the 24 prose-form sites plus the respelled `field.zod.ts:370`. Four of them were base census deaths that were never deaths: `objectui PR objectstack-ai#8758` three times, and the respelled `objectstack-ai#6111`. - **No longer extracted (21 ordinals):** `OQ#10` ×10, `PD#12`/`PD#10` ×8, `Prime-Directive-objectstack-ai#10`/`objectstack-ai#12` ×2 and `PKCS#11` ×1. - **Whole-census tally:** - Base `288611e3e5`: 38,109 judged. resolves 32,202 · resolves-as-pull 1,863 · cross-repo-unjudged 1,535 · allocated-but-absent 2,509. - Branch `a3c14755f8`: 38,088 judged. resolves 32,689 · resolves-as-pull 1,891 · cross-repo-unjudged 976 · allocated-but-absent 2,532. - The board moved between the two runs, so the per-site transition above is the reading, not the tally difference. The cross-repo count reconciles exactly: 1,535 − 563 − 21 + 25 = 976. ## Verification All gates below ran at `a3c14755f8`, the branch head. - **Self-test.** `node scripts/check-issue-citations.mjs --self-test` exits 0 with 114 cases across 8 batteries (base: 73 cases across 7). The new battery `qualifier` (floor 40) pins every spelling both ways: lit on a live number and a FINDING on a dead one for `pre-` `post-` `Pre-` `Post-` `PRE-` `POST-`, and for `framework` `Framework` `objectstack-ai/framework` `objectstack`. It also pins cross-repo even when dead for `objectui` `OBJECTUI` `ui` `cloud` `hotcrm` `objectstack-ai/objectui` `better-auth/better-auth`, and covers: - an unknown word prefix read as prose; - the four ordinal heads; - the prose form, lit and dead, including `PR #N` and `UI #N` NOT being the prose form; - the pair, no carry (dead second number red) and qualified number by number (both deferred); - `boardWanted` and a probed-board resolution of `objectstack#20330`; - registry hygiene. `live-corpus` gains a pin that every qualifier the live corpus keeps names a repository. - **Ablations.** Six mutations went through `scripts/ablation-replace.mjs`, each landing on disk with the anchor count 1 → 0 and the blob changed, each red on its own case, and each restored with blob equal to HEAD `8b6cf12653dd` and `git diff HEAD` empty: - M1: the recogniser returns any bare candidate. The self-test reds on "`pre-` is prose". - M2: the probe set reverts to unqualified-only. It reds on "the board's probe set". - M3: the `framework` row is renamed. It reds on "`Framework` is THIS repository". - M4: the hyphen head is off. It reds on "`Prime-Directive-objectstack-ai#12`". - M5: the prose form is off. It reds on "`objectui PR #N` names objectui". - M6: the `OQ` head is removed. It reds on "`ADR-0076 OQ#10`". - **Diff-scoped verdict** (`node scripts/check-issue-citations.mjs`, as CI runs it): exit 0 on this branch. It judged the respelled line's 2 citations, both `cross-repo-unjudged`. - **One-time end-to-end proof** (no permanent test; injected uncommitted and restored with blob equal to HEAD and `git diff HEAD` empty). I appended `pre-objectstack-ai#12248` (dead) and `framework#20330` (live) to `packages/cli/src/commands/generate.ts` and ran the diff verdict twice: - The branch gate exits **2**. `pre-objectstack-ai#12248` is `allocated-but-absent`, `framework#20330` resolves on a board `probed (2 citations)`, and the respelled pair stays cross-repo. - The `288611e3e5` gate, from a temporary copy, exits **0** with all 4 `cross-repo-unjudged`. That is the hole this closes. - **Census** (`--census --json`): exit 0. It is report-only and never fails. - **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 97 commands, and all 97 ran. 96 exit 0, including `pnpm check:pm-dispatch-gates`, whose `dispatch-gates.mjs --self-test` passes 1,976 cases. That self-test pins this file's `:204 local-env` declaration line, and every edit here stays below it or is line-neutral. `--ran` reconciliation reads "97 derived famil(ies) accounted for — 96 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)". - **NOT MEASURED: `check:dual-build-cjs-loads`.** Reason: it loads every package's built CJS entry, and this box holds no dist for 80+ packages. The diff changes no emitted code. `@objectstack/spec` was rebuilt, and its entry gates (`check:browser-reachable-entries`, `check:entry-nameability`) exit 0. CI's Lint and Repo Gates owns this one. - **Spec package.** `pnpm --filter @objectstack/spec typecheck` returned VERDICT command-exit 0. See the report comment for the test run. ## Acceptance notes - **Dead sites the census hands over, not rewritten here.** One is under `packages/spec/src/**` and is input for the staged sweep on objectstack-ai#20234: `packages/spec/src/meta-spelling/manifest-collection-spelling.ts:71` `pre-objectstack-ai#10194`. The other 26 are outside `packages/spec/src/**`, and no card names them: - `packages/cli/src/commands/generate.ts:1842` `pre-objectstack-ai#14657` - `packages/cli/src/utils/storage-driver.ts:206` `pre-objectstack-ai#6345` - `packages/drivers/driver-sql/src/schema-drift.ts:2458`, `:2474` `pre-objectstack-ai#12998` - `packages/drivers/driver-sql/src/sql-driver.ts:3872`, `:16545` `pre-objectstack-ai#17590` - `packages/drivers/driver-sql/src/sql-driver.ts:18285`, `:18324` `pre-objectstack-ai#12998` - `packages/drivers/driver-sql/src/sql-driver.ts:20095` `Pre-objectstack-ai#12380` - `packages/drivers/driver-turso/src/remote-transport.ts:2624` `pre-objectstack-ai#12380` - `packages/lint/src/validate-searchable-fields.ts:312` `pre-objectstack-ai#8404` - `packages/metadata-protocol/src/protocol.ts:2793` `pre-objectstack-ai#10888` - `packages/metadata-protocol/src/seed-loader.ts:1947` `pre-objectstack-ai#11674` - `packages/objectql/src/action-governance.ts:339` `pre-objectstack-ai#14423` - `packages/plugins/plugin-auth/src/auth-manager.ts:5629` `pre-objectstack-ai#14762` - `packages/plugins/plugin-security/src/bootstrap-platform-admin.ts:266`, `:630` `pre-objectstack-ai#8692` - `packages/plugins/plugin-security/src/per-organization-catalog.ts:314` `pre-objectstack-ai#8692` - `packages/plugins/plugin-security/src/permission-set-projection.ts:482` `pre-objectstack-ai#6483` - `packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts:5` `pre-objectstack-ai#14484` - `packages/runtime/src/domains/mcp.ts:364` `pre-objectstack-ai#8726` - `packages/runtime/src/sandbox/body-runner.ts:548`, `:735` `pre-objectstack-ai#14758` - `packages/runtime/src/sandbox/script-runner.ts:440` `pre-objectstack-ai#14758` - `packages/types/src/driver-error-classification.ts:608` `pre-objectstack-ai#13324` - `packages/types/src/node.ts:1428` `pre-objectstack-ai#10943` - **The same `objectui#6110 + objectstack-ai#6111` pair outside the claim's surface.** It still reads `objectstack-ai#6111` as this repository's (404 here), so these are existing census deaths: `packages/spec/src/ui/view.zod.ts:3634` (for the objectstack-ai#20234 sweep), `packages/metadata-core/src/form-predicate-root-policy.ts:14`, `:120` and `:205`, and `packages/metadata/src/plugin.ts:910`. Each respells to `objectui#6110 + objectui#6111`. - **Pairs that read silently wrong, not dead.** Several `REPO#N / #M` pairs name the qualifier's own second number, which resolves here as an unrelated record. Examples: `hotcrm-heimao#35/objectstack-ai#40/objectstack-ai#59`, `objectui#2715/objectstack-ai#2717`, `objectui#2711/objectstack-ai#2722`, `objectui#4648/objectstack-ai#4901`, `objectui#5018 / objectstack-ai#6469`, `cloud#957 / objectstack-ai#962` and `cloud#930/objectstack-ai#944`. No gate can see these, because they resolve. The convention in the refusal text (qualify each number) is the remedy when someone next touches the line. - **Seat 4's `objectui PR objectstack-ai#10264` specimen.** `packages/spec/src/api/export-job-family-retirement.test.ts:25` sits on a DEFERRED surface (`packages/**/*.test.ts`), and `surfaceFor` answers `null` for it. The census never judged that site. The prose form it names is now read correctly wherever the census does look. - **Observed once: a truncated board enumeration accepted as a reading.** My first branch `--census` read `enumerated (126 pages)` with frontier objectstack-ai#13977, against 184 pages and objectstack-ai#20551 on the re-run minutes later, and reported 9,160 `never-issued` phantoms. `enumerateBoard` stops at the first page without `rel="next"` and trusts the maximum it saw as the frontier. This diff does not touch that code. The diff-scoped verdict enumerates only past 400 distinct numbers. Recorded, not filed; the seat decides. - **Scope declaration.** `NON_CITATION_HEADS` (two rows) and `nonCitationHead` (the hyphen) are grammar next to the qualifier, not the qualifier itself. They are here because closing the qualifier made those 13 ordinal sites judged citations of this repository's objectstack-ai#10, objectstack-ai#11 and objectstack-ai#12, which is false. No gate was added, and the diff-scoped blocking rule is unchanged. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…ned and URL-spelled citations, pinned in one spelling table (objectstack-ai#20989) Fixes objectstack-ai#20636 Clause-②: no The family closeout for the citation extractor in `scripts/check-issue-citations.mjs`: every spelling a seat measured as invisible to the diff gate and the census is now read, every exclusion keeps only the shapes it was measured protecting, and the self-test carries the one enumeration table the triage asked for (48 spellings, each "extracted as" or "not a citation, because"). Landing site: `scripts/check-issue-citations.mjs` only. `scripts/check-doc-authoring.mjs` is untouched; H2 below says why. ## What changed - **Hyphen after the number.** The lookahead no longer refuses a `-`, so `objectstack-ai#13398-class`, `objectstack-ai#5347-A` and `ui#6206-B` read as citations of their number. It still refuses a word character, so a hex colour stays out. - **Slash before the `#`.** A `/` is now valid context before a bare `#`. It stays refused only before a qualifier candidate, so a URL path fragment such as `https://example.com/docs/page#12` never reads `page` as prose. A `/` right after a digit is the exception, so `objectstack-ai#3076/objectui#2614` still reads its own qualifier. - **Slash-joined continuation.** In `#A/#B`, the second number takes the chain head's reading: bare after a bare or prose head, the head's repository after a qualified head, and an ordinal after an ordinal. Only a joined `/` continues a chain. `objectui#1 / objectstack-ai#2` and `objectui#1 + objectstack-ai#2` stay two separate readings. - **URL spelling.** `https://github.com/OWNER/REPO/issues/N` and `.../pull/N` are now citations, qualified by their own `OWNER/REPO`. That puts `objectstack-ai/framework` in this repository and makes every other repository's URL cross-repo, never a finding. Inside a markdown link `[#N](URL)`, the citation counts once. - **Head rows.** I retired `re-charter`, `clause` and `option` (named in the thread), plus `acceptance` and the section mark (found by the same measurement). Each protects 0 sites repo-wide at two or more digits and hides board citations. The grammar's two-digit floor already keeps one-digit ordinals out. I added a `](` row, the narrower guard the hyphen exclusion leaves behind for markdown in-page heading anchors. - **Self-test.** - A new `spellings` battery (56 cases) reads the table row by row. - Every head row must excuse at least one table row, and every required spelling (the card's list plus the thread's) must be present. - The `live-corpus` battery gains three floors, one per new arm, each counting only a number spelled once on its line. - The roster floor rises from 6 to 9 batteries. - Total: 114 cases in 8 batteries became 173 in 9. - I edited the header in place and kept its line count, because `scripts/pm/dispatch-gates.mjs`'s self-test pins `scripts/check-issue-citations.mjs:204 local-env`. The marker is still on line 204, and that pin passes (see Gates). ## H1: what each exclusion protected and hid Measured on `3693a1b50` over the declared surfaces. Every arm was judged against one enumerated board: 188 pages, frontier 20959, 2026-09-30T22:55Z. The instrument mirrors the gate's extractor and matched it file for file on all 2,640 files (0 mismatches). | exclusion | hid (sites, dead) | protected in the declared surfaces | disposition | |---|---|---|---| | hyphen after the number | 58, 1 dead (8 cross-repo) | 0: no numeric range, slug or hex-like token. Repo-wide: in-page heading anchors, 16 lines in `docs/design/**` and `skills/**`, plus one range, `docs/audits/...md`, whose first number is a citation | dropped; the `](` row keeps the anchor out | | `/` before the `#` | 528, 10 dead: 523 `#A/#B` second numbers and 5 `TOKEN/#N` such as `ADR-0049/objectstack-ai#1888` | 0 paths and 0 URL fragments | narrowed to the candidate arm; continuations read as their chain | | head `re-charter` | 0 left on this tree (the 26 dead `re-charter objectstack-ai#13135` were rewritten by PR objectstack-ai#20750) | 0; only the gate's own fixtures used it | retired | | head `clause` | 0 in the surfaces; 4 in deferred test files, all board citations | 0 | retired | | head `option` | 1 (`option objectstack-ai#14088`, live); 1 more under `scripts/**` | 0 | retired | | head `acceptance` | 1 (`the silent acceptance objectstack-ai#6132 closed`, live) | 0 at two or more digits | retired (in-place, below) | | head section mark | 0 in the surfaces; 4 in test files (`§6 objectstack-ai#11176's decisions`) | 0 at two or more digits | retired (in-place, below) | | heads kept | none measured hiding a citation | directive 203 (max 13), `PD` 85 (max 13), batch 276 (69 distinct, 11 to 227), `OQ` 10, `PKCS` 1 | kept | The 8 slash chains headed by another repository are not a case where the two populations cannot be told apart. The 5 on objectui's public board each name objectui's record, the issue and then the pull request that fixed it, read one by one against both boards: - `objectui#2715/objectstack-ai#2717` - `objectstack-ai#2711/objectstack-ai#2722` - `objectstack-ai#2725/objectstack-ai#2732` - `objectstack-ai#2967/objectstack-ai#2904` - `objectstack-ai#4648/objectstack-ai#4901` This repository's records with the same numbers are unrelated. The other 3 (`cloud`, `hotcrm-heimao`) are boards one credential cannot read, so they stay unjudged, as they were before. ## H2: where the URL spelling belongs The extractor. At `3693a1b50`, 57 URL sites sit in the gate's projection: 56 in package comments and 1 link on a release page. 4 of them are dead. Only 2 URL sites in package sources are inside string literals, both internal `note:` strings in `packages/runtime/src/route-ledger.ts`. `check:doc-authoring` asks a different question: may a runtime string carry a tracker reference at all? It reads string literals, skills and spec refusal messages. The two projections are disjoint, so adding the URL to the extractor double-counts nothing there. Inside the extractor, the one double-spelled site (`[objectstack-ai#15325](...objectstack-ai/issues/15325)` on `v17/17-3.mdx`) counts once. `check-doc-authoring.mjs` is not touched. ## H3: open PRs' added lines All 13 open PRs at 2026-09-30T23:2xZ: their heads were fetched into a private ref namespace (deleted afterwards). For each, the BASE extractor and this one were run over the lines it adds, against its merge base. Result: 85 added-line citations under both extractors, 0 newly extracted, 0 lost. No PR's verdict changes. Lines a PR does not add are never judged, which is unchanged and pinned in the `diff-scope` battery. ## Census, before and after The gate's own `--census --json`, once with the `3693a1b50` script and once with this one, over the same tree: | | judged | resolves | resolves as PR | cross-repo | allocated-but-absent | |---|---|---|---|---|---| | before (frontier 20965) | 37,152 | 33,403 | 1,985 | 1,024 | 740 | | after (frontier 20966) | 37,796 | 33,917 | 2,083 | 1,041 | 755 | That is 644 more judged sites and 15 more dead ones, with 0 findings lost. By arm: hyphen 1 dead, slash 10 dead, URL 4 dead. Newly visible dead sites per lane. I rewrote none of them; they belong to the lane cards: - **objectstack-ai#20594 (`domain:cli`): 1.** `packages/rest/src/rest-server.ts:7456`, objectstack-ai#11006. - **objectstack-ai#20595 (`domain:engine`): 6.** - `driver-sql`: `sql-driver.ts:3933` (URL, objectstack-ai#17590), `:12110` (objectstack-ai#10629), `:16106` (objectstack-ai#17343). - `metadata`: `loaders/ambiguous-metadata-stem.ts:40` (objectstack-ai#14423). - `metadata-protocol`: `migrations/partial-index-probe.ts:395` (objectstack-ai#16657). - `objectql`: `plugin.ts:1496` (objectstack-ai#10629). - **objectstack-ai#20596 (`domain:services`): 0.** - **objectstack-ai#20597 (`domain:spec`, `packages/lint`): 0.** - **objectstack-ai#20234 (`packages/spec/src`): 7.** - `data/datasource.zod.ts:701` (objectstack-ai#9040). - `data/filter.zod.ts:1040` (URL, objectstack-ai#17590) and `:1042` (URL, objectstack-ai#17286). - `data/value-roundtrip-conformance.ts:100` (URL, objectstack-ai#12380). - `ui/component.zod.ts:593` and `:669` (objectstack-ai#6276), and `:3771` (objectstack-ai#9972). - **Release pages (`domain:devx`, no lane card): 1.** `content/docs/releases/v17/index.mdx:168` (objectstack-ai#6075). This census was run on the PR head's tree, not after landing. A re-run after landing reads the same corpus plus whatever `main` has gained by then. ## In-place fixes beyond the three named rows I retired the `acceptance` and section-mark rows here rather than filing them. All four conditions hold: 1. They are the same defect class as `option` and `clause`: a head row hiding a board citation. 2. The fix is mechanical, and the shape is pinned by the table. 3. The file is this claim's own surface (`NON_CITATION_HEADS`). 4. The same gate's self-test covers them, so no new verification surface is added. Evidence: `acceptance objectstack-ai#6132` (live) in `packages/formula/src/cel-pushdown-limits.ts:82`, and the section-mark sites in deferred test files. Neither row has any two-or-more-digit ordinal anywhere in the repository. ## Gates (final head `25d96fcc0`) I re-derived the list with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, which gave 32 commands. The same derivation on a throwaway tree at `origin/main` (`05be35259`) with this diff applied gave an identical list. I ran all 32, plus `pnpm check:doc-authoring` and the self-test, and every one exited 0. Reconciliation verdict line: `✓ dispatch-gates --ran: 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED zero — all 32 recorded an exit code and none of them is 3).` Verdict lines worth quoting: - `node scripts/check-issue-citations.mjs --self-test`: `✅ ... every spelling enumerated ... (173 cases, 9 batteries)`. It also passes on `origin/main` `05be35259` with this diff applied. - `node scripts/check-issue-citations.mjs` (diff mode): `✅ check-issue-citations: no issue citations added against 3693a1b (0 file(s) read).` The script lives in the deferred `scripts/**` surface. - `pnpm check:pm-dispatch-gates`: `✓ dispatch-gates self-test: 1976 cases pass.` (1237.9s). Its pin `scripts/check-issue-citations.mjs:204 local-env` holds. - `pnpm check:doc-authoring`: `✓ doc authoring guard: ... hold the baseline — 549 pinned site(s)`. - `pnpm check:nul-bytes`: `check-nul-bytes: OK (scanned 9582 text file(s) ...)`. - `node scripts/check-scripts-symbol-anchors.mjs`: `✅ ... 3706 anchors across 282 scripts resolve`. ## Ablations (one-shot, from committed `25d96fcc0`, via `scripts/ablation-replace.mjs`) Every leg was expected to go red, and every leg did. Each one restored to blob `c732ce2e21c7` (equal to HEAD) with an empty `git diff HEAD`. No permanent ablation file is left. | mutation | first red | |---|---| | hyphen refused again after the number | `the live corpus must yield a #N-word citation` | | `/` refused again before the `#` | `the live corpus must yield a slash-joined #A/#B second number` | | URL arm disabled | `the live corpus must yield a URL-spelled citation` | | `option` head row restored | `spelling option #N in "the option objectstack-ai#14088 gave" must read objectstack-ai#14088; got nothing` | | continuation disabled | `spelling repo#A/#B ... must read objectstack-ai/objectui#2711, objectstack-ai/objectui#2722` | | `](` row disabled | `a markdown link's in-page heading anchor is not a citation` | | link de-duplication disabled | `spelling [#N](URL) ... must read objectstack-ai#15325` | The first slash ablation, run before the floors were tightened, went red in the table but left the live-corpus continuation floor green. A line citing the same number twice let a plain citation stand in for the slash-joined one. Commit `25d96fcc0` makes each new floor count only a number spelled once on its line. The re-run above is red at that floor. ## Acceptance notes - **URL spelling in runtime strings.** `check:doc-authoring` does not read it. At `3693a1b50` the population is 2 internal route-ledger `note:` strings (`packages/runtime/src/route-ledger.ts:459`, `:465`), and no author-facing door shows them. Noted, not filed; carrier: none. - **Range second numbers.** In a range such as `objectstack-ai#712-714`, the second number carries no `#` and is not read. There is 1 site repo-wide, in `docs/audits/**`, outside the declared surfaces. Pinned in the table with its reason. - **Dormant test-file citations.** The 8 test-file citations the retired `clause` and section-mark rows hid are in the deferred test surface. They become visible only when that surface is swept. - **Line-number pin.** `dispatch-gates.mjs` pins this file's `local-env` marker by line number (`:204`), so any future header growth above it has to move that pin in the same PR. No changeset: a root `scripts/` file publishes nothing (the root `package.json` is private, and no package's `files` ships `scripts/`), so this PR takes `skip-changeset`. --- _Generated by [Claude Code](https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
修 #4899 引入的那个守卫的两处缺陷 —— 紧接着的下一次发布(
89d2a4e)就把它们全暴露了。那一轮:npm 成功发布了全部 17.0.0-rc.2 并推了 tag,然后创建
@objectstack/spec的 GitHub Release 时撞上 API 的 125k body 上限失败(该版本的 changelog 段落 342,911 字符,详见 #4900)。步骤标红 →published没置真 → 运行时镜像被静默丢掉。缺陷 1:守卫根本触发不了
GitHub 会给不带状态函数的
if:隐式包一层success()。所以实际是
success() && (...)—— changesets 步骤一失败它就被 skipped,而那正是它存在的两个理由之一。改为!cancelled() && ...。缺陷 2:它守的契约错了
原来写的是「缺失就补发」。这覆盖不了「发布成功但在上报前挂了」:rc.2 当时已经在 npm 上,即使它执行了也是 no-op,镜像照样丢。
现在它守的是发布真正欠下的不变量 —— 本仓库的当前版本必须在 npm 上,且必须有对应的运行时镜像 —— 并把后半句通过 job output 上报,让
dockerjob 去建。dockerjob 同样加!cancelled()默认隐式
success()下,上游 job 一失败依赖 job 就被跳过 —— 于是镜像丢在了一个发生在包已经公开之后的故障上。真正的闸门是published输出,不是 release job 的退出码。run 仍然会红(GitHub Release 确实失败了),但镜像不再陪葬。一个刻意的取舍
ghcr 探测失败按「镜像缺失」处理:多建一次镜像只是几分钟,而错误地跳过会留下一个「npm 上有包、没有镜像、且没有任何信号」的发布 —— 正是今天这个坑。
验证
四条路径都用打桩的
npm/pnpm/git/curl实跑过:ghcr 探测本身是对线上真实注册表验证的:能正常换到匿名 pull token(长度 68),并正确报告
17.0.0-rc.1存在、17.0.0-rc.2不存在。YAML 解析通过,新步骤 shell 过
bash -n,两个 job output 表达式取值已核对。关联
docker-publish.yml的workflow_dispatch手动补发。只动
release.yml,不涉及任何运行时代码。🤖 Generated with Claude Code
https://claude.ai/code/session_01BbNVKv6KgPzuQ5p76nMgnf
Generated by Claude Code