Skip to content

test(spec): contracts and conversions test titles state each cited decision in words instead of a tracker number (stage 13) - #21763

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20749-test-strings-d
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20749-test-strings-d

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 in 5902360492 on #20513. This stage takes two whole directories, contracts/ and conversions/. 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 (md5 9d08602ab972b4b8643c90d64d40fa41), census.cjs (md5 6e42a45a926d375013c32d62f16a296e) and census-wide.cjs (md5 c98410a19529c439adb0afbfb00026a2), 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 a describe / it / test call, .each / .skip / .only chains 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 and conversions/ 34 / 34, also stage 12's figures.

directory files messages / ids titles other
data/ 95 468 / 501 445 / 475 23 / 26
ui/ 81 392 / 415 374 / 397 18 / 18
api/ 40 189 / 201 181 / 193 8 / 8
system/ 34 154 / 165 128 / 138 26 / 27
(files directly in src/) 30 118 / 120 117 / 119 1 / 1
contracts/ (this PR) 25 63 / 74 59 / 70 4 / 4
conversions/ (this PR) 9 34 / 34 34 / 34 0
security/ 8 28 / 28 28 / 28 0
ai/ 9 18 / 20 13 / 15 5 / 5
identity/ 6 15 / 15 14 / 14 1 / 1
integration/ 4 14 / 14 13 / 13 1 / 1
migrations/ 2 9 / 12 9 / 12 0
marketplace/, meta-spelling/, studio/ 5 7 / 7 7 / 7 0
total 348 1509 / 1606 1422 / 1515 87 / 91
  • Controls. Lit, a title: contracts/data-engine.test.ts:575 reads one message with four ids. Lit, an expect message: contracts/metadata-service-roundtrip-conformance.test.ts:142 reads one message. Dark: the comment at contracts/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.
  • A wider pattern (any # plus digits) reads 63 / 75 under contracts/ and 35 / 36 under conversions/ test files at the base. The extras are (batch #76) in the resume-failure-report.pin.test.ts:137 title, and PD #12 (Prime Directive 12, contract-first) in two conversions.test.ts titles, :180 and :785. None matches the gate's 3-to-5-digit pattern.
  • At the head: 1413 messages / 1499 ids in 315 files. contracts/ reads 1 / 1 (the needle below), conversions/ 0 / 0. Nothing else moved. The wider pattern reads that needle and the two PD #12 titles, 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) and system/ (165) each exceed it alone, and the files directly in src/ (120) are 20% over. No single remaining directory fits except smaller ones, and contracts/ with conversions/ 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 in src/ (one, 120), and security/, ai/, identity/, integration/, migrations/, marketplace/, meta-spelling/ and studio/ 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.

record(s) literal now reads
#16293 action-confirmation-contract.pin.test.ts:63 "action-confirmation contract — an unconfirmed gated action is refused". The ruling: a gated action without an explicit confirmation is refused loudly, with the way to confirm.
#10331 approval-service.test.ts:23 "approval rows declare the organization_id they are stamped with". The finding's first reading landed: both row types declare it, optional and nullable.
#19846 automation-context-caller-param-keys.pin.test.ts:49 "AutomationContext.callerParamKeys — the keys the caller supplied". The ruling replaced the headless-screen inference with this explicit signal.
#18235 automation-service.test.ts:422 "FlowRuntimeState — carries the reason a flow is unbound", so a policy-disabled flow reads differently from a broken binding.
#15937 confirmed-blueprint-identity-contract.pin.test.ts:52 "confirmedBlueprintIdentity — declared on the protocol ToolExecutionContext". The maintainer chose option 1: declare it in the protocol, not only on cloud's augmentation.
#11493 (2) data-driver.test.ts:290, data-engine.test.ts:455 "introspectSchema — an optional driver member at the spec shape" and "introspectDatasource — answers the spec introspection shape". Both ruled steps.
#12248, #11833 data-engine.test.ts:514 "datasource resolution members — declared, and optional". Fork 1 of the #11833 ruling, option A, landed as 8425c17cc.
#12248, #12010, #12805, #11833 data-engine.test.ts:575 "datasource lifecycle members — declared at the shape the engine keeps". Item 4 of the #11833 ruling put ConnectionEngineLike's members on the contract (8425c17cc, 77b91bd), and #12805 caught the declared def up to what the engine retains.
#12482, #12010, #11833 data-engine.test.ts:679 "syncObjectSchema — declared, since two services already call it".
#5040 http-server.test.ts:183 "optional setFallbackHandler — a not-found hook, not a wildcard route". The design's option C, so a declared endpoint never shadows a registered route.
#9835 http-server.test.ts:316 "optional afterResponse — a transport-agnostic response observer".
#6617 job-service.test.ts:126 "JobHandler degraded-outcome channel — optional and additive".
#14766, #14501 job-service.test.ts:263 "IJobService.replay force option — a succeeded window replays only when forced". The A + a2 ruling.
#4127 notification-service.test.ts:153 "inbox — declared on the contract, and optional".
#5928 objectql-engine-hook-scope.test.ts:41 "IObjectQLEngine.registerHook scope faces — global minus excluded objects". The ruled A shape, excludeObjects.
#12248, #11833 objectql-engine.test.ts:29 "getObject return contract — a structured answer, not unknown". Fork 3.
#12481, #12248, #11833 objectql-engine.test.ts:106 "getSchema return contract — the same answer as its alias getObject". Fork 3, one member over.
#20157, #19995 objectql-engine.test.ts:163 "judgeFilter contract — the engine judges a filter without running it". Ruling C.
#4539 sharing-service.test.ts:35 "recipient vocabularies, each under its own name". The same-name, different-form exports were split by renaming.
#5858 sharing-service.test.ts:194 "HierarchyScopeContext tenancy authority — organizationId is authoritative".

Dropped 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.

The one id that stays

contracts/approval-service.test.ts:274 is expect(doc).toContain('#16495'). It is not a title: it is the expected value of an assertion that reads the continueRestoredRun docblock in contracts/approval-service.ts and 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 rewrites approval-service.ts:999.

Readers

  • Test-name filters: none. A tracked-tree search for -t and --testNamePattern finds only packages/qa/dogfood/README.md:142 (-t "owner-scoped"), which is unrelated.
  • Snapshots: none. Neither directory has __snapshots__, and no .snap file is tracked under packages/spec.
  • Projects: none of the 34 files is listed in packages/spec/vitest.repo-tests.json, so all run in the local project.
  • By substring: every old literal, plus a window around each id (241 needles), was searched across the tracked tree outside its own file. No gate, doc, filter, snapshot or scripts/check-*.mjs self-test reads one. The 17 needle hits land on 11 lines:
    • Sibling test strings in other lanes' packages: 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:136 and :165 ([#20935]), resolve-permission-sets-for-context.pin.test.ts:103 ([#7616]), authored-row-write-verdict.test.ts:350 ([#5493]); and packages/metadata-core/src/artifact-forward-conversion.test.ts:277 ((ADR-0113, #16693)).
    • Code comments: 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:11 and packages/objectql/src/view-container-name-refusal.ts:13 (#7378 row 1).

Text-only proof

Stage 10's scratch tool (textonly10.cjs, md5 d5e4801dbb4329ab1984da91e92fc47c) compares base and head file by file on three legs:

  1. Skeleton: the full AST, with string pieces masked. It must be identical.
  2. Comments: every comment, byte-equal.
  3. Strings: each changed string leaf must sit in a test-call title position or on a declared line, must carry a tracker id before, and must carry no # plus digits after. The declared lines are the three in metadata-service-roundtrip-conformance.test.ts: the reference double's two refusal messages (:68, :75) and the expect message at :142. No test asserts on the dropped text: the refusal checks assert code, status and the write's coordinates.
  • Result: 33 of 34 files SAME on all three legs. conversions.test.ts passes the skeleton and comment legs and is flagged on one string, :785, because its rewritten title keeps PD #12. That was predicted in writing before the run. With PD #12 spelled PD-12 in both the base and head copies of that one line, the file reads SAME with 19 changed titles.
  • Totals: 96 changed literals, 93 titles and 3 declared. The diff's + lines are exactly the 96 planned lines, and every file keeps its line count.
  • Controls (10 of 10 as predicted, on scratch copies, each anchor hit once): identifier rename DIFF; numeric literal DIFF; comment edit COMMENT DIFF; a non-title string given an id VIOLATION; a rewritten title given a new id VIOLATION; a title that was id-free at base edited VIOLATION; one title reverted to base SAME; a declared string keeping an id VIOLATION; an undeclared expect message 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 the local project. 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: two sqlite rows of the stored.test.ts it.each table share the %s name. That predates this PR.

Changeset: skip-changeset

Measured, not assumed:

  • npm pack --dry-run of @objectstack/spec lists 2068 files. 0 of the 34 touched files are in it, and no *.test.ts at all. The controls src/shared/expression.zod.ts and dist/contracts/index.js are in it.
  • In dist/, four new phrases and three old ones each read in 0 files. The control Unrecognized key(s) on reads in 42.

So this PR publishes nothing, and no changeset is added.

Verification (at 72513933ee)

  • pnpm turbo run build over all packages: 71 / 71.
  • @objectstack/spec:
    • vitest run --project local: 614 files, 18285 passed, 1 todo.
    • typecheck exit 0, including check:test-typecheck. Its program holds all 34 touched files, counted with tsc --listFilesOnly.
  • Gates: dispatch-gates --commands derived 79 families (stage 12's 80 without check:future-spec-major, which no touched file feeds), and all 79 exit 0. --ran reconciles: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN.
    • The five roster families whose rosters sit under a touched directory were also run, and each exits 0: check:meta-url-spelling, check:spec-changes, check:authz-resolver, check:error-code-casing and check:filter-alias-parity.
  • ESLint, a proven narrowing: --no-inline-config over the 34 files, 0 errors and 0 warnings. The population comes from ESLint's own config: 34 configured, 0 ignored. No parserOptions.project or projectService, so no untouched file's verdict can move.
  • check-governed-merges --test: NOT governed, 192 changed lines.

Acceptance notes


Generated by Claude Code

…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>
@github-actions github-actions Bot added the size/m label Oct 4, 2026
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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): node scripts/docs-audit/affected-docs.mjs --json a6a7547074d2f705f7a2c3f525250099c42b6b72 → packageMentionDocs.

@github-actions github-actions Bot added the tests label Oct 4, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 4, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 00:04
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 5, 2026 00:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit 8256a4b Oct 5, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20749-test-strings-d branch October 5, 2026 00:45
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m 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.

2 participants