Skip to content

Commit 06513bc

Browse files
test(spec): the deleted-baseline reachability pin measures descent below the unregistered connector root again (#29831)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBY61hHdLdFdzwXbxaNtv58y)_ Fixes #29668 ## 维护者速读 - 改了什么:`build-schemas-check-mode.deleted-baseline.test.ts` 的 #17356 用例在原有的根自身 def(`integration/DeclarativeConnectorEntry`)旁,新增一个挂在未注册根 `connector` 之下的 def:`integration/ConnectorInstanceBearerAuth`(`connector.auth` → 实例鉴权联合 → `bearer` 分支),并加夹具诚实守卫。仅测试,+97/−1。 - 为什么改:#29573 把该用例的样本移到根自身 def 后,实测它已不能判别 #17356 缺陷:根集合收窄回已注册类型时,它经共享的 `_lock` 等记账键实例被读成 derived-clone,删除照样被拒;新样本此时读 `null`,会被 proof 2 豁免,正是该缺陷的形态。 - 风险与回滚:不改任何生成器或 schema,不动任何接受集合;回滚即 revert 本 PR。 - 先看:测试文件中 `DELETED_BENEATH_UNREGISTERED_KIND_DEF` 的文档注释,再看 `beforeAll` 里 #29668 守卫和 #17356 用例中新增的四条断言。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run --project repo scripts/build-schemas-check-mode.deleted-baseline.test.ts -t 17356`,预期通过;把 `computeSurfaceReachability()` 的 BFS 改为不下探未注册根,预期本用例在新断言处变红,下方两次消融均已实测。 - 需要你做的:无,席位落地。 Clause-②: no Metadata-protocol: none ## What changes - **Depth specimen.** The #17356 case seeds a third deleted base line, `integration/ConnectorInstanceBearerAuth:zzBeneathAnUnregisteredRoot29668`, and asserts that check (c) refuses it: in the eager regime it reads `def reachable from the metadata-type roots; … was LIVE`, in the lazy regime `def … was LIVE (never tombstoned)`, and in neither does it read `def not reachable from the …` (the proof-2 waiver). The existing specimen and its assertions are unchanged. It runs in the same spawned run as the existing specimen, so no new spawn and no test-budget change. - **Fixture guard.** `beforeAll` holds the def beneath `connector` and beneath no other root. It reads the def the way the gate does, on both routes: its own instance in a BFS closure (`zodChildSchemas`), and a shape entry (same name, same instance) held by a visited object node (`zodShapeOf`). It also checks the def is still emitted with authorable keys. Imports added for the guard only: `getMetadataTypeSchema`, `ConnectorInstanceBearerAuthSchema`, `zodChildSchemas` and `zodShapeOf`. - **Docblock.** The constant's docblock says why the root's own def no longer discriminates, gives the measurement, and lists the nine sibling arms that qualify the same way. ## Why #29573 moved the #17356 specimen from `integration/RetryConfig` to the connector root's own def. The card asks for a specimen beneath that root again. Measuring which one qualifies on `main` turned up more than the card said: the root's own def no longer catches the defect at all. Every metadata document carries the shared bookkeeping keys (`_lock`, `_lockReason`, …) as the same schema instances. So with the roots narrowed back to `listMetadataTypeSchemaTypes()` (the #17356 defect), `integration/DeclarativeConnectorEntry` resolves as `derived-clone` through any registered root's `_lock`, and check (c) still refuses its deletion. Only the wording changes. The existing pin goes red on that wording, but the waiver it was written against no longer happens to it. ## Measurement (on `main` at 0e4b0fa) A one-off probe, deleted before commit, ran the gate's own walk (`zodChildSchemas` closure plus the shape-entry bridge) over every Integration/Shared/… namespace export, in both schema regimes. Defs that read `root-graph` from `connector` and not from the other 32 roots: | def | without `connector` | on the surface | |---|---|---| | `integration/ConnectorInstance{NoAuth,BearerAuth,APIKeyAuth,BasicAuth,OAuth2AuthorizationCodeAuth,OAuth2ClientCredentialsAuth,AppCredentialAuth,SignedAlipayV3Auth,SignedAliyunAcs3Auth,SignedTencentCloudTc3Auth}` | `null` | yes (10) | | `integration/ConnectorInstance{Auth,OAuth2Auth,SignedAuth}` (unions) | `null` | no keys | | `integration/DeclarativeConnectorEntry` (the root's own def) | `derived-clone` | yes | With the registered set alone (30 roots, the pre-#17356 roots): `DeclarativeConnectorEntry` gives `derived-clone`, `ConnectorInstanceBearerAuth` gives `null`, in both regimes. `bearer` was picked as the plainest arm. ## Ablations (the new assertion, red; then restored) Each ablation edited `packages/spec/scripts/build-schemas.ts`, ran `vitest run --project repo … -t 17356` under `os-verify-lock`, and was restored with `git checkout`. The committed tree has no generator change. 1. **Stop the walk at the unregistered roots.** The root stays visited, but the BFS does not push its children. Exit 1, red at the new eager assertion (`:617`). The gate printed: - `integration/ConnectorInstanceBearerAuth:zzBeneathAnUnregisteredRoot29668 — def not reachable from the 33 metadata-type roots` (waived) - `integration/DeclarativeConnectorEntry:zzOnlyRootIsAnUnregisteredKind17356 — def reachable from the metadata-type roots; … was LIVE (never tombstoned).` (the existing assertions held) Only the new specimen measures the descent. 2. **Narrow the roots back to the registered set** (the #17356 defect: drop the `listUnregisteredKindSchemaTypes()` line from `reachabilityRootTypes()`). Exit 1, red at the existing wording assertion (`:606`). The gate printed: - `ConnectorInstanceBearerAuth:… — def not reachable from the 30 metadata-type roots` (waived) - `DeclarativeConnectorEntry:… — def authorable through a derived clone of a root-reachable schema; … was LIVE` (still refused) This confirms the docblock's claim with the real gate. ## Item 1 (ruled, already on `main`) Item 1 was ruled by the maintainer on PR #29573 (comment 6593804783, 「显式 {} 就清掉,带前提」), and #29573 built it and is merged. The HTTP-door pin is on `main`: `packages/qa/dogfood/test/connector-meta-door-url-and-nested-keys.dogfood.test.ts`, test "`retryConfig: {}` replaces the stored one whole: the next read of the stored row no longer has the undeclared key", in the CARRY-FORWARD describe. It is a `PUT /meta/connector/:name` of the served form with `retryConfig: {}` over a seeded row holding `retryConfig.secret`. It asserts a `withheld-key-dropped` advisory at `connectors[0].retryConfig.secret`, the stored row's `retryConfig` equal to `{}`, and a control that a withheld position outside `retryConfig` is still carried forward. The served-object variant is in `connector-meta-door-undeclared-keys-withheld.dogfood.test.ts`. This PR adds no dogfood pin. ## Verification All under `scripts/pm/os-verify-lock.sh` on head `d1cdcbe1`, shared box: - `pnpm --filter @objectstack/spec build`: exit 0 (93 s). - `pnpm --filter @objectstack/spec test`: exit 0, 23961 passed, 1 todo. - `pnpm --filter @objectstack/spec test:repo`: exit 0, 89 files, 1522 tests (the deleted-baseline file runs here: 15/15, 186 s alone). - `pnpm --filter @objectstack/spec check:test-typecheck` and `check:scripts-typecheck`: exit 0. - `node scripts/pm/dispatch-gates.mjs --commands`: 58 families, each run and recorded. `--ran` exits 0 with 56 run, 2 NOT MEASURED, 0 unrun. NOT MEASURED: `check:dual-build-cjs-loads` and `check:lean-entry-closure`, both exit 3 because they need every package's `dist` and only spec was built. CI's Build Core runs them, and a test file under `packages/spec/scripts/` moves no dist. `check-plugin-teardown-shape --self-test` first exited 3 on the shallow clone; after `git fetch origin 621a487` it exited 0. - `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD`: exit 0. ## Acceptance notes - **Changeset: none, label `skip-changeset`.** AGENTS.md (Post-Task Checklist 3) reserves the label for "a diff that publishes nothing from any released package". `packages/spec/package.json` `files` does not include `scripts/`, so this test file publishes nothing. - `Clause-②: no` and `Metadata-protocol: none`: no accept set and no authorable surface moves (the diff is one test file). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzeePBUouzbRj7MtDva --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2c881fb commit 06513bc

1 file changed

Lines changed: 102 additions & 4 deletions

File tree

‎packages/spec/scripts/build-schemas-check-mode.deleted-baseline.test.ts‎

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ import { RETIRED_KEYS_BY_MAJOR } from '../src/migrations/registry';
1818
// Read ONLY to keep the #17356 fixture honest about which SET its root lives in
1919
// — never to assert gate behaviour, which is read off the spawned run's output.
2020
import {
21+
getMetadataTypeSchema,
2122
listMetadataTypeSchemaTypes,
2223
listUnregisteredKindSchemaTypes,
2324
} from '../src/kernel/metadata-type-schemas';
25+
// Read ONLY to keep the #29668 depth fixture honest about WHERE its def hangs —
26+
// beneath the unregistered root and beneath no other — never to assert gate
27+
// behaviour, which is read off the spawned run's output.
28+
import { ConnectorInstanceBearerAuthSchema } from '../src/integration/connector.zod';
29+
import { zodChildSchemas, zodShapeOf } from './lib/zod-graph';
2430
// Read ONLY to keep the #18301 fixture honest about the TREE FACT it models — a
2531
// key that left the shape whose door still answers an author with a prescription
2632
// — never to assert gate behaviour, which is read off the spawned run's output.
@@ -126,6 +132,37 @@ const DELETED_VIA_UNREGISTERED_KIND = `${DELETED_VIA_UNREGISTERED_KIND_DEF}:zzOn
126132
/** The unregistered kind that def's only root lives in. */
127133
const UNREGISTERED_KIND_ROOT = 'connector';
128134

135+
/** #29668's pin — the DEPTH half of #17356. A def reached ONLY by descending
136+
* below the unregistered `connector` root: `connector.auth` (optional) →
137+
* `ConnectorInstanceAuthSchema` (a discriminated union on `type`) → its
138+
* `bearer` arm. Nothing registered and no other unregistered kind holds the
139+
* `auth` union, so the walk reaches this def through that root's graph or
140+
* not at all.
141+
*
142+
* Why the root's own def above is no longer enough. Every metadata document
143+
* carries the shared bookkeeping keys (`_lock`, `_lockReason`, …) as the SAME
144+
* schema instances, so with the roots narrowed back to
145+
* `listMetadataTypeSchemaTypes()` — the #17356 defect —
146+
* `integration/DeclarativeConnectorEntry` still reads 'derived-clone' through
147+
* any registered root's `_lock`, and its deletion is still refused (only the
148+
* wording moves). This def has no such entry: under the narrowed roots it
149+
* reads `null`, and check (c) proof 2 WAIVES its deletion as over-collection.
150+
* Measured on `main` at 0e4b0fa4 by a BFS over the gate's own walk
151+
* (`zodChildSchemas` + the shape-entry bridge), in both schema regimes. The
152+
* def was `integration/RetryConfig` (`connector.retryConfig`) until #29573
153+
* moved the #17356 specimen to the root's own def; `integration/RetryConfig`
154+
* now resolves through the closed twin's derived-clone bridge, so it measures
155+
* a different reading.
156+
*
157+
* Nine sibling arms with authorable keys (`api-key`, `basic`, `oauth2*`,
158+
* `app-credential`, `signed*`, `none`) qualify the same way; `bearer` is the
159+
* plainest. The prop
160+
* is synthetic for the same reason the #17356 one is: check (c) judges the
161+
* DEF half, and only ever sees a key the build STOPPED emitting. */
162+
const DELETED_BENEATH_UNREGISTERED_KIND_DEF = 'integration/ConnectorInstanceBearerAuth';
163+
164+
const DELETED_BENEATH_UNREGISTERED_KIND = `${DELETED_BENEATH_UNREGISTERED_KIND_DEF}:zzBeneathAnUnregisteredRoot29668`;
165+
129166
/** Def the build no longer emits at all — the literal #4643 cluster. */
130167
const DELETED_GONE_DEF = ['identity/Session:userId', 'identity/Session:token'];
131168

@@ -215,6 +252,7 @@ describe('build-schemas.ts — deleted baseline lines must prove themselves (#46
215252
DELETED_LEAF_COLLIDER,
216253
DELETED_UNREACHABLE,
217254
DELETED_VIA_UNREGISTERED_KIND,
255+
DELETED_BENEATH_UNREGISTERED_KIND,
218256
...DELETED_GONE_DEF,
219257
DELETED_AGED,
220258
DELETED_BY_RENAME,
@@ -274,6 +312,52 @@ describe('build-schemas.ts — deleted baseline lines must prove themselves (#46
274312
`${DELETED_VIA_UNREGISTERED_KIND_DEF} is no longer emitted with authorable keys — check (c) ` +
275313
`would route this fixture to the vanished-def proof instead; re-pick the def`,
276314
).toBe(true);
315+
// #29668's depth fixture is only a pin while its def hangs beneath the
316+
// unregistered root and beneath NOTHING else. Read the way the gate reads a
317+
// def: its own instance in a BFS closure, or one of its shape entries (same
318+
// name, same instance) held by a visited object node. Reached from
319+
// `connector`, and by neither route from every other root — otherwise the
320+
// narrowed root set would still reach it and the assertion below would pass
321+
// while asserting nothing about descent beneath an unregistered root.
322+
expect(
323+
keys.some((k) => k.startsWith(`${DELETED_BENEATH_UNREGISTERED_KIND_DEF}:`)),
324+
`${DELETED_BENEATH_UNREGISTERED_KIND_DEF} is no longer emitted with authorable keys — check ` +
325+
`(c) would route this fixture to the vanished-def proof instead; re-pick the def`,
326+
).toBe(true);
327+
type ZodNode = Parameters<typeof zodChildSchemas>[0];
328+
const closureFrom = (types: string[]): Set<ZodNode> => {
329+
const visited = new Set<ZodNode>();
330+
const queue = types.map((t) => getMetadataTypeSchema(t)).filter((s): s is ZodNode => s !== undefined);
331+
while (queue.length > 0) {
332+
const node = queue.pop()!;
333+
if (visited.has(node)) continue;
334+
visited.add(node);
335+
queue.push(...zodChildSchemas(node));
336+
}
337+
return visited;
338+
};
339+
const depthDef = ConnectorInstanceBearerAuthSchema;
340+
expect(
341+
closureFrom([UNREGISTERED_KIND_ROOT]).has(depthDef),
342+
`${DELETED_BENEATH_UNREGISTERED_KIND_DEF} is no longer beneath the '${UNREGISTERED_KIND_ROOT}' ` +
343+
`root — re-pick a def the walk reaches only by descending below it`,
344+
).toBe(true);
345+
const otherRoots = [...listMetadataTypeSchemaTypes(), ...listUnregisteredKindSchemaTypes()].filter(
346+
(t) => t !== UNREGISTERED_KIND_ROOT,
347+
);
348+
const elsewhere = closureFrom(otherRoots);
349+
const depthShape = Object.entries(zodShapeOf(depthDef) ?? {});
350+
expect(depthShape.length, `${DELETED_BENEATH_UNREGISTERED_KIND_DEF} has no object shape`).toBeGreaterThan(0);
351+
expect(
352+
elsewhere.has(depthDef) ||
353+
[...elsewhere].some((node) => {
354+
const shape = zodShapeOf(node);
355+
return shape !== null && depthShape.some(([name, prop]) => shape[name] === prop);
356+
}),
357+
`${DELETED_BENEATH_UNREGISTERED_KIND_DEF} is reachable from a root other than ` +
358+
`'${UNREGISTERED_KIND_ROOT}' — its only root is no longer the unregistered kind, so the ` +
359+
`#29668 fixture no longer measures descent beneath it; re-pick the def`,
360+
).toBe(false);
277361
// #18301's fixture is only a pin while `data/Metric:filters` is still a
278362
// completed guidance-route retirement in the tree. Three halves, all loud,
279363
// because each can rot on its own and each rots the pin into a green that
@@ -502,7 +586,9 @@ describe('build-schemas.ts — deleted baseline lines must prove themselves (#46
502586
// The gate now enumerates its own reachability root union; the KIND
503587
// vocabulary `listMetadataTypeSchemaTypes()` answers is untouched (the
504588
// `beforeAll` guard above asserts that half).
505-
seedBase((s) => [...s, DELETED_VIA_UNREGISTERED_KIND, DELETED_UNREACHABLE].sort());
589+
seedBase((s) =>
590+
[...s, DELETED_VIA_UNREGISTERED_KIND, DELETED_BENEATH_UNREGISTERED_KIND, DELETED_UNREACHABLE].sort(),
591+
);
506592
const canonical = seedSurface((s) => s);
507593

508594
const rx = (key: string, tail: string): RegExp =>
@@ -520,10 +606,20 @@ describe('build-schemas.ts — deleted baseline lines must prove themselves (#46
520606
expect(eager.output).toMatch(
521607
rx(DELETED_VIA_UNREGISTERED_KIND, 'def reachable from the metadata-type roots; .*was LIVE'),
522608
);
523-
// Specifically NOT the proof-2 waiver, for this key. Asserting the absence
524-
// is the pin: narrow the roots back to `listMetadataTypeSchemaTypes()` and
525-
// the run exits 0 printing exactly the string below.
609+
// Specifically NOT the proof-2 waiver, for this key. Narrowing the roots
610+
// back to `listMetadataTypeSchemaTypes()` no longer prints that waiver for
611+
// the root's own def: its bookkeeping keys bridge it as a derived clone,
612+
// so the run still exits 1 and only the wording above moves (measured,
613+
// #29668). The depth key below is the one that is waived then.
526614
expect(eager.output).not.toMatch(rx(DELETED_VIA_UNREGISTERED_KIND, 'def not reachable from the'));
615+
// Direction 1 at DEPTH (#29668) — the walk descends below that root. The
616+
// root's own def above is refused under the pre-#17356 roots too (its
617+
// bookkeeping keys bridge it as a derived clone), so it is THIS key whose
618+
// verdict flips to the proof-2 waiver when the descent is lost.
619+
expect(eager.output).toMatch(
620+
rx(DELETED_BENEATH_UNREGISTERED_KIND, 'def reachable from the metadata-type roots; .*was LIVE'),
621+
);
622+
expect(eager.output).not.toMatch(rx(DELETED_BENEATH_UNREGISTERED_KIND, 'def not reachable from the'));
527623

528624
// Direction 2 — conservatism is not turned around. A REST response
529625
// envelope no metadata document is parsed against still reads unreachable
@@ -544,6 +640,8 @@ describe('build-schemas.ts — deleted baseline lines must prove themselves (#46
544640
expect(lazy.status).toBe(1);
545641
expect(lazy.output).toMatch(rx(DELETED_VIA_UNREGISTERED_KIND, 'def .*was LIVE \\(never tombstoned\\)'));
546642
expect(lazy.output).not.toMatch(rx(DELETED_VIA_UNREGISTERED_KIND, 'def not reachable from the'));
643+
expect(lazy.output).toMatch(rx(DELETED_BENEATH_UNREGISTERED_KIND, 'def .*was LIVE \\(never tombstoned\\)'));
644+
expect(lazy.output).not.toMatch(rx(DELETED_BENEATH_UNREGISTERED_KIND, 'def not reachable from the'));
547645
expect(lazy.output).toMatch(rx(DELETED_UNREACHABLE, 'def not reachable from the \\d+ metadata-type roots'));
548646

549647
expect(readSurface()).toBe(canonical);

0 commit comments

Comments
 (0)