Repository navigation
test(spec): contracts and conversions test titles state each cited decision in words instead of a tracker number (stage 13) - #21763
Merged
Conversation
…cision in words instead of a tracker number Each tracker id in a test title or test string under packages/spec/src/contracts and packages/spec/src/conversions now either states what its record decided, in words, or is dropped where the title already says it. Text only: no assertion, identifier, test count or code comment changes. One id stays: the '#16495' needle in approval-service.test.ts asserts the contract docblock's own citation. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Oct 5, 2026
This was referenced Oct 5, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
… contributeOwnershipFloorAlternates as optional, feature-detected members (objectstack-ai#21781) Fixes objectstack-ai#21756 Clause-②: yes (widening) `ISecurityService` now declares the two members that the registered `security` service already served without a declaration: `discardPermissionSetOverlay` and `contributeOwnershipFloorAlternates`. Both are **optional**, documented as feature-detected, and pinned by a contract-test row each. A new test-only enumeration pin in `plugin-security` turns red, by name, when the registered service serves a member that is neither declared on the contract nor ledgered with a reason. This follows the direction triage set (`5981803299`) and the claim `5982111525`. No runtime source changes, and no behaviour changes. ## Measured first: what is served vs what is declared Read at base `a6a7547074`. `registeredSecurityService` in `packages/plugins/plugin-security/src/security-plugin.ts` (`:1881` typed literal + `:2106` `Object.assign` extension) serves 21 members. I measured them by enumerating the object the real plugin registers, not by reading the source: `canExport`, `canReadObject`, `checkAuthoredRowWrite`, `confirmAudienceBindingSuggestion`, `contributeOwnershipFloorAlternates`, `describeDelegableScope`, `describeDelegationNarrowing`, `discardPermissionSetOverlay`, `dismissAudienceBindingSuggestion`, `explain`, `getEffectiveObjectPermissions`, `getMetadataReadableFields`, `getQueryableFields`, `getReadFilter`, `getReadableFields`, `getWritableFields`, `hasWriteBypass`, `listAudienceBindingSuggestions`, `resolvePermissionSetNames`, `resolvePermissionSetsForContext`, `resolveWriteScope`. - **Served but not declared:** exactly the two this card names. Every other member is in the typed literal, so the compiler already holds it to the contract. No third member was found, so the pin's ledger is empty. - **Declared but not served:** none. All 19 previously declared members (11 required, 8 optional) are served. ### The two callers and what the members really refuse - `discardPermissionSetOverlay(callerContext, id)`: called by `packages/rest/src/rest-server.ts` (`POST …/security/permission-sets/:id/discard-overlay`, about `:12699`). The route feature-detects it and answers `501 NOT_IMPLEMENTED` when it is absent. The implementation (`permission-set-overlay-discard.ts`) refuses with `PERMISSION_DENIED` 403 (the caller is not a tenant-level admin, or no installed package declares the set), `NOT_FOUND` 404 (unknown row) and `INVALID_STATE` 409 (no active overlay). The last two are the `ERROR_CODE_LEDGER['@objectstack/plugin-security']` rows. A refused re-projection write still resolves (`healedObjectGrantCount` then equals the pre-discard count). The docblock says so. - `contributeOwnershipFloorAlternates(plugin, alternates)`: called at boot by `packages/services/service-storage/src/attachment-delete-floor-alternate.ts`, through a local seam interface and feature detection. The implementation (`ownership-floor-alternates.ts`) throws a plain `Error` with no registered code when: `plugin` is empty, the list is not an array, an alternate names no object or names `'*'`, its operation is not exactly `update` or `delete`, its `using` is missing, or the policy does not parse as `RowLevelSecurityPolicySchema`. ## What changed - `packages/spec/src/contracts/security-service.ts`: two optional members, documented in the `getMetadataReadableFields` pattern (what each does, what it refuses with which codes, that callers feature-detect, that the unguarded call does not compile). Two parameter and result types are plugin-internal (`PermissionSetOverlayDiscardResult` and `OwnershipFloorAlternate` in `plugin-security`), so the spec declares the **minimal contract shape** of each under the same name, and the docblocks say so. They are the only new public exports, both type-only (`api-surface/contracts.json` +2, `export-origins/contracts.json` +2, regenerated by `check:generated --fix`, not by hand). - `packages/spec/src/contracts/security-service.test.ts`: one contract-test row per member, appended after the existing rows. Each shows that absence is typed (the unguarded call is a `@ts-expect-error`), how the caller handles the absent branch, and that the present member is called as declared. The second row also shows the type rejects `operation: 'all'`. No existing title is edited. - `packages/plugins/plugin-security/src/registered-security-service-members.pin.test.ts` (new, test-only): the enumeration pin. - `.changeset/21756-security-service-declared-members.md`: `@objectstack/spec` `minor`, `Clause-②: yes (widening)`. ### The pin, and why the declared list is test-local The pin boots the real `SecurityPlugin` (`init` + `start`) and takes the object it passes to `registerService('security', …)`. It walks every own key along the prototype chain, so a class-backed service would not pass over zero members. It fails, by name, on any member that is in neither `DECLARED_MEMBERS` nor `SERVED_NOT_DECLARED`. Its non-vacuity control requires every required member to be among the enumerated ones. It needs a runtime list of declared members. I chose a **test-local** `DECLARED_MEMBERS` map held to the interface by `satisfies { readonly [K in keyof ISecurityService]-?: 'required' | 'optional' }`, computed per member. If the interface gains a member and the list does not, the list stops compiling. A name the interface lacks, or a wrong required/optional tag, also stops it compiling. The compile half runs in this package's `typecheck` (`tsconfig.test.json` compiles every test here, at zero debt). **No new spec export was needed.** A runtime list exported from `packages/spec` would have grown the published surface to serve one test, and would still need a clause like this one to keep it equal to the interface. The pin's boot fake has no engine write or read verb (`objectql` carries only `registerMiddleware` and `getSchema`), so it is not a double the `check:engine-double-contract` family scans. A third case is compile-only: it types a witness of each extension member's contract signature, delegating to the implementation function the registered member delegates to. If the contract and the implementation disagree, that case stops compiling. ## Proof the pin can fail (predicted first, run from committed state) Run from commit `ff69d4c4eb`. Mutations went through `node scripts/ablation-replace.mjs` (anchor must hit, blob must move, restore proven by blob == HEAD and an empty `git diff HEAD`). The pin imports `./security-plugin.js` by relative path, so no `dist/` sits between the mutation and the run. 1. **Runtime half.** Prediction: test 1 red, naming `zzScratchServedMember`; tests 2 and 3 green. I planted `zzScratchServedMember: () => undefined` in the `Object.assign` extension of `security-plugin.ts` (blob `bf796ff10c8d` -> `cd61be11613c`). Observed, as predicted: `AssertionError: served by the registered security service but neither declared on ISecurityService … expected [ 'zzScratchServedMember' ] to deeply equal []`, `Tests 1 failed | 2 passed (3)`. Restored to blob `bf796ff10c8d` == HEAD, `git diff HEAD` empty. (My first attempt used an anchor that the replacement still contained. The tool refused it before running anything, so it measured nothing. The second attempt is the measurement.) 2. **Compile half.** Prediction: `tsc -p tsconfig.test.json` red at the `satisfies` clause, in this file only. I dropped `contributeOwnershipFloorAlternates` from `DECLARED_MEMBERS`. Observed: `registered-security-service-members.pin.test.ts(77,12): error TS1360: … does not satisfy the expected type 'DeclaredOptionality'`, the only error in the program. Restored to blob == HEAD. This also proves the test program read the **rebuilt** spec `.d.ts`: against a stale one without the new member, the unmutated list would be the red one (an excess property). ## Verification, on the final tree All runs below are at `f2f466c4a9` (the branch merged with `origin/main` `ebfe658c72`, artifacts regenerated, changeset committed). - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/contracts/security-service.test.ts`: `Tests 23 passed (23)` (21 before + 2). - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2` (whole package): `Test Files 615 passed (615)`, `Tests 18360 passed | 1 todo`. - `pnpm --filter @objectstack/spec typecheck`: exit 0. The test layer compiles, and `security-service.test.ts` has no `test-typecheck-debt.json` entry, so its `@ts-expect-error` lines are live checks. - `pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2` (whole package): `Test Files 166 passed (166)`, `Tests 3582 passed | 45 skipped`. - `pnpm --filter @objectstack/plugin-security typecheck`: exit 0 (`0 file(s) / 0 error(s)` in the test-layer ledger). - `pnpm --filter @objectstack/spec check:generated`: exit 1 before the fix (exactly `api-surface/` and `export-origins/` stale, +2 interfaces each). After `--fix` re-checked them: `✓ check:api-surface`, `✓ check:export-origins`. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 91 commands from the merge-base change set (6 paths). I ran all 91 with their exit codes recorded before any pipe: 89 exited 0. `pnpm check:i18n` and `pnpm check:dual-build-cjs-loads` first exited 3 (`PREREQUISITE NOT MET`, no `dist/`). Both were re-run after building their prerequisites (the closure `check:i18n` names, then a workspace `pnpm build`, all turbo cache hits) and exited 0: `check-i18n-bundles: OK (9 package(s) — all bundles in sync …)` and `✓ check:dual-build-cjs-loads — 106 published require entry point(s) across 66 package(s) load`. `dispatch-gates.mjs --ran` over the recorded codes: `91 derived famil(ies) accounted for — 91 run, 0 NOT-MEASURED`. The six artifact-roster gates whose roster sits under a touched directory (`check-changeset-fixed`, `check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`) were also run, and all exited 0. - Lint (narrowed, a measurement rather than a skip): `pnpm exec eslint --no-inline-config --format json` over the three changed `.ts` files reports `files 3 errors 0 warnings 0`. All three are in the configured population (`eslint --print-config` resolves each). This repo's `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project`, no `projectService`), so the diff cannot move the verdict on any untouched file. The full `pnpm lint` is CI's. Consumers: the public-surface change is two optional interface members plus two type-only exports. The two callers do not type their access against `ISecurityService`: `rest-server.ts` holds the service as `any` (its provider returns a Promise of `any`), and `service-storage` uses its own local seam interface. So their compiled verdicts cannot move, and they are not re-run here. Every other package that references `ISecurityService` reads it as a `Partial` of `ISecurityService` or calls only pre-existing members (`git grep` over `packages/**`). The full consumer sweep is left to CI's workspace type-check lane. ## Overlap objectstack-ai#21763 (objectstack-ai#20749 stage 13) rewrites tracker ids in test titles of `security-service.test.ts` at lines 258, 287, 309, 471, 494 and 518. When this PR opened it was still in the merge queue, not on `main`. This PR edits no existing title. It adds one import specifier (line 8) and two rows after the last existing row (after line 560), so no added line is next to a title objectstack-ai#21763 changes. Neither new title carries a tracker id. A local trial merge of this branch with objectstack-ai#21763's head (`git merge-tree --write-tree`; this file is not routed to the regen merge driver, so the text merge is the same one GitHub runs) is clean. Whichever lands later merges `origin/main` (no rebase). ## Acceptance notes Noted, not filed. These are observations, not defects or contract violations. Each is out of this card's scope: the dispatch forbids editing `security-plugin.ts`, `rest-server.ts` and `service-storage` source. - **Comments now stale.** These comments still describe the members as undeclared extensions whose spec seat is "a separate change": `security-plugin.ts` around `:2100`–`:2122` (the comments above the `Object.assign`), the header of `ownership-floor-alternates.ts` ("an EXTENSION of the published contract"), and the header of `attachment-delete-floor-alternate.ts`. Carrier: the next PR that edits those files. - **The registration log line drifts.** `security-plugin.ts:2137` prints a hand-written member list. It names the two extension members, but omits `hasWriteBypass`, `resolveWriteScope`, `describeDelegationNarrowing`, `getEffectiveObjectPermissions` and `describeDelegableScope`. Log text only. Carrier: the next editor of `security-plugin.ts`. Holder: none. - **Possible follow-up shape.** With both members now on the contract, they could move from the `Object.assign` extension into the typed literal, where the compiler holds their signatures directly. That would make the pin's compile witness redundant for them. It is a `plugin-security` source change, so it is not done here. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #20749
Clause-②: no
Stage 13 of this card, and the fourth area of class (e): the test strings shipped under
packages/spec/src, as ruled in5902360492on #20513. This stage takes two whole directories,contracts/andconversions/. Their 97 test-title and test-string literals carried 108 tracker ids citing 78 records. 107 ids in 96 literals now either state what their record decided, in words (form D), or are dropped where the title already says it. One id stays, for the reason given below. Text only: no assertion, identifier, test count or code comment changes.Census at the base (
866b4393d0, the claim's base)Instruments:
census10.cjs(md59d08602ab972b4b8643c90d64d40fa41),census.cjs(md56e42a45a926d375013c32d62f16a296e) andcensus-wide.cjs(md5c98410a19529c439adb0afbfb00026a2), byte-identical to the copies stages 10 to 12 used. A literal counts as a test title when its folded message is argument 0 of adescribe/it/testcall,.each/.skip/.onlychains included. Everything else is an "other" string.Both instruments read 1509 messages / 1606 ids in 348 files at the base, which is stage 12's head reading exactly.
contracts/reads 63 / 74 andconversions/34 / 34, also stage 12's figures.data/ui/api/system/src/)contracts/(this PR)conversions/(this PR)security/ai/identity/integration/migrations/marketplace/,meta-spelling/,studio/contracts/data-engine.test.ts:575reads one message with four ids. Lit, anexpectmessage:contracts/metadata-service-roundtrip-conformance.test.ts:142reads one message. Dark: the comment atcontracts/core-service-contracts.test.ts:3("[dispatcher 多个 domain 调用契约里没有的方法 —— #4087 的同类,只是方向相反(契约缺声明,不是调用点乱编) #4127] The map claims a binding per slot") reads 0. Planted in a scratch copy: an id put back into a title reads 1 / 1, and an id in an added comment reads 0.#plus digits) reads 63 / 75 undercontracts/and 35 / 36 underconversions/test files at the base. The extras are(batch #76)in theresume-failure-report.pin.test.ts:137title, andPD #12(Prime Directive 12, contract-first) in twoconversions.test.tstitles,:180and:785. None matches the gate's 3-to-5-digit pattern.contracts/reads 1 / 1 (the needle below),conversions/0 / 0. Nothing else moved. The wider pattern reads that needle and the twoPD #12titles, and nothing else.How the area was chosen
Stage 10's rule: rank whole first-level directories by ids, and take the busiest within about 10% of the ~100-id bound.
data/(501),ui/(415),api/(201) andsystem/(165) each exceed it alone, and the files directly insrc/(120) are 20% over. No single remaining directory fits except smaller ones, andcontracts/withconversions/reads exactly 108, within 10% of the bound. That is the pairing the stage-10 ACCEPT named, so the rule needed no second pass.Named for the next stages:
data/(about five stages, by subdirectory or file group;data/driver/alone is 52),ui/(about four),api/(two),system/(two), the files directly insrc/(one, 120), andsecurity/,ai/,identity/,integration/,migrations/,marketplace/,meta-spelling/andstudio/together (one, 96).What each id became
32 ids in 21 literals now state a decision in words. 75 ids in 75 literals are dropped where the title already says what the record decided. Every cited record was read with its comments through REST: 71 answer 200, and 7 answer 404 (#6345, #6523, #11741, #12010, #12248, #16559, #16786). For those seven the decision was read from what landed: the landing commit and the CHANGELOG entry.
action-confirmation-contract.pin.test.ts:63approval-service.test.ts:23automation-context-caller-param-keys.pin.test.ts:49automation-service.test.ts:422confirmed-blueprint-identity-contract.pin.test.ts:52data-driver.test.ts:290,data-engine.test.ts:455data-engine.test.ts:5148425c17cc.data-engine.test.ts:575ConnectionEngineLike's members on the contract (8425c17cc,77b91bd), and #12805 caught the declared def up to what the engine retains.data-engine.test.ts:679http-server.test.ts:183http-server.test.ts:316job-service.test.ts:126job-service.test.ts:263notification-service.test.ts:153objectql-engine-hook-scope.test.ts:41excludeObjects.objectql-engine.test.ts:29unknown". Fork 3.objectql-engine.test.ts:106objectql-engine.test.ts:163sharing-service.test.ts:35sharing-service.test.ts:194Dropped only (75 ids): #3903, #4045, #4127 (2), #4158, #4251, #4343, #4347 (2), #4401, #4456 (2), #4538 (2), #4827, #4829, #4923 (5), #5011, #5122, #5125, #5126, #5493 (3), #5777, #5817, #5945 (4), #6345 (2), #6428, #6430, #6523, #6775 (2), #6776, #7378 (3), #7616 (2), #8321, #11122 (2), #11741, #11832, #13700, #13937, #14103, #14244, #14384, #14945, #14969, #15389, #15429, #16231, #16495, #16559, #16693, #16786, #19620, #20323, #20390, #20740, #20935, #20940, #21005, #21220, #21458.
conversions/most titles are the conversion entry's own id ("action-aria-removed", "app-hidden-to-unpublished"), which is the decision that landed.anyrule missesgetService<any>(...)— 80 sites erase the slot contract, 3 of them inside the rule's own scope #4251 B3" (IObjectQLEnginewidensIDataEngine; the title says it), "runtime导出的HttpServer包装器静默丢弃IHttpServer的全部可选成员(getPort/getRawApp/setFallbackHandler) #5122 shape" (a wrapper that forwards only required members; the title says it), and "MetadataFacadeanswers threeregister→getround-trip cases differently from every other shippedIMetadataService#7378 row 1" / "row 3" (both refusal messages already state their row's ruling).--database-driver/OS_DATABASE_DRIVER)在 CLI 与 standalone stack 之间仍有三处分叉(#6265 后续) #6345 (e2798fa: one driver vocabulary,mongoconverged tomongodb), finding:SharingExecutionContext是同族第四个窄 enforcement 契约类型(sharing / approval / report 三个服务共用),#6206 裁决的「不留 per-site 子集」默认尚未覆盖它 #6523 (aa4b90d: enforcement takes the fullExecutionContext), WidenSendEmailInputwithorganizationIdsosys_emailcan be stamped at its producers (Decision 2 of #11303) #11741 (b706af9: an optionalorganizationIdon both email inputs), spec(contracts): declare the machine-readable stranded-resume details once, and add the optional carrier field to the three doors ruled in decision batch #76 #16559 (c7aca0dce: the resume failure declared once, carried by a success answer), [finding] Ruling A on #16231 narrowsIScopedObjectRepository, butctx.api.object(name)resolves through the CLASSObjectRepository— the hook-facing door keepsPromise<any>, andupdateByIdkeeps it too #16786 (6059b29:updateByIddeclares the record ornull). Each title already carries what landed.(batch #76)inresume-failure-report.pin.test.ts:137goes with#16559in the same literal. It names the decision batch whose ruling the title already states ("the resume failure a success answer carries"). It is outside the gate's pattern, and it is declared here.PD #12stays inconversions.test.ts:785. It is a Prime Directive reference in AGENTS.md, not a tracker number, and the untouched sibling title at:180spells it the same way.The one id that stays
contracts/approval-service.test.ts:274isexpect(doc).toContain('#16495'). It is not a title: it is the expected value of an assertion that reads thecontinueRestoredRundocblock incontracts/approval-service.tsand pins that the docblock names the sibling it was ruled to copy. Changing it needs a code comment and assertion logic, which this claim excludes. It moves with that docblock when the comment lane rewritesapproval-service.ts:999.Readers
-tand--testNamePatternfinds onlypackages/qa/dogfood/README.md:142(-t "owner-scoped"), which is unrelated.__snapshots__, and no.snapfile is tracked underpackages/spec.packages/spec/vitest.repo-tests.json, so all run in thelocalproject.scripts/check-*.mjsself-test reads one. The 17 needle hits land on 11 lines:packages/objectql/src/metadata-service-roundtrip-conformance.test.ts:232(the objectql driver of the same table,register must REFUSE this write (#7378));packages/plugins/plugin-security/src/get-queryable-fields.test.ts:136and:165([#20935]),resolve-permission-sets-for-context.pin.test.ts:103([#7616]),authored-row-write-verdict.test.ts:350([#5493]); andpackages/metadata-core/src/artifact-forward-conversion.test.ts:277((ADR-0113, #16693)).packages/spec/src/contracts/automation-service.ts:1061(#13937 shape 4),packages/spec/src/contracts/index.ts:44((#4127)),packages/cli/src/utils/view-container-names.ts:11andpackages/objectql/src/view-container-name-refusal.ts:13(#7378 row 1).Text-only proof
Stage 10's scratch tool (
textonly10.cjs, md5d5e4801dbb4329ab1984da91e92fc47c) compares base and head file by file on three legs:#plus digits after. The declared lines are the three inmetadata-service-roundtrip-conformance.test.ts: the reference double's two refusal messages (:68,:75) and theexpectmessage at:142. No test asserts on the dropped text: the refusal checks assertcode,statusand the write's coordinates.conversions.test.tspasses the skeleton and comment legs and is flagged on one string,:785, because its rewritten title keepsPD #12. That was predicted in writing before the run. WithPD #12spelledPD-12in both the base and head copies of that one line, the file reads SAME with 19 changed titles.+lines are exactly the 96 planned lines, and every file keeps its line count.expectmessage changed VIOLATION; a title re-split into a+chain DIFF.Test counts: the 34 files were run at the base (in a separate base worktree at
866b4393d0) and at the head, in thelocalproject. Both sides read 595 / 595 passed, with the same count and status sequence per file in 34 of 34. 354 full test names change, and each equals the base name with the planned replacements applied. One full name repeats on both sides: twosqliterows of thestored.test.tsit.eachtable share the%sname. That predates this PR.Changeset:
skip-changesetMeasured, not assumed:
npm pack --dry-runof@objectstack/speclists 2068 files. 0 of the 34 touched files are in it, and no*.test.tsat all. The controlssrc/shared/expression.zod.tsanddist/contracts/index.jsare in it.dist/, four new phrases and three old ones each read in 0 files. The controlUnrecognized key(s) onreads in 42.So this PR publishes nothing, and no changeset is added.
Verification (at
72513933ee)pnpm turbo run buildover all packages: 71 / 71.@objectstack/spec:vitest run --project local: 614 files, 18285 passed, 1 todo.typecheckexit 0, includingcheck:test-typecheck. Its program holds all 34 touched files, counted withtsc --listFilesOnly.dispatch-gates --commandsderived 79 families (stage 12's 80 withoutcheck:future-spec-major, which no touched file feeds), and all 79 exit 0.--ranreconciles: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN.check:meta-url-spelling,check:spec-changes,check:authz-resolver,check:error-code-casingandcheck:filter-alias-parity.--no-inline-configover the 34 files, 0 errors and 0 warnings. The population comes from ESLint's own config: 34 configured, 0 ignored. NoparserOptions.projectorprojectService, so no untouched file's verdict can move.check-governed-merges --test: NOT governed, 192 changed lines.Acceptance notes
#16495needle atapproval-service.test.ts:274stays, with theapproval-service.ts:999docblock it pins. The comment lane owns both.plugin-securitytest files and onemetadata-coretest (listed under Readers). Each is its own lane's test-string stage.origin/mainmoved five commits past the base before this PR opened (fix(auth): TOTP enrollment names the deployment app name as its issuer, not Better Auth #21752, fix(plugin-security): explain credits read depth, not sharing, for a row only the depth admits #21754, fix(service-storage): a parent-record editor may delete another user's attachment #21753, fix(service-storage): a file field's accept/maxSize refusal answers 400 ERR_FILE_CONSTRAINT #21751, fix(metadata-core): the object-schema field mask also removes a denied field's references from the served document (ADR-0106 D1) #21743). None touchespackages/spec, so nothing was merged. spec(contracts): ISecurityService does not declare two members the registered security service carries — contributeOwnershipFloorAlternates and discardPermissionSetOverlay #21756, which also editscontracts/security-service.test.ts, has no PR yet; whichever lands later mergesorigin/main.Generated by Claude Code