Skip to content

test(plugin-sharing): seam ObjectQL double holds the caller limit - #28670

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-qk6ezf
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-qk6ezf

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread
Fixes #28608

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:plugin-sharing 测试用的内存 ObjectQL 替身(makeSeamQl().find)现在会遵守调用方传入的 limit,先按 where 过滤、再按 limit 截断。
  • 为什么改:真实引擎会遵守 limit,resolveAuthzContext 读这个替身时也确实传了 limit(如选默认组织时的 1 和 2);替身丢掉它,测试看到的行比运行时多,可能在运行时不会出现的结果上变绿。
  • 风险与回滚:只改测试文件,dist 不含这个 testkit,不发版;回滚即 revert 本 PR。
  • 先看:packages/plugins/plugin-sharing/src/exec-context-seam.testkit.ts(find 的 return 一行),packages/plugins/plugin-sharing/src/exec-context-seam.limit.test.ts。
  • 两分钟验证:pnpm --filter @objectstack/plugin-sharing exec vitest run src/exec-context-seam.limit.test.ts,预期 3 passed。
  • 需要你做的:无,席位落地。

What changes

  • makeSeamQl().find in exec-context-seam.testkit.ts now applies the caller's bound after the where filter, by presence: typeof opts?.limit === 'number' ? matched.slice(0, opts.limit) : matched. That is the first of the two shapes scripts/check-objectql-double-limit.mjs prescribes for a compliant double. It was picked over "throw on a bound it does not implement" because resolveAuthzContext really does read through this double with bounds (limit: 1, limit: 2, limit: 200 and others), so a throwing double would break every importer. where handling is unchanged.
  • makeSeamQl is now exported, only so its own pin can drive it directly.
  • New pin exec-context-seam.limit.test.ts: limit: 1 over two matching rows answers one row; the bound applies after the filter, not before it; no limit answers every match.

Why

The double silently dropped limit, so a test running through the seam could see more rows than the real engine would hand the resolver. Card #28608 question 1 (triage record 6557807357). Question 2 (widening the two double gates to walk *.testkit.ts) is graded out and not touched: neither gate script is edited.

Tests

  • Red first (export applied, fix not yet applied): answers at most limit rows when more match fails, expected [ Array(2) ] to deeply equal [ { id: 'a', …(1) } ], Tests 1 failed, 2 passed (3).
  • Green on the fix: Tests 3 passed (3).
  • Ablation through scripts/ablation-replace.mjs: the limit line replaced by return matched; (anchor x1 to x0, blob d27c845e10d6 to 17d1ac7833f6), the same case reds (1 failed, 2 passed), then restored: blob after restore equals the HEAD blob d27c845e10d6 and git diff HEAD is empty. The testkit is source imported relatively by the test, so no dist leg applies.
  • Whole @objectstack/plugin-sharing suite on head e410091: Test Files 59 passed (59), Tests 1299 passed (1299). The three test files that import the seam (sharing-service.test.ts, share-link-enforcement-context.test.ts, share-link-tenancy-posture-admission.test.ts, plus the type-only import in exec-context-annotation.pin.ts) pass unchanged.
  • pnpm --filter @objectstack/plugin-sharing typecheck: exit 0 (test layer compiles, debt held). eslint --no-inline-config packages/plugins/plugin-sharing: exit 0.

Acceptance notes

  • Gate-walk probe the card cites, re-run read-only: check-objectql-double-limit.mjs copied with its testFilesUnder regex widened to (test|testkit).ts, never committed and deleted after. On head: OK ObjectQL double limit conformance holds: 643 double(s) graded, no plugin-sharing row. On the base testkit blob, same probe: exec-context-seam.testkit.ts ... (1 blind ...) line 58: BLIND, exit 1. Restore proven by blob equality and an empty git diff HEAD.
  • skip-changeset: plugin-sharing's files[] is dist, README.md, CHANGELOG.md. After a build, makeSeamQl and bootRequestContext have 0 hits there, while the positive control SharingService has 4 files in dist.

Verification

