Skip to content

docs(spec): name the node slot in the structural-condition ruling and its ADR-0087 entry - #17761

Merged
os-bill merged 3 commits into
mainfrom
claude/issue-17493-node-door-refusal-residues
Sep 12, 2026
Merged

os-bill merged 3 commits into
mainfrom
claude/issue-17493-node-door-refusal-residues

Conversation

@os-bill

@os-bill os-bill commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Part of #17493

Items ① and ② of the card. ⛔ Item ③ is a ruling, not a refactor — it is measured and reported here, and deliberately not implemented; the card stays open on it, and this PR therefore carries no closing keyword.

Nothing in this diff changes behaviour. The refusal already shipped (#17322 at registerFlow, #17495 at objectstack validate); what shipped with it was the notification, and this is that half.

  • Clause-②: no — this PR puts no new key on any published payload.

① The on-site ruling that had become false

packages/spec/src/automation/flow-node-expression-paths.ts — the docblock of structuralConditionRefusal.

It admitted a whitespace-only string on this ground, verbatim as it stood:

every string, including a whitespace-only one. What a non-empty string says stays validateExpression('predicate', …)'s verdict, and a whitespace-only condition meaning false is consistent on both sides and is ruled correct, not a defect.

#15807 removed that ground (FlowEdgeSchema.condition composes EvaluatedExpressionInputSchema), #17322 ruled on the disagreement that left, and #17495 finished it at the third door. ⛔ Not deleted — it records a real decision, so the edit says what changed it:

  • The admission itself is unchanged and still correct. This function answers the SHAPE question; a string is a well-shaped structural condition. The blank is refused beside it, by the imported evaluated-slot rule, answering EVALUATED_EXPRESSION_SOURCE_REQUIRED and not STRUCTURAL_CONDITION_SHAPE_REFUSAL.
  • A new section records why item ③'s question is open, without answering it.
  • The same stale ground sat three lines from the symbol, in flow-node-expression-paths.test.ts's own case comment ("not authored on both sides and stays so"). Same defect class, same file face, mechanical: the comment is corrected, both assertions untouched. ⭐ Declared as a bounded on-site fix beyond the dispatched file face.

② The ADR-0087 entry named only the edge slot

packages/spec/src/migrations/entries/semantic/18.flow-edge-condition-evaluated-slot-source-required.tssurface and acceptanceCriteria.

Both named only edges[].condition, so a consumer replaying the chain was told to sweep the edge key alone and a deployment carrying a blank config.condition on a node was never told to look. Both now name both structural slots, plus the node key's own locator phrase (the one the structural pass builds, e.g. node 'gate' (start) condition) beside the edge's flows.N.edges.N.condition, and the sweep carries the warning that deleting a condition from a start node opens the trigger gate rather than preserving it.

  • id, replacement and reason are untouched, per the card.
  • No backticks in surface — measured, 0 (build-upgrade-guide.ts renders it inside a code span).
  • No new entry. This is one decision reaching its second slot, which is what both follow-up changesets (blank-node-condition-refused-at-registration.md, validate-refuses-blank-structural-condition.md) already declared by taking the not-required (already-registered …) disposition against this very entry.
  • packages/spec/src/migrations/registry.ts is regenerated by the repo's own generator, pnpm --filter @objectstack/spec gen:migration-registry — ⛔ never hand-edited. check:migration-registry is green.

⛔ ③ Measured, reported, NOT implemented

registerFlow still ACCEPTS a whitespace-only string at both sibling ledger predicate slots, on this head, against controls that prove the probe reaches them:

slot valid CEL (positive control) envelope with a blank source (refusal control) ' ' ''
config.conditions[].expression ACCEPTED REFUSED — PREDICATE_SLOT_STRING_REFUSAL at config.conditions[0].expression ACCEPTED ACCEPTED
screen.fields[].visibleWhen ACCEPTED REFUSED — PREDICATE_SLOT_STRING_REFUSAL at config.fields[0].visibleWhen ACCEPTED ACCEPTED
config.condition (lit control — the slot #17322 DID narrow) ACCEPTED REFUSED REFUSED REFUSED

evaluateCondition(' ') and evaluateCondition('') both answer false — the same silent dead branch. The current behaviour is pinned as correct by #15572 at packages/services/service-automation/src/decision-predicate-envelope.test.ts (the it block at :110:114 on this head; the card cited :113:117 from aefbb07b2, a 3-line drift, the pin itself intact).

⛔ Narrowing those slots re-judges that pin and moves a published accept-set: a ruling, not a refactor. packages/services/** is untouched by this diff — 0 files.

验收备注

Verification

Every run below is on the final head 5a77c75b29 (origin/main merged at that commit), with the gate's own verdict line, never a bare exit status read through a pipe.

  • pnpm --filter @objectstack/spec build && check:generated && typecheck && test — VERDICT command-exit 0; 473 test files / 13436 tests passed.
  • The one test-layer file this diff touches, re-run alone: pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/automation/flow-node-expression-paths.test.ts — 1 file / 26 tests passed.
  • node scripts/pm/dispatch-gates.mjs --commands derived 84 gate families for this change set; 84 run, 82 green, 0 unrun — reconciled with --ran, exit codes captured before any pipe. The two non-green exited 3 = PREREQUISITE NOT MET, i.e. NOT MEASURED and not a finding: check:dual-build-cjs-loads and check:type-check-debt both refuse without a repo-wide build closure, and both are declared to CI, which builds one.
  • Item ②'s own gate, check:adr-0087-registration — exit 0, verdict: "this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)". check:migration-registry exit 0, verdict: "src/migrations/registry.ts is current (202 semantic, 167 retired-key, 178 retired-def)". check:spec-changes and check:upgrade-guide both report their artifact up to date; check:docs reports 222 generated files in sync.
  • eslint . --no-inline-config --format json over the whole repo, eslint's own population read from its JSON output: 6638 files, 0 errors, 0 warnings. No narrowing claimed and none needed.
  • check:nul-bytes exit 0 (8441 text files), plus a control-character self-scan over every changed file: 0 hits.

Changeset: .changeset/17493-node-door-refusal-residues.md, @objectstack/spec: patch, card-scoped filename in the tree's prevailing spelling.


Generated by Claude Code

… its ADR-0087 entry

The blank structural condition is refused at all three doors since #17322 and
#17495; two places in `packages/spec` still described the world before that.

`structuralConditionRefusal`'s docblock admitted a whitespace-only string on
#15662's ground that it "is consistent on both sides and is ruled correct, not
a defect". #15807 removed that ground at the edge door and #17322 ruled on the
disagreement. The admission itself is unchanged and still correct — this
function answers the SHAPE question and the blank is refused beside it by the
imported evaluated-slot rule — so what changes is the recorded reason, plus a
section recording, without answering, the open question at the sibling ledger
`predicate` slots.

The ADR-0087 D3 entry `flow-edge-condition-evaluated-slot-source-required` named
only `edges[].condition` in its `surface` and `acceptanceCriteria`, so a consumer
replaying the chain was told to sweep the edge key alone and a deployment
carrying a blank `config.condition` was never told to look. Both fields now name
both structural slots, with the node key's own locator phrase. `registry.ts` is
regenerated by `pnpm --filter @objectstack/spec gen:migration-registry`.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
… its assertions

The same stale ground as the docblock, three lines from the symbol: the case
admitted a blank structural condition as "not authored on both sides", which
#15807 and #17322 ended. The two assertions are unchanged and still correct —
they pin that this function does NOT carry the blank rule — and the comment now
says so instead of restating a ruling that no longer holds.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 2 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/automation/flow-node-expression-paths.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/api/index.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/concepts/metadata-lifecycle.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/data-modeling/drivers.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/data-modeling/objects.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/deployment/cli.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/deployment/environment-variables.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/deployment/validating-metadata.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/kernel/cluster.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/kernel/contracts/metadata-service.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/kernel/services-checklist.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/permissions/authorization.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/permissions/permission-sets.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/plugins/packages.mdx (via sys_metadata (literal, a string literal in surface))

4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/releases/v16.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/releases/v17/17-0.mdx (via sys_metadata (literal, a string literal in surface))
  • content/docs/releases/v17/17-3.mdx (via sys_metadata (literal, a string literal in surface))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/automation/flow-node-expression-paths.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ec739e67192ce686f956cd490b7bc2b9d0d64cacpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 70e3731293fd1ce6db29faee0cf157a329d95818 — the merge of head 5a77c75b29637540fcb3acdc4a2078049767659d into base ec739e67192ce686f956cd490b7bc2b9d0d64cac, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 70e3731293fd1ce6db29faee0cf157a329d95818 && git checkout 70e3731293fd1ce6db29faee0cf157a329d95818
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ec739e67192ce686f956cd490b7bc2b9d0d64cac 5a77c75b29637540fcb3acdc4a2078049767659d && git checkout -B drift-repro ec739e67192ce686f956cd490b7bc2b9d0d64cac && git merge --no-ff 5a77c75b29637540fcb3acdc4a2078049767659d

node scripts/docs-audit/affected-docs.mjs --json ec739e67192ce686f956cd490b7bc2b9d0d64cac

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ec739e67192ce686f956cd490b7bc2b9d0d64cac → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 12, 2026
@os-bill
os-bill marked this pull request as ready for review September 12, 2026 03:17
@os-bill
os-bill added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 0a88a80 Sep 12, 2026
35 of 36 checks passed
@os-bill
os-bill deleted the claude/issue-17493-node-door-refusal-residues branch September 12, 2026 03:52
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…bjectstack-ai#17776)

Fixes objectstack-ai#17279

## The defect

PR objectstack-ai#15724 closed objectstack-ai#15627 by widening a `type-surface-only` reference to a
**dotted member path**, so an author can name a nested member instead of
a bare identifier. The walker it added narrows each leading segment
through an **object literal only**. A member declared on a **class**
therefore has no dotted spelling, and the bare fallback is "the first
same-named definition in the file" — which is the defect objectstack-ai#15627 was
filed on, surviving its own fix on the container kind that fix did not
cover.

Reproduced at the merge base on the card's own evidence,
`packages/objectql/src/engine.ts` (read-only here, not edited):

| spelling | before | after |
|---|---|---|
| `packages/objectql/src/engine.ts#delete` (bare) | `Promise<boolean \|
number>` — `ObjectQL.delete`, which the card's diff never touched |
unchanged |
| `packages/objectql/src/engine.ts#ObjectRepository.delete` | refused:
``no `ObjectRepository` object literal (`ObjectRepository: {` or
`ObjectRepository = {`) is declared at the top of the file`` |
`Promise<any>` |
| `packages/objectql/src/engine.ts#ObjectRepository.findOne` | same
refusal | `Promise<Record<string, any> \| null>` |
| `packages/objectql/src/engine.ts#ObjectRepository.update` | same
refusal | `Promise<Record<string, any> \| number \| null>` |

The first two rows are the load-bearing pair: the two spellings reach
**different members** (`Promise<boolean | number>` vs `Promise<any>`),
so the bare one is not a usable substitute — it answers a true sentence
about the wrong member.

**Lit control for that probe:** the object-literal case objectstack-ai#15724 rescued,
`packages/client/src/index.ts#oauth.applications.get`, resolves to
`Promise<OAuthApplication>` both before and after. A probe that found
nothing for classes was one that could find something for literals.

## ⭐ The population, established before fixing (triage's gate)

Triage asked how many published narrowings sit on class members today,
and made the answer decide how this PR is written. **It is not zero.**

Method: enumerate every `adr-0087: not-required (type-surface-only …)`
marker in the tree with `git grep` (markers are copied verbatim from
`.changeset/*.md` into each package's `CHANGELOG.md`, so the tree at
HEAD carries the published ones too), split the comma-separated
reference lists, then classify each named symbol's innermost container
by walking class / interface / enum bodies and named object literals
over the repo's own comment- and literal-masked projection.

**Space searched:** every tracked file at HEAD — which is where markers
live: `.changeset/*.md` (4 files), `packages/*/CHANGELOG.md` (4 files),
`docs/adr/0087-*.md` and this gate's own fixtures. Placeholder
references (`path/to/file.ts#Symbol` and the angle-bracket forms) were
excluded.

**Result — 42 real references:**

| container of the named symbol | count |
|---|---|
| object literal | 30 |
| **class** | **10** |
| top level | 1 |
| not a member definition (a top-level type name) | 1 |

The ten class members:

- `packages/drivers/driver-sql/src/sql-driver.ts#aggregate`,
`#bulkCreate`, `#create`, `#findOne`, `#update` — all on `class
SqlDriver`
- `packages/drivers/driver-turso/src/turso-driver.ts#aggregate`,
`#bulkCreate`, `#create`, `#findOne`, `#update` — all on `class
TursoDriver`

⭐ **Every one of the ten is written BARELY, and not one of them could
have been written dotted.** Each resolves today only because that name
happens to be unique in its file (measured: 1 same-named definition
each); the dotted spelling `SqlDriver.findOne` was refused for all ten
with the object-literal message. So the category has been in live use on
class members all along, addressable only by an accident of naming — and
on the one file where the accident does not hold, `engine.ts`, the
member had no spelling at all.

**Lit controls for the census probe** (a zero is a reading only if the
instrument could have come back the other way):

- class: `packages/objectql/src/engine.ts#findOne` → 2 definitions,
`class ObjectQL` (L9844) and `class ObjectRepository` (L15072). Lit.
- object literal: `packages/client/src/index.ts#oauth.applications.get`
→ 14 definitions across nested literals inside `class
ObjectStackClient`. Lit.
- top level: `scripts/check-adr-0087-registration.mjs#parseSymbolRef` →
1, TOP LEVEL. Lit.

⇒ The gap is **live, not latent**. This PR unblocks the `engine.ts`
shape outright, and gives the ten existing references a spelling that
survives a same-named member being added above them.

## The change

`resolveMemberPath` walks each leading segment through
`containerBodiesFor`, the union of `objectLiteralBodiesFor` and the new
`classBodiesFor`. A union, not two passes: a segment naming **one
literal and one class** is AMBIGUOUS exactly as two literals are, and
counting the kinds separately would let it through as "one of each".

- `class`, `export class`, `export default class`, `declare` and
`abstract` prefixes; the body is found with the existing
`declarationBodyStart`, so `extends Base<{ a: 1 }>` does not send the
walk into a type argument.
- A **class expression** (`const X = class { … }`) is deliberately not
walked — that name belongs to the binding, not the class.
- `definitionsAtTopDepth` now treats any nested container as deeper
nesting, so the objectstack-ai#16571 top-depth rule holds inside a class body too.
- Refusal texts and the author-facing `fix:` remedy name both container
shapes, and the remedy now prints a class example beside the literal
one. An author who cannot see the spelling in the remedy cannot write
it.

**Bare references are untouched.** Nothing about what the marker means
changed, and nothing beyond naming a class member was widened.

## ⭐ Ablation — it can fail, and it still refuses what it should

Removing the class limb from the union (`containerBodiesFor` back to
object literals only), proven on disk before reading any result — anchor
occurrences 1 → 0, blob `394fd83b` → `c3a303c3`, restored afterwards and
verified byte-identical to `HEAD` — turns **9 assertions red**:

- `TSO-C1` reads back the exact original refusal: ``no
`ObjectRepository` object literal or class (…) is declared at the top of
the file.``
- `TSO-C12` (end to end, through `scan()`) fails as ``[predicate 4]
cannot be resolved at HEAD in packages/objectql/src/engine.ts``.
- ⭐ `TSO-C9`, the union pin, does not merely fail — it **silently
resolves**: `dual.findOne` returns `Promise<Lit>` instead of refusing,
i.e. the ablated walker picks one of two real candidates. That is the
"writable but wrong" reference the whole dotted grammar exists to
prevent, and it is why the union is counted as one set.

Still refused **after** the change, so the gate did not simply become
permissive:

- `NoSuchClass.findOne` → ``no `NoSuchClass` object literal or class
(`NoSuchClass: {`, `NoSuchClass = {` or `class NoSuchClass {`) is
declared at the top of the file`` (and end to end, `TSO-C14`).
- `ObjectRepository.nosuch` → ``no `nosuch` definition sits inside it``
— never resolved outward to the same-named member on the other class.
- two classes of one name → ``opens 2 classes …, so the path is
AMBIGUOUS``.
- one literal + one class → ``opens 2 object literals and class bodies
…, so the path is AMBIGUOUS``.
- a class expression → refused, never guessed.

**Whole-tree regression control:** all **42 of 42** live references read
**byte-identically** before and after — 0 moved, 0 newly resolved. The
ten bare class-member references in particular did not move.

## Changeset — measured, not assumed

`skip-changeset`. Measurement, with both controls, on a real build:

- **Subject:** the new identifiers `containerBodiesFor` /
`classBodiesFor` / `describeBodies` / `BODY_PLURALS` occur 3 / 2 / 2 / 2
times in the changed source and **0 times** in any built `dist`.
- **Positive control:** `OpenAIEmbedder`, a symbol that ships → **58**
occurrences in `packages/plugins/embedder-openai/dist`. The probe finds
shipped text.
- **Negative control:** `createOpenAIEmbedder presets`, which exists
only in that package's test file → 1 in `src/__tests__`, **0** in
`dist`. The probe separates shipped from unshipped.
- **Structural half:** the root `package.json` is `private: true`, and
**0 of 70** non-private manifests has a directory containing either
changed path, so no `files[]` can reach `scripts/**` or `docs/adr/**`.

⇒ Nothing published moves.

- **Clause-②: no** — this PR puts no new key on any published payload.

## ⛔ Governed surface — the maintainer merges this by hand

The diff touches `docs/adr/0087-metadata-protocol-upgrade-contract.md`,
and `docs/adr/**` is in today's register (`docs/adr/** · .claude/** ·
skills/** · AGENTS.md · CLAUDE.md`). The ADR edit is not optional: the
anchor record for this script states *"Never widen the exemptions here
without the ADR half in the same PR"*, and the addendum described the
walk as object-literal nesting. ⛔ No seat merges, queues, or arms
auto-merge on this PR.

## 维护者速读(草稿)

**改了什么** — `type-surface-only`
标记里的点分成员路径,原先只能穿过对象字面量;现在也能穿过类体,于是声明在类方法上的类型收窄终于能在它自己的标记里被点名。裸标识符的含义一个字节都没动。

**为什么改** — 这是 objectstack-ai#15627 的残留:它的修复只覆盖了一种容器。树上已有 **10
条**活的引用落在类成员上(`SqlDriver` / `TursoDriver` 各 5
条),全部靠"这个名字在该文件里恰好唯一"才能解析;一旦不唯一(`engine.ts`
就是),作者只剩两条路——写一个指向别的成员的错标记,或者丢掉 `**BREAKING**` 去躲开判据。后者正是 objectstack-ai#13080 记录的侵蚀。

**风险与代价(含回滚)** — 风险面是这一个门禁脚本的解析行为。全树 42
条现存引用改动前后逐条比对**完全一致**,消融显示去掉类分支会让 9 条断言转红、其中 1 条会变成"静默挑一个"。回滚 = revert 本
PR,门禁回到今天的行为,无数据迁移、无发布物变动。

**席位意见** — (留空,待维护者)

**你要做的** — 本 PR 触及 `docs/adr/**`(受管面),⛔ 不进合并队列、不开自动合并,需要你手动合并或给出授权的
APPROVED 评审。

## Verification

- `node scripts/check-adr-0087-registration.mjs --self-test` → **355
assertions**, exit 0 (338 at the merge base; +17 in the new `TSO-C`
battery, registered at its floor in `SELF_TEST_BATTERIES`).
- `node scripts/check-adr-0087-registration.mjs --base origin/main` →
exit 0.
- Derived family via `node scripts/pm/dispatch-gates.mjs --commands` on
the merged head: **43 commands — 42 run and green, 1 NOT MEASURED.**
- `check:scripts-symbol-anchors` caught a real regression of mine
(abbreviated `engine.ts#findOne` prose read as a live anchor) — fixed in
its own commit and re-run green.
- `check:doc-formula-expressions` first exited **3 (PREREQUISITE NOT MET
— nothing measured)** because `@objectstack/formula` was unbuilt; after
`turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` it exits 0.
- ⊘ **NOT MEASURED: `pnpm check:pm-dispatch-gates`** — its self-test did
not reach a verdict inside this container's foreground window across
four attempts (still running after 11 minutes, no output advancing), so
it is **declared to CI** rather than reported as green. It grades the
dispatch-gates checker's own fixtures, not this diff, and this diff
touches no `scripts/pm/` path. ⛔ Read this as unmeasured, not as passed.
- `main` merged once (`0a88a800bd`, which brought PR objectstack-ai#17761's
`packages/spec/src/migrations/**` changeset) and everything above
re-measured on the merged head: self-test 355/0, census unchanged at 10
of 42, whole-tree comparison still 42 of 42 identical.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants