Skip to content

test: the stage-8 test half — suites stop depending on the four catalog row objects (#15204 stage 8, S8-T) - #22962

Merged
os-zhuang merged 9 commits into
mainfrom
claude/issue-15204-s8t-tests
Oct 13, 2026
Merged

os-zhuang merged 9 commits into
mainfrom
claude/issue-15204-s8t-tests

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Clause-②: no

Part of #15204 (C3 stage 8, the test half: S8-T). The code half (S8) is built ahead on claude/issue-15204-s8-catalog-objects and lands right after this PR. #15204 remains open.

What this changes

Test files only. Every edit keeps the suite green on main and on main + S8, where the four security-catalog row objects (sys_position, sys_permission_set, sys_position_permission_set, sys_capability) are no longer registered (ADR-0131 D3).

  • A testkit for the retired tables. plugin-security/src/__tests__/legacy-catalog-objects.testkit.ts holds the four tables' shapes (fields, indexes, validations only), for suites that model an existing database whose rows outlive the object declarations until the organization-ownership migration (ADR-0131 D10). The suites that registered the object declarations import the shapes from it; withLegacyCatalogTables() adds the shapes a plugin no longer declares. plugin-auth's two grant-reader suites register the sys_permission_set shape the same way (plugin-auth/src/__tests__/legacy-catalog-objects.testkit.ts, a test-only copy: the plugin-security file is no export, and a relative import would make a test read another package's source) before seeding the platform-bucket rows.
  • Deleted, because they pin what S8 removes: the object-declaration pins (sys-*.organization-unique, sys-capability-deactivation-claim, clone-carryover, the junction/position cases of rbac-objects, reserved-identity-names, managed-by-system-data, sys-security-assignment-display-title, rbac-objects-bulk-exposure), position-write-through.test.ts (S8 unwires that middleware; 7b deletes the module), bootstrap-platform-admin-refused-existence-read.test.ts (the legacy-row read S8 removes), row-active.test.ts (S8 deletes isRowActive), pin 6 of packaged-permission-set-lock.test.ts, and the dogfood files for the data-door write-throughs, the projection, the row lock and the junction gates: two-doors-permission, declared-position-provenance, permission-set-lock-row-provenance, permission-set-write-through-package-binding, showcase-permission-projection, position-environment-write-through, position-held-name-setup-create. ensureLegacyPermissionSetRow leaves the dogfood catalog helper with its last callers. walled-catalog-data-door.test.ts (fix(security)!: under a wall the security catalog is the platform operator's — tenants are refused catalog writes and may only assign (ADR-0131 D3) #22895) stays with main's bytes: S8 deletes it together with the walled data-door refusal it pins.
  • Fixtures moved off the rows: dogfood suites that authored a permission set through POST /api/v1/data/sys_permission_set use PUT /api/v1/meta/permission/:name (or protocol.saveMetaItem); checks that a set "was seeded" read the security catalog (catalogPermissionSet) or the registry; vestigial sys_position row inserts are gone. mail-message-pool-read-gate reads its fixture sets from the catalog (split out and landed on main as test(dogfood): the inbound-mail pool suite reads its fixture sets from the security catalog #23159, b8f226c440, the same blob).
  • Pins made to span the change:
    • censuses of a retired table run only where the object is registered (showcase-permission-seeding, the runtime seeder-copy suite, permission-set-clone-door, showcase-d7-default-profile);
    • wall-catalog-refusal's data-door case answers 403 PERMISSION_DENIED while the row objects are registered and 404 OBJECT_NOT_FOUND once they are retired, with no definition either way;
    • builtin-positions.boot's walled golden keeps control: create a position row (403 PERMISSION_DENIED, the walled data-door refusal of walled-catalog-data-door.ts) wherever the plugin declares sys_position, and leaves it out only once the object is retired; every other key is unchanged;
    • cleanup-package-permissions.test.ts hands the sweep D's UninstallDeclared shape ({ permission: [...] }) and asserts what holds both ways; D's two named-grant pins are restated over this fixture;
    • the clone-route refusal prose is pinned around the route; the uninstall-cleanup runner request may carry declared; page/registry floors drop by the retired page and the four names (the record-page label-map floor is 38, with B2's two maps gone); the tenancy-inventory and dangling-reference fixtures use surviving security tables.
  • New pins:
    • bootstrap-platform-admin-fresh-boot.golden.test.ts: a fresh single-posture engine holding only the plugin's own objects promotes its first human (E6's first pin). E6's second pin is the existing grant-readers-unnamed-grant suite.
    • permission-metadata-door-reaches-catalog.dogfood.test.ts: a create, an edit and a delete through /api/v1/meta/permission each reach the security catalog the resolver reads (the premise E1 → A rested on).

Merge of main after D, and the re-check against D

Merge commit a805619472 takes main at f931e765b7 (D #22900 is c8a9783eca). No rebase, no force-push; generated files went through scripts/pm/os-regen-merge.sh (this branch touches none; the rerun's step 2 took nothing). Conflicts, all hand-written test files:

D changed after fcaedfdaa1, the base S8-T was first built against. Every case the stage-8 report pinned against D was re-checked on this merge:

case result
S8-T's deletions over D's edits taken; the deleted suites' assertions are what S8 removes
catalogPermissionSet reads in the four dogfood files kept; green on both trees
showcase-fls-read-mask-strip, showcase-permission-zoo S8-T's side (FLS also keeps main's catalog read); green on both trees
ensureLegacyPermissionSetRow deleted from the dogfood helper; no caller left (git grep)
showcase-permission-seeding census guarded by getObject('sys_permission_set'); green on both trees
plugin-security imports seedLegacyPlatformBucket plus the testkit; green on both trees
D's uninstall cleanup by catalog set name D landed UninstallDeclared ({ permission }), not the bare array S8-T was written against: cleanup-package-permissions.test.ts now passes that shape, D's two named-grant pins restated; green on both trees
E6 fresh-boot golden green on both trees; ablation below
E6 second pin (grant-readers-unnamed-grant, plugin-security and plugin-auth) green on both trees; plugin-auth's copy registers the legacy table before seeding
E1 premise (permission-metadata-door-reaches-catalog) green on both trees
uninstall-cleanup runner declared green on both trees

Suites main brought in or changed after S8-T's base that read the four objects, made to hold on both trees here: wall-catalog-refusal, mail-message-pool-read-gate, plugin-auth's two grant-reader suites (legacy table registered before the T2 bucket seed, seat ruling), builtin-positions.boot (walled create judged while sys_position is declared, seat ruling). walled-catalog-data-door.test.ts is kept with main's bytes: green on main + S8-T, red on main + S8-T + S8 by design, and S8 deletes it with the door.

Verification

Two trees, built locally through scripts/pm/os-verify-lock.sh:

  • main + S8-T: this branch.
  • main + S8-T + S8: a local, never-pushed scratch tree, S8's own commit dbae752f6e replayed onto this branch, its conflicts with D's final form resolved the way the S8 build-ahead resolved them (D's captureUninstallDeclared / UninstallDeclared kept; the walled data-door refusal unregistered with the write-throughs).
suite main + S8-T main + S8-T + S8
plugin-security, full 186 files, 3,831 passed, 45 skipped (at 9d55c0589a, walled-catalog-data-door.test.ts included) 185 files, 3,815 passed, 45 skipped, 4 failed in builtin-positions.boot (at S8-T 008ca32a75), fixed below
builtin-positions.boot 52 passed (at 9d9bdd96b2; the walled create key judged, 403 PERMISSION_DENIED) 52 passed (the key left out)
walled-catalog-data-door (main's bytes) 12 passed (at 9d9bdd96b2) red by design (Cannot find module './objects/sys-position.object.js'); S8 deletes it with the door
cleanup-package-permissions 10 passed 10 passed
plugin-auth grant-readers-by-name.golden, grant-readers-unnamed-grant 9 passed (at e40fe491c1) 9 failed at 008ca32a75 ("Object 'sys_permission_set' not found"); 9 passed at e40fe491c1
cli unit (organization-ownership-inventory, platform-page-i18n-parity) 35 passed 1 failed at 008ca32a75 (38 label maps under a floor of 39); 35 passed with the floor at 38
metadata-protocol (3 files) 89 passed 89 passed
objectql (2 files) 58 passed 58 passed
platform-objects (1 file) 20 passed 15 passed
lint, rest, runtime, spec (1 file each) 15, 11, 13, 11 passed 15, 11, 13, 11 passed
core src/security 37 files, 867 passed 37 files, 867 passed
typecheck 12 touched packages, 77 turbo tasks green (at d426f922ed); plugin-auth again at e40fe491c1 plugin-security, plugin-auth, dogfood: 67 tasks green; plugin-auth again at the final head

Every file changed after a full run was re-run on both trees at the final head. E6 ablation on the main + S8-T + S8 tree: with main's (D's) bootstrap-platform-admin.ts put back (blob edf2a164d9, on disk by hash), the fresh-boot golden goes red, 2 of 2 (adminPromoted: false); restored (blob 56166db769, git diff HEAD empty), green, 2 of 2.

Dogfood

PR and queue CI do not run the dogfood shards (#22998), so this is their first record before main's full battery. The 24 files S8-T touches or that read the four objects, on the showcase boot:

tree result
main + S8-T 23 of 24 files at 9d55c0589a, the red one mail-message-pool-read-gate (main's own red since D; this branch carries the hunk #23159 landed on main); at c0dc292ae8 that file and wall-catalog-refusal green, activity-text-predicate timed out once (5000 ms, contended box) and passed alone (22 of 22) at d426f922ed
main + S8-T + S8 24 of 24 files, 280 tests passed

No dogfood file changed after 008ca32a75.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 89 commands for this diff at 9d9bdd96b2 (the same 89 as the dispatch's list at 0d43004100). All 89 ran after the final commit and exit 0 (check:dual-build-cjs-loads first answered PREREQUISITE NOT MET, exit 3, and exit 0 once every package was built); --ran with each exit code recorded: "89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN". check:entry-nameability keeps its existing note that @objectstack/spec/api-assembled and /qa have no callable export to measure; packages/spec is untouched.

Size

93 files, +1,255 / −3,950 = 5,205 changed lines against the merge base f931e765b7, almost all deletion: over the 3,000-line line, so this lands by the maintainer's approval. mail-message-pool-read-gate leaves the diff at the next main merge (main carries the same blob).

skip-changeset: only test files, a test-only testkit under src/__tests__ (no entry imports it; files ships dist only) and the private dogfood package change.

维护者速读(草稿)

改了什么
C3 第 8 阶段的"测试半边"(S8-T),只改测试文件:93 个文件,+1,255 / −3,950 行,绝大部分是删除。四个已退役的安全目录行对象(sys_position、sys_permission_set、sys_position_permission_set、sys_capability)将在 S8 中取消注册;本 PR 先让测试不再依赖它们:删除只钉住"S8 要拆掉的行为"的测试,夹具改为走元数据门(/api/v1/meta)或读安全目录,少数断言改成无论对象是否注册都成立。本轮还把 D 落地后的 main 合进来,并修了 main 新带进来、依赖这四个对象的 5 个测试文件。

为什么改
ADR-0131 D3 规定权限集、岗位、能力只有一个归宿:安全目录(注册表)。S8 取消注册这四个行对象;测试若不先迁走,S8 一落地就会大面积变红。测试半边单独先落,S8 本身的代码改动才能保持可审。

风险与代价(含回滚)

席位意见

你要做的
批准本 PR(授权批准或亲自合并);之后席位接着落 S8。


Generated by Claude Code

…og row objects (#15204 stage 8, S8-T)

Every touched suite stays green on main and on main + D + S8, where
sys_position, sys_permission_set, sys_position_permission_set and
sys_capability are no longer registered (ADR-0131 D3).

- a testkit holds the retired tables' shapes for suites that model an
  existing database;
- pins of the retired declarations, the unwired data-door write-throughs,
  the projection, the row lock and the junction gates are deleted;
- fixtures author permission sets through the metadata door;
- prose and floor pins span the route move and the retired page;
- new: a fresh-boot promotion golden (E6) and a metadata-door-reaches-
  the-catalog dogfood test (the premise of E1).

Claude-Session: https://claude.ai/code/session_01MGoWgeE8rhsrig77V4oUZn
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 12, 2026
os-steve pushed a commit that referenced this pull request Oct 12, 2026
# Conflicts:
#	packages/plugins/plugin-security/src/bootstrap-platform-admin-authenticable-target.test.ts
#	packages/plugins/plugin-security/src/bootstrap-platform-admin-promotion-selection.test.ts
#	packages/plugins/plugin-security/src/bootstrap-platform-admin-refused-existence-read.test.ts
#	packages/plugins/plugin-security/src/objects/rbac-objects.test.ts
#	packages/plugins/plugin-security/src/per-organization-catalog.test.ts
#	packages/plugins/plugin-security/src/position-write-through.test.ts
#	packages/qa/dogfood/test/activity-text-predicate.dogfood.test.ts
#	packages/qa/dogfood/test/approval-snapshot-masked-field.dogfood.test.ts
#	packages/qa/dogfood/test/permission-set-lock-row-provenance.dogfood.test.ts
#	packages/qa/dogfood/test/permission-set-write-through-package-binding.dogfood.test.ts
#	packages/qa/dogfood/test/sharing-rule-org-less-caller.dogfood.test.ts
#	packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts
#	packages/qa/dogfood/test/showcase-fls-read-mask-strip.dogfood.test.ts
#	packages/qa/dogfood/test/showcase-permission-projection.dogfood.test.ts
#	packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts
#	packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts
This was referenced Oct 12, 2026
@objectstack-fleet objectstack-fleet Bot assigned os-steve and unassigned marchtian Oct 13, 2026
…8 test half resolved onto main

Conflicts resolved by the stage-8 report's D-merge recipe:
- S8-T's deletions taken over main's edits: bootstrap-platform-admin-
  refused-existence-read, position-write-through, and the dogfood files
  permission-set-lock-row-provenance, permission-set-write-through-
  package-binding, showcase-permission-projection, two-doors-permission;
  rbac-objects keeps S8-T's removal of the retired declaration pins.
- main's catalogPermissionSet reads kept in activity-text-predicate,
  approval-snapshot-masked-field, sharing-rule-org-less-caller and
  showcase-crud-persona-matrix.
- showcase-permission-zoo takes S8-T's side; showcase-fls-read-mask-strip
  takes S8-T's metadata-door authoring and keeps main's catalog read.
- plugin-security imports keep main's seedLegacyPlatformBucket and point
  the object shapes at the legacy-catalog-objects testkit.
- ensureLegacyPermissionSetRow leaves the dogfood catalog helper: every
  caller is deleted. showcase-permission-seeding's row census runs only
  where sys_permission_set is still registered.
- the runtime seeder-copy suite keeps main's "no row" pin beside S8-T's
  registered-object guard; the record-page label-map floor is restated
  for B2's two dropped maps and the retired position page.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
…he four catalog row objects

Three files that reached main after S8-T's base read the retired objects:
- walled-catalog-data-door.test.ts is deleted: it pins the walled
  catalog refusal's data-door registration on sys_position,
  sys_permission_set and sys_capability, which stage 8 unregisters with
  the write-throughs (the precedent of position-write-through.test.ts);
- wall-catalog-refusal's data-door case answers 403 PERMISSION_DENIED
  while the row objects are registered and 404 OBJECT_NOT_FOUND once
  they are retired, and asserts no definition either way;
- mail-message-pool-read-gate reads its fixture sets from the security
  catalog: no boot has written a sys_permission_set row since D, so the
  row read failed on main as well.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
os-steve pushed a commit that referenced this pull request Oct 13, 2026
…h: the stage-8 test half under the code half

S8-T is draft PR #22962 and carries a merge of main at f931e76, after D
(c8a9783). Clean merge, no file in common with stage 8's own diff; every
one of S8-T's 93 files here is byte-identical to its head. Until S8-T lands
on main this branch's diff also carries its hunks.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
Measured on a local main + S8-T + S8 tree:
- plugin-auth's two grant-reader suites seed the legacy platform-bucket
  rows only where sys_permission_set is still registered;
- builtin-positions expects the walled tenant create refusal while the
  plugin declares the sys_position row object, which stage 8 retires
  together with that refusal;
- the record-page label-map floor is 38, the count with B2's two maps and
  the retired position page gone.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
… data-door create out

Seat ruling: the walled scenarios no longer judge a tenant's create of the
hand-registered legacy sys_position row. That data-door refusal retires
with the object in stage 8, as walled-catalog-data-door.test.ts did, and
the walled create refusal is pinned at the /meta wall
(wall-catalog-refusal.dogfood.test.ts). Replaces the previous commit's
declares-the-object switch; every other key of the golden is unchanged.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
…ermission_set table before seeding its bucket rows

Seat ruling: the two suites model an existing database whose platform-bucket
rows outlive the object declaration, so each registers the legacy
sys_permission_set table shape (a test-only copy under src/__tests__, the
shape plugin-security's testkit holds) whenever the plugin no longer declares
it, and seeds the rows unconditionally. Replaces the previous commit's skip of
the seed after stage 8; the walled organization copies and the legacy-id
write read their rows on both trees again, as on main.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
…ed object list

The plugin's manifest hands its objects as unknown[], so the helper reads
each entry's name defensively; the test layer compiles again under
tsconfig.test.json (check:test-typecheck).

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e40fe491c1ae73ffe48e606877cd794a8d6b11fe
Local-runs: none

Read-only inputs: PR #22962's body, file list (94) and commits since 813dfed93e (the merge a805619472 of main f931e765b7, then six fast-forward commits); the net three-dot diff against main (94 files, +1243 / −4180, merge base f931e765b7); card #23056 (body and 11 comments, the claim 6136942521, the S8 dispatch 6137095247, the main-red ask 6137492570 and PR #23159, the dev report 6140041869); #15204's 6118994290, 6110777865 and 6116546399; AGENTS.md's deletion clause (ruling record 6120820653); the 48 check-runs on the head. main has since moved to d4aaeaf0e2 (232 files); of the 94, only mail-message-pool-read-gate moved on main, and to the same blob.

① Derived judgments

Test-only, confirmed. 74 modified, 16 removed, 4 added. Every path is a *.test.ts, a test helper or config of the private dogfood package (test/catalog-permission-set.ts, vitest.config.ts), or a testkit under src/__tests__/. No product source.

The 16 deletions against E1 → A, E3 → A, E4 → A and the deletion clause. Reading applied: the epic seat ruled "Tests go in T-PRs ahead of" stage 8 (6110777865), so a pin whose code S8 removes is deleted here; the clause is then met in substance, not in letter, and the window between S8-T and S8 is the ruled cost. Per file:

No deleted file pins a behaviour that survives S8 without a pin at a layer at least as close to the code. Not a FAIL.

The clause finding, walled-catalog-data-door.test.ts. On main the door is registered (security-plugin.ts:4367-4369, one middleware per object in WALLED_CATALOG_OBJECT_TYPES). The deleted file pins six things: the walled create refusal; the walled update and delete refusal with the row untouched; the operator rung and a system write admitted; a tenant rung below the operator refused; the judged set being exactly the three catalog objects. After this PR the only remaining pin is the data-door case of wall-catalog-refusal.dogfood.test.ts, create only, now 403 PERMISSION_DENIED while the row objects are registered and 404 OBJECT_NOT_FOUND after. The /meta wall case in the same file pins a different code path, the metadata door's wall (#22935, #23003). So one case of six remains, at the dogfood layer, which PR and queue CI do not run (#22998). Verdict on the clause: not met in letter; met in substance only under the ruled T-PR design and because S8 unregisters the door with the write-throughs; the five other cases have no pin anywhere in the window. The PR body and the comments in builtin-positions.boot.test.ts (at the dropped key and in refusalGoldenFor) say the walled create refusal "stays pinned at the /meta wall": that names the wrong door. Fix: (a) restore the file in S8-T, which is main's bytes and green on main, and delete it in S8 together with the door (the S8 build-ahead already resolves the door's unregistration, c211fdd416); or (b) the seat records on the PR that the clause is waived for a pin whose code the next PR removes, and corrects the attribution. The attribution correction blocks; the restore is recommended under the ruled design and becomes mandatory if S8 does not land right after S8-T.

Weakened or removed assertions in kept files.

  • builtin-positions.boot.test.ts: withLegacyCatalogTables registers the four tables beside securityObjects; the walled scenarios drop the golden key control: create a position row (was 403 PERMISSION_DENIED) on both trees, while the insert still runs. Every other key is unchanged: the two built-in row refusals, the dangling and row-only assignment refusals, the stack-declared assignment control. The dropped key could have been kept where the plugin declares sys_position, the registration-guarded pattern this PR uses for its censuses, which keeps the unit pin on main at no cost on the S8 tree. Not a mask; a dropped pin on main for the window.
  • wall-catalog-refusal.dogfood.test.ts: the data-door case spans by ql.getObject?.(object); a missing getObject would make the test expect 404 on main and fail loudly, not pass vacuously. Verified on main: the suite exists and pins the walled /meta create, save and delete refusals (403 FORBIDDEN), the data-door create refusal (403 PERMISSION_DENIED), the clone-door refusal, and the assign and operator controls; the /meta, clone and control cases are unchanged here.
  • plugin-auth grant-readers-unnamed-grant and grant-readers-by-name.golden: withLegacyPermissionSetTable adds the sys_permission_set shape only when the manifest does not declare it; seedLegacyPlatformBucket then writes the platform-bucket rows, and the suite reads a seeded row's id to write a legacy permission_set_id (the case "writing the legacy id moves no standing"). The seed is meaningful: it models the existing database whose grants carry legacy ids (ADR-0131 D10). The shape is reached through a copy inside plugin-auth (src/__tests__/legacy-catalog-objects.testkit.ts, one object), not a relative import out of the package, so check:cross-package-test-inputs has nothing to flag; it sits in the green Lint & Repo Gates. Within the rules.
  • cleanup-package-permissions.test.ts: the sweep is handed D's UninstallDeclared shape { permission: [...] }; the fixture drops the sys_position_permission_set rows and gives every grant a name; the exact totals (sets, positionBindings, the legacy-id userGrants) become toMatchObject on the name-leg fields and refused becomes toBeGreaterThanOrEqual(4) (was 7). D's two named-grant pins are restated: "revokes every grant naming a declared set, and only those" is exact (4 grants, exact survivors); the control "handed no names" keeps the pin that a grant naming an un-handed set survives (arrayContaining over the un-handed grants). Spans correctly; the counts should be re-tightened after S8.
  • catalog-reference-report.test.ts: resolves for twin_lead (the position backfill's doing) is no longer asserted; the backfill-ledger case is deleted; the kernel:bootstrapped handler floor goes 3 to 2. grant-permission-set-name-backfill.test.ts: handlers toHaveLength(3) becomes toBeGreaterThanOrEqual(2). Spans; re-tighten after S8.
  • tenant-wall-post-hook-image.test.ts: the [#20013] ADR-0094 data-door case is removed (the door S8 unwires); the tenant-wall post-hook cases stay.
  • The clone-route prose pins (protocol.packaged-base-refusal, sys-metadata-repository.package-writability, rest-meta-packaged-action-permission-refusal): the sentence is pinned up to POST /api/v1/ and from with a new name)., with sentenceMatches requiring one /api/v1/ path between. Spans; pin the clone door's route after S8.
  • Floors: platform-page-i18n-parity label maps 44 to 38 (measured on main + S8) and record pages 3 to 2; organization-ownership-inventory registry 80 to 75; canonical-expression-envelopes 3 to 2. Floors, not masks.
  • tenancy-by-object-classification.test.ts: the sys_permission_set global assertions become a loop over PLATFORM_OBJECT_TENANCY; after S8 the inventory holds no global entry, so the discriminating half of Observation: package permission sets are materialized with a tenant-less system context, so sys_permission_set rows land organization-less #8672 then pins nothing. Accept, or re-pin with a fixture entry after S8.
  • validate-capability-references.test.ts: the sys_capability seed-row source case is removed (S8 removes that source); the one-line verdict is pinned by prefix and suffix. me-apps-and-everyone-baseline: the read "no everyone row is written" is dropped rather than registration-guarded; the unit pin stays in builtin-positions.boot (lines 512 and 523). showcase-permission-zoo: the three junction-write gate cases (ADR-0090 D5/D9/D12 on sys_position_permission_set rows) go with the object; see ③ item 5. get-effective-object-permissions: the parity case moves from sys_position/sys_permission_set to sys_user_position/sys_user_permission_set, which organization_admin still names read-only (the kept rbac-objects loop). dangling-reference-audit: the fixture is renamed to a surviving table; the shape is the fixture's own.
  • Censuses guarded by getObject (showcase-permission-seeding, builtin-capabilities.boot, permission-set-clone-door, showcase-d7-default-profile, standalone-stack-seeder-declaration-copy): the "no row" pins hold on main and are vacuous after S8, by design.

The testkits. plugin-security/src/__tests__/legacy-catalog-objects.testkit.ts (521 lines): four ObjectSchema.create declarations with fields, indexes, the enable flags and, for sys_position, the reserved_identity_name validation copied from main's object; LEGACY_CATALOG_OBJECTS and withLegacyCatalogTables(); no hooks, actions, list views or functions beyond the registration filter. Field lists match main's objects for sys_position, sys_capability and sys_position_permission_set; sys_permission_set carries two columns main's object no longer declares, drift_status and drift_detail (present at S8-T's base eab7236ff7, removed by D c8a9783eca). No suite reads them; harmless, but the header's "as the last release that declared the object stored them" should say which release. The plugin-auth copy: one object, fields and indexes, withLegacyPermissionSetTable() over the manifest's untyped list. Neither masks behaviour: the plugin's own middleware is what the suites exercise; the tables are only the rows' home.

The mail-pool hunk. packages/qa/dogfood/test/mail-message-pool-read-gate.dogfood.test.ts is blob adb8b0fa73 on the head and on current origin/main (b8f226c440, PR #23159); the two-dot diff is empty. A no-op at the next main merge.

Both trees. Tree A (main + S8-T) is CI-verified on this head: Test Core 1/6 to 6/6, the Type Check lanes and every Lint lane are success. Tree B (main + S8-T + S8) exists only as the dev's never-pushed scratch tree. The dev's account names each file the S8 build-ahead reported red, with counts at shas: builtin-positions.boot 4 failed at 008ca32a75, 52/52 at d426f922ed; the plugin-auth pair 9 failed at 008ca32a75 ("Object 'sys_permission_set' not found"), 9/9 at e40fe491c1; cli 1 failed (38 maps under a floor of 39), 35/35 at the floor of 38; plus plugin-security 185 files / 3815 passed, dogfood 24/24 (280 tests), typecheck 67 tasks. Declared narrowing: those changed unit files and the plugin-auth typecheck ran outside the verify lock at --maxWorkers=1. The claim is supported by the evidence, file by file, and not verified here; nothing in the diff contradicts it. The dogfood shards did not run on this head (skipped under #22998), so the 22 dogfood files have no CI record before main's next full battery.

② Semver level

No changeset, skip-changeset, Clause-②: no. Nothing published moves: every file is a test, a test helper or config of the private @objectstack/dogfood, or a testkit under src/__tests__/ of @objectstack/plugin-security or @objectstack/plugin-auth. Both packages build with tsup from their entry points (exports ., and ./rate-limit-storage for plugin-auth); no non-test source imports a testkit, so none reaches dist, and files ships dist only. packages/spec/src/system/constants/system-names.test.ts loses its POSITION and PERMISSION_SET assertions while the constants stay (S8 removes them). Confirmed.

③ Boundary flags

  1. Act before the queue: the PR body's pin attribution. The body says the walled create refusal "stays pinned at the /meta wall by wall-catalog-refusal.dogfood.test.ts" (and builtin-positions.boot.test.ts repeats it in two comments). That names the wrong door: the data-door refusal (walled-catalog-data-door.ts, fix(security)!: under a wall the security catalog is the platform operator's — tenants are refused catalog writes and may only assign (ADR-0131 D3) #22895) is pinned only by that file's data-door case, registration-conditional, dogfood layer. Correct the body sentence; the test comments can follow in S8 or with the restore in item 2.
  2. The deletion clause, walled-catalog-data-door.test.ts. Not met in letter (①). Recommended: restore the file in S8-T and delete it in S8 with the door. If the seat instead rules the clause satisfied in substance for a pin whose code the next PR removes, that ruling belongs on the PR, not only in the dev report's deviations. Either way, if S8 does not land right after S8-T the restore stops being optional: five of the door's six cases are unpinned in the window and PR and queue CI run no dogfood.
  3. builtin-positions.boot's walled create key: dropped on main where a registration-guarded span was available. Recommended with the restore; not blocking.
  4. Spans to re-tighten after S8, needing a carrier (the S8 dispatch forbids S8 from editing S8-T's files, so 8-post): cleanup-package-permissions (toMatchObject, toBeGreaterThanOrEqual(4)), grant-permission-set-name-backfill (handlers at least 2), catalog-reference-report (handlers at least 2, objectContaining), the clone-route prose pins (/api/v1/ prefix), wall-catalog-refusal (403 or 404 by registration), tenancy-by-object-classification (no global entry), protocol.uninstall-cleanups-runner (objectContaining(request)). None masks a regression on main; each is looser than before.
  5. Junction gates retired without a metadata-door counterpart pinned. ADR-0090 D5/D9 (a high-privilege set bound to the everyone anchor is refused) and D12 (manageBindings: false) were pinned on sys_position_permission_set writes in showcase-permission-zoo; after S8 a position's sets are its definition's permissionSets. Whether the metadata door enforces the same is NOT MEASURED here; the stage-8 report lists the delegated-admin gate and the SP catalog gates as 8-post residue. Name it on 8-post.
  6. Dev deviations, answered. The scratch tree by cherry-pick: acceptable, the S8 branch carries a superseded D. The walled door aligned with c211fdd416 before the ruling: consistent with the ruling that followed. Unlocked runs for the changed unit files: a declared narrowing; tree A is CI-covered. Two commits superseded by rulings (c0dc292ae8 by d426f922ed; 08524a1563 widened by e40fe491c1): history kept. The walled-door clause deviation: item 2. test(dogfood): the inbound-mail pool suite reads its fixture sets from the security catalog #23159's body cites the earlier product tree; the report carries the 121847a7d5 run. main moved after the merge (now d4aaeaf0e2): the overlap is the identical mail-pool blob; no test file added on main since f931e765b7 reads the four objects; modified ones were not swept here. Model-free Co-Authored-By trailers on all seven commits since the handoff, as the card requires. Open questions: none. Out-of-scope findings: test(dogfood): the inbound-mail pool suite reads its fixture sets from the security catalog #23159 is landed; the S8 scratch-resolution note is S8's; check:type-check-debt's unlocked build is an acceptance note.
  7. Check-runs on e40fe491c1: 48, 38 success, 10 skipped, none running. Skipped: Check Changeset (skip-changeset), the three Dogfood Regression Gate shards, Dogfood Verify CLI, Temporal Conformance, Build Docs, Console Pin Gate, Packed-tarball smoke, and the label-triggered re-runs of Auto Label and Check PR Size (their first runs success). The gate verdicts are green; the dogfood half of this PR has no CI verdict until main's full battery, which the seat reads after the merge as it did for test(dogfood): the inbound-mail pool suite reads its fixture sets from the security catalog #23159.
  8. Form. Still a draft; Tier H by size (5,423 changed lines, 77% deletion), so the maintainer's approval or merge is required; the 维护者速读 is marked 草稿 with 席位意见 empty, for the seat to fill before readying. NOT MEASURED: tree B (no CI run exists); the dogfood files on this head (no CI run exists); the testkit's drift_status and drift_detail columns' effect on any upgrade-path suite (none reads them).

Adopted by domain:engine seat 2 at 2026-10-13T07:16Z, after a tool-use audit of the isolated reviewer's transcript: reads, git fetch into a review ref, and one record write. It made no GitHub write and ran no build, test or gate. The seat takes both act-before-queue items as blocking, and the recommended one too. The S8-T dev runs a patch round: walled-catalog-data-door.test.ts is restored with main's bytes, and S8 deletes it together with the door it pins, so AGENTS.md's deletion clause is met in letter. builtin-positions.boot's walled create control is kept wherever the plugin declares sys_position, and its comments name the right door. The seat corrects the PR body's door sentence. A delta record runs on the new head before the Tier H kit.

Implemented-by: claude/issue-15204-s8t-tests
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

…sitions judges the walled create while sys_position is declared

Patch round 1 (contract review on e40fe49, seat ruling):
- walled-catalog-data-door.test.ts is restored with main's bytes: five of
  its six cases have no other pin until stage 8 removes the door, and the
  deletion clause asks for one. Stage 8 deletes it together with the door.
- builtin-positions.boot keeps the walled `control: create a position row`
  key (403 PERMISSION_DENIED) wherever the plugin declares sys_position, and
  leaves it out only once the object is retired. Its comments name the door
  that refuses the create: walled-catalog-data-door.ts, pinned by that suite
  and by wall-catalog-refusal's data-door case. Every other key unchanged.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9d9bdd96b2457b51ff32a86c3928c73c5771ad98
Local-runs: none

Delta record after the PASS 6140381269 on e40fe491c1. Read-only inputs: PR #22962's body (as edited by the seat for patch round 1, updated_at 2026-10-13T08:10Z), its file list (93) and its nine commits; the delta e40fe491c1..9d9bdd96b2 (one fast-forward commit, parent e40fe491c1, committed 2026-10-13T07:18Z); the net diff against main (merge base f931e765b7; origin/main read at d889448b93); card #23056's body and all 13 comments (the claim 6136942521, the S8 dispatch 6137095247, the dev reports 6140041869 and 6141188275, the seat's correction 6140398039); the prior record 6140381269 and the docs-drift comment on the PR; the 48 check-runs on this head; packages/plugins/plugin-security/src/walled-catalog-data-door.test.ts, walled-catalog-data-door.ts, security-plugin.ts and manifest.ts on origin/main. Nothing built, run or re-run.

① Derived judgments

The delta is exactly the two files the patch round named. git diff --name-status e40fe491c1..9d9bdd96b2: A packages/plugins/plugin-security/src/walled-catalog-data-door.test.ts, M packages/plugins/plugin-security/src/builtin-positions.boot.test.ts; nothing else. The restored file is blob 003a885f3f on the head, on origin/main and at the merge base f931e765b7, so it leaves the PR diff (the three-dot diff against origin/main and the two-dot diff against f931e765b7 both read 93 files, 74 modified, 15 removed, 4 added, +1,255 / −3,950; the prior record's 94 was this file as the 16th removal). No main merge, no rebase, no force-push: e40fe491c1 is the head's parent and the branch's eight earlier commits are unchanged. No other change exists to review.

builtin-positions.boot is a faithful both-trees pin, and no weakening on main. The guard is PLUGIN_DECLARES_POSITION_ROWS, a module-level securityObjects.some(...) over the object name sys_position, read off ./manifest.js's securityObjects, the plugin's own declared object list (on main it lists SysPosition first; withLegacyCatalogTables adds only the tables that list does not declare, so it does not feed the guard). judgesCreate(scenario) is posture === 'single' || PLUGIN_DECLARES_POSITION_ROWS; refusalGoldenFor for a walled scenario is { ...REFUSAL_GOLDEN, 'control: create a position row': { ok: false, code: 'PERMISSION_DENIED', status: 403 } } while the object is declared, which is byte-equal to main's golden at f931e765b7, and the create-less golden only once it is not. On main the observed key set and the golden are therefore identical to main's own: the walled create is judged and expected 403 PERMISSION_DENIED in all four walled scenarios, the single scenarios keep ok: true, and every other key (the dangling and row-only assignment refusals, the stack-declared control, the two built-in row refusals) is untouched. On the S8 tree the key leaves; the insert still runs and is not asserted, which is the designed vacuity. The guard is a proxy: the door itself is registered by security-plugin.ts once per key of WALLED_CATALOG_OBJECT_TYPES, not by the object list, so a tree that retired the object and kept the door would drop the key silently. That tree is not S8's design, and the restored unit suite imports both ./walled-catalog-data-door.js and ./objects/sys-position.object.js, so a tree holding one without the other reds at import instead of passing quietly; the pair covers the combination. The three comments (at the dropped key, in the REFUSAL_GOLDEN docblock, in refusalGoldenFor) now name walled-catalog-data-door.ts as the refusing door and the two pins (walled-catalog-data-door.test.ts, the data-door case of wall-catalog-refusal.dogfood.test.ts); no comment in the file names the /meta wall for the data-door create any more.

The deletion clause (ruling record 6120820653, as the prior record read it under 6110777865). The walled finding is closed: walled-catalog-data-door.test.ts is not deleted, so its six cases (the walled create of a position, a set and a capability refused with nothing stored; update and delete refused with the row untouched; the assign control; the operator rung and a system write admitted; a tenant rung below the operator refused; the judged set exactly the three catalog objects) stay on main at the unit layer, in PR and queue CI, until S8 deletes the suite with the door. The 15 remaining deletions are unchanged from the prior record's per-file reading, and nothing in the delta moves it: the deletions whose halves survive S8 name a duplicate at a layer at least as close to the code (clone-carryover and pin 6 of packaged-permission-set-lock by runtime/src/domains/permission-set-clone.test.ts; the dogfood write-through, row-lock and projection files by the metadata-protocol, plugin-security and objectql unit pins the prior record lists; permission-set-lock-row-provenance by the kept packaged-permission-set-lock.test.ts), so the clause is met in letter for them; the deletions that pin only what S8 removes (position-write-through, row-active, bootstrap-platform-admin-refused-existence-read, the declaration-shape and string pins, the junction-gate dogfood cases) have no surviving assertion to duplicate and rest on the ruled T-PR design, met in substance as the prior record said. That class is not new on this head and was not a FAIL before; it is not one now. What is new is that no deleted file leaves a behaviour unpinned in the window that is not itself removed by S8.

The PR body's corrected passages are true at this head. The Deleted bullet no longer lists the walled suite among the deletions and ends with it staying at main's bytes, S8 deleting it with the door: true. The builtin-positions span item (the key kept, 403 PERMISSION_DENIED, the refusal attributed to walled-catalog-data-door.ts, left out once the object is retired, every other key unchanged): true, as read above. The "Suites main brought in" paragraph (five suites made to hold on both trees, the walled suite kept and red on the S8 tree by design): true; the 维护者速读's "5 个测试文件" matches its five names. The verification rows: plugin-security full at 9d55c0589a with the walled suite included (the suite was still present at that commit, deleted at 008ca32a75, restored here), builtin-positions.boot 52 passed at 9d9bdd96b2 with the key judged, walled-catalog-data-door 12 passed at 9d9bdd96b2 and red at import on the S8 tree: consistent with the file's twelve cases (two postures by five, one single control, one judged-set case) and with its import of sys-position.object.js; the counts are the dev's, weighed not repeated. The Gates paragraph names 9d9bdd96b2 and the 89 derived commands with the check:dual-build-cjs-loads exit-3-then-0 note: consistent with the patch-round report. The Size line reads 93 files, +1,255 / −3,950 = 5,205: measured here both ways; 3,950 / 5,205 is 75.9 percent, so the 速读's "约 76% 是删除" is right and "改了什么" carries the same figures. The 风险与代价 window bullet names walled-catalog-data-door.ts, the kept suite and builtin-positions.boot as its pins: true. No other body sentence attributes the walled create refusal to the /meta wall: the three remaining /meta mentions are the fixtures bullet (PUT /api/v1/meta/permission/:name), the E1 premise pin and the 速读's 夹具改为走元数据门, each about the metadata door's own cases.

The prior record's ① judgments hold on this head. Test-only: every one of the 93 paths is a *.test.ts, a test helper or config of the private dogfood package, or a testkit under src/__tests__/; the delta touched two test files. The kept-file assertion list is unchanged except builtin-positions.boot, which is no longer a dropped pin on main. The two testkits are unchanged (the drift_status and drift_detail columns note stands). The mail-pool hunk is still blob adb8b0fa73 on the head and on origin/main at d889448b93, a no-op at the next merge; main moved 900 files since f931e765b7 and overlaps the PR on that file only; no test file added on main since the merge base names the four objects, and no modified test file outside the PR adds a mention of them. Tree A (main + S8-T) is CI-verified on this head. Tree B remains the dev's never-pushed scratch, re-created for this round: builtin-positions.boot 52/52 both trees, the walled suite 12/12 on A and red at import on B by design, plugin-security typecheck green, 89 of 89 gates exit 0; supported by the diff, not verified here.

② Semver level

Unchanged. No changeset, skip-changeset, Clause-②: no. The delta adds nothing to the published surface: the restored file is main's own test and leaves the diff, and the other file is a test. Nothing published moves; the prior record's reading of exports, files and the testkits' reach stands.

③ Boundary flags

  1. Prior act-before-queue item 1, the body's door attribution: resolved. The Deleted bullet, the builtin-positions item, the "Suites main brought in" paragraph and the 速读's window bullet name walled-catalog-data-door.ts; the test file's three comments were corrected on this head. Nothing left to act on.
  2. Prior act-before-queue item 2, the deletion clause for walled-catalog-data-door.test.ts: resolved. Restored at main's exact bytes (blob 003a885f3f); the clause is met in letter for that file, and its six cases are pinned on main at the unit layer through the window. The "becomes mandatory if S8 does not land right after S8-T" condition no longer applies.
  3. Prior recommended item 3, builtin-positions.boot's walled create key: taken, as a registration-guarded span read off the plugin's own object list; no weakening on main (①).
  4. Follow-ups for 8-post (C3 #15204 stages 7a ∥ 7b → 8-post, build ahead: delete the permission-set projection and the position write-through that stage 8 leaves unreferenced, then the post-stage-8 catalog gates and residue #22975, after S8): recorded on C3 #15204 stage 8: S8-T #22962 + S8 retire the four catalog row objects; both Tier H; land right after D #22900 (handed to the engine lane) #23056 at 6140398039, as the prior record named them: the spans to re-tighten (cleanup-package-permissions's toMatchObject and refused at least 4; the two handler floors at least 2; the clone-route prose prefix pins; wall-catalog-refusal's 403 or 404 by registration; tenancy-by-object-classification's missing global entry; protocol.uninstall-cleanups-runner's objectContaining), and the ADR-0090 D5 / D9 / D12 junction gates retired from showcase-permission-zoo with no metadata-door counterpart pinned, still NOT MEASURED. Open by design; the carrier is named.
  5. New, for S8 (not blocking S8-T). walled-catalog-data-door.test.ts is now main's file and not an S8-T file, so the S8 dispatch's "S8-T's files change only by merging its branch" does not cover it: S8's PR deletes it together with walled-catalog-data-door.ts and its registration in security-plugin.ts, and S8's body names that deletion under the clause (the behaviour leaves with the door, the same ruled reading). Until S8 does, the S8 tree is red at import in that file, by design, which is one more reason S8 lands right after S8-T. The dev report says the seat has told the S8 dev; that instruction is not in the S8 dispatch comment and should be in S8's own record.
  6. Check-runs on 9d9bdd96b2: 48; 38 success, 10 skipped, none running. The five required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard) are all success; Test Core 1/6 to 6/6, the four Type Check lanes and every Lint lane are success. Skipped: Check Changeset (by label), the three Dogfood Regression Gate shards, Dogfood Verify CLI, Temporal Conformance, Build Docs, Console Pin Gate, Packed-tarball smoke, and the label-triggered re-runs of Auto Label and Check PR Size whose first runs are success. The gate verdicts are green; the dogfood half of this PR still has no CI verdict before main's full battery, which the seat reads after the merge.
  7. Form. Still a draft, auto-merge not armed, no review on the PR, head repository equal to base repository, assignee os-steve as the card's. Tier H by size: 5,205 changed lines, over 3,000, zero governed paths, so the maintainer's approval or merge is required and no seat readies, queues or arms it before that word; the 维护者速读 is still headed 草稿 with 席位意见 empty, for the seat to fill before readying. The head commit carries the model-free trailer pair. NOT MEASURED: tree B (no CI run exists); the 22 dogfood files on this head (no CI run exists); a merge of main at d889448b93 into the branch (not done; the only overlap is the identical mail-pool blob, so the next merge is expected to touch none of the PR's files, but that is an expectation, not a reading).

Adopted by domain:engine seat 2 at 2026-10-13T08:18Z, after a tool-use audit of the isolated reviewer's transcript: reads, git fetch into a review ref, and one record write. It made no GitHub write and ran no build, test or gate. This record is the landing record for this head; 6140381269 stays as history.

Implemented-by: claude/issue-15204-s8t-tests
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 · PR #22962(S8-T,C3 第 8 阶段的测试半边)· domain:engine 2 号席 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-13T08:20Z

改了什么
只改测试文件:93 个文件,+1,255 / −3,950 行,约 76% 是删除,没有一行产品代码,也没有 changeset。S8 会取消注册四个已退役的安全目录行对象(sys_position、sys_permission_set、sys_position_permission_set、sys_capability)。本 PR 先让测试不再依赖它们:

  • 删掉只钉着"S8 要拆掉的行为"的测试;
  • 夹具改为走元数据入口(/api/v1/meta),或直接读安全目录;
  • 少数断言改成无论这些对象是否还注册都成立。

D(#22900)落地后的 main 已合入,main 新带进来、依赖这四个对象的测试文件也一并修好。

为什么改
ADR-0131 D3 规定,权限集、职位、能力只有一个归宿:安全目录(注册表)。测试不先迁走,S8 一落地就会大面积变红。测试半边先单独落地,S8 本身的代码改动才能保持可审。

风险与代价(含回滚)

席位意见
建议批准。依据:

  • 席位自己逐文件读过 diff:全是测试,删除的每个文件都对得上 S8 要拆的东西;
  • 契约审查 PASS(6140381269),它提出的两项入队前要处理的事已在补丁里改完;补丁这一轮的增量审查同样 PASS(6141353282);
  • CI 全绿;和 main 无冲突,没碰受管路径。

超过 3000 行,按规则要你批准,这是唯一原因。

你要做的
用 os-zhuang 或 hotlong 批准本 PR(或者亲自合并)。批准后,席位转 ready、送进合并队列,然后接着落 S8。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

protocol:system size/xl 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.

4 participants