Local subset on head e410091 (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 52 derived families), each exit captured before any pipe; reconciled with --ran: 52 derived famil(ies) accounted for — 51 run, 1 NOT-MEASURED.

  • 51 families exit 0, including check:objectql-double-limit, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:test-source-alias, check:type-check-debt, check:published-files and check:nul-bytes.
  • check-plugin-teardown-shape.mjs --self-test: exit 3 on the shallow clone first, exit 0 after fetching its pinned fixture commit.
  • check:i18n: exit 3 (CLI not built) first, exit 0 after its one-command prerequisite build (all bundles in sync).
  • NOT MEASURED: check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (needs every package dist in this fresh worktree); CI runs it.
  • Full dogfood not run: no ! in the title and no (narrowing) arm.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (scoped to plugin-sharing).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (nothing publishes: skip-changeset)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (the maintainer brief above is Chinese by the dispatch contract)
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5kA7pasGpR4Pf7mtNb


Generated by Claude Code

makeSeamQl().find filtered on `where` and dropped `limit`, so a test
running through the seam saw more rows than the real engine would hand
resolveAuthzContext. Slice the filtered rows by presence of a numeric
limit, the shape check-objectql-double-limit prescribes, and pin it.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 11 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 3944d2e12a1ee67e6ab484959e5da80b2708dd73 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c0027aad56b26a44a4e5e58e5cdd3ea4a3e92759 — the merge of head e410091fd04f197eb28655062947520b397f7508 into base 3944d2e12a1ee67e6ab484959e5da80b2708dd73, 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 c0027aad56b26a44a4e5e58e5cdd3ea4a3e92759 && git checkout c0027aad56b26a44a4e5e58e5cdd3ea4a3e92759
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3944d2e12a1ee67e6ab484959e5da80b2708dd73 e410091fd04f197eb28655062947520b397f7508 && git checkout -B drift-repro 3944d2e12a1ee67e6ab484959e5da80b2708dd73 && git merge --no-ff e410091fd04f197eb28655062947520b397f7508

node scripts/docs-audit/affected-docs.mjs --json 3944d2e12a1ee67e6ab484959e5da80b2708dd73

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-11-01T13:10Z

Head e410091f, report 6558195305 (on #28608), claim 6557864891, triage record 6557807357 (p3, question 1 only). No contract review is owed: test-only, no packages/spec/** path, no governed path, no narrowing, Clause-②: no.

item reading
Shape Draft, base main, Fixes #28608. Two files (+49 / −3), labels size/s, tests, skip-changeset. Clause-②: no, Metadata-protocol: none. @objectstack/plugin-sharing ships files: ['dist', 'README.md', 'CHANGELOG.md'], and the testkit is not reached from the package entry, so skip-changeset holds.
Diff check (seat) makeSeamQl().find keeps its where filter byte-for-byte and then applies limit by presence, after the filter (typeof opts?.limit === 'number' ? matched.slice(0, opts.limit) : matched). Slicing was chosen over throwing because resolveAuthzContext reads through the double with real bounds (1, 2, 200). makeSeamQl is now exported, and only the new pin exec-context-seam.limit.test.ts imports it: at most limit rows, the bound applied after the filter, and every match with no limit. Neither double gate script is touched (question 2 graded out).
Evidence CI green on head e410091f (filter=latest: 35 success, 18 skipped, 0 failed; Test Core, TypeScript Type Check, Lint & Repo Gates and Build Core all success). Governed check: NOT governed, 52 changed lines. The report gives red first and an ablation that goes red and restores clean, @objectstack/plugin-sharing at 1299/1299, 51 of 52 derived gates exit 0 with 1 NOT MEASURED (check:dual-build-cjs-loads, exit 3, needs all dists), and the card's widened-walk probe (temporary, uncommitted) reading OK on head and BLIND at line 58 on base. The importer count is measured at 3 test files plus 1 type-only import, not five, all unchanged and green.
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, once every check on this head is green or an expected skip.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 13:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 13:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 49a6a5c Nov 1, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-qk6ezf branch November 1, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test doubles: a limit-blind ObjectQL find double in a plugin-sharing testkit sits outside both double gates, which walk *.test.ts only

1 participant