Repository navigation
Commit dfab597
feat(spec): declare declaredRuleNamed on ISharingRuleService; document sharing-rule regime C (#25250)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wg6zwDuV2XvoDkqC7bXgqnpc)_
Fixes #25187
Clause-②: yes (widening)
Metadata-protocol: none
This PR handles the three regime C follow-ups the card lists: the
contract member, the docs paragraph and the ledger docblock. It changes
no behaviour.
## What changed
1. **Contract** (`packages/spec/src/contracts/sharing-service.ts`).
`ISharingRuleService` gains an optional `declaredRuleNamed?(name:
string)` member. It resolves to `{ name: string; packageId?: string }`
or `null`. It is optional, has the shape the activation door already
duck-typed, and its docblock covers the probe-and-refuse duty and the
503.
2. **The door reads the contract's type**
(`packages/runtime/src/domains/catalog-activation.ts`). The local
`interface DeclaredSharingRuleReader` is now a type alias: `Required`
applied to a `Pick` of `ISharingRuleService`'s `declaredRuleNamed`. The
change is type-only: the `typeof … === 'function'` probe, the 404 and
the 503 `SERVICE_UNAVAILABLE` stay as they were. A runtime build shows
the symbol in no emitted file (see Evidence).
3. **`SharingRuleService` satisfies the member as declared.** It already
`implements ISharingRuleService`, and its `declaredRuleNamed(name)`,
which resolves to a `DeclaredSharingRule` or `null`, type-checks against
the new member. Nothing in `plugin-sharing` changed.
4. **Docs** (`content/docs/permissions/sharing-rules.mdx`). This adds a
new section, "Declared rules are read-only: clone one, then switch it
off". It covers read-only declared rules, clone and switch off through
`POST {basePath}/security/_activation/sharing_rule/:name`, the 409
`RESOURCE_CONFLICT` refusal of a new or renamed row under a declared
name, the `manage_metadata` gate plus the platform-operator wall, and
the reconcile-failure window. Each claim cites the function and file it
was read from at this head. The `/meta` half links to
`concepts/metadata-lifecycle`, which this PR leaves unchanged and which
stays consistent with the new section.
5. **Ledger docblock**
(`packages/platform-objects/src/system/sys-metadata-activation.object.ts`).
The `metadata_type` description now lists every type the ledger holds at
this head: `flow` (service-automation's flow store), `action`
(objectql's action store), and `position`, `permission`, `sharing_rule`
and `webhook` (the catalog activation door).
6. **Changeset** `.changeset/25187-declared-rule-reader-contract.md`:
`@objectstack/spec` and `@objectstack/platform-objects` at `patch`,
following `Metadata-protocol: none`. `@objectstack/runtime` is not
listed because its change is type-only and nothing in its published
`dist` moves (see Evidence).
## How the docs claims were checked against the code
- **Read-only.** `SharingRuleService.deleteRule` refuses a declared name
with `PERMISSION_DENIED`, and `sharing-routes.ts` `handleError` answers
403. `PUT /meta/sharing_rule/:name` is refused because the
`sharing_rule` registry row in `metadata-plugin.zod.ts` sets
`allowRuntimeCreate: false`.
- **409.** `DeclaredSharingRuleNameConflictError` sets `code =
'RESOURCE_CONFLICT'` and `status = 409`. It is thrown by `defineRule`
(create) and by `bindDeclaredRuleNameGuard`'s
`beforeInsert`/`beforeUpdate` hooks (insert, and a rename onto a
declared name). System writes are exempt.
- **Operator wall.** In `activation-gate.ts`,
`refuseUngrantedActivationAuthoring` requires `manage_metadata`.
`refuseUngrantedActivationWrite` is inert when no posture is set, or
under `single`; under a wall it requires `posture === 'PLATFORM_ADMIN'`.
`SHARING_RULE_ACTIVATION_SUBJECT.remedy` names the platform operator.
- **Reconcile window.** `bindSharingRuleActivationReconcile` registers a
plain `registerHook` handler with no `meta`, so
`ObjectQL.opensAfterAbortUnit` opens no unit.
`ObjectStoreMetadataActivationStore.setActive` opens no transaction. So
the ledger row stays written when the reconcile throws, and the door
answers the error through `errorFromThrown(err, 503)`. At boot,
`kernel:bootstrapped` in `sharing-plugin.ts` runs the `listRules`
backfill, which includes served declared projections, and then
`sweepOrphanedRuleGrants`, which revokes the grants of a declared rule
that is no longer served. A repeated switch re-runs the reconcile
because `setActive` always issues an `update`, and `afterUpdate` fires
on it.
## Evidence
All readings were taken on head `672daf099`, the last commit, unless
noted otherwise.
- `pnpm --filter @objectstack/spec build && pnpm --filter
@objectstack/spec check:generated`: VERDICT command-exit 0, "All 14
generated artifacts are up to date". No regeneration was needed.
- `pnpm --filter @objectstack/runtime --filter
@objectstack/plugin-sharing --filter @objectstack/platform-objects
--filter @objectstack/spec run typecheck`: VERDICT command-exit 0, all
four packages done.
- **Reverse verification** (cross-package type change). The member's
return was mutated to `{ name: number; … }` through
`scripts/ablation-replace.mjs`, and spec was rebuilt. `tsc --noEmit`
then went red in both consumers:
- `runtime src/domains/catalog-activation.ts(159,36): error TS2322`
- `plugin-sharing src/sharing-rule-service.ts(1050,9): error TS2416
Property 'declaredRuleNamed' in type 'SharingRuleService' is not
assignable to the same property in base type 'ISharingRuleService'`
The restore was checked three ways: the blob equals HEAD with an empty
`git diff HEAD`; after a spec rebuild, 0 dist files carry the mutated
type; and both consumers' `tsc` passed again.
- **Tests:**
- `vitest run src/domains/catalog-activation-door.test.ts` (runtime):
64/64.
- `vitest run src/sharing-rule.test.ts` (plugin-sharing): 140/140.
- `pnpm --filter @objectstack/platform-objects test`: 87 files,
1278/1278.
- **Runtime publish measure.** After `pnpm --filter @objectstack/runtime
build`, a grep of `dist/` (maps excluded) for
`DeclaredSharingRuleReader`, `ISharingRuleService` and the new comment
text finds 0 files each. The positive control
`declaredSharingRuleReaderOf` is found in 2 files.
- **Gates.** The local subset comes from `node
scripts/pm/dispatch-gates.mjs --commands` (106 commands). Results are
under "Local gates" below.
## Local gates
All gates were run on head `672daf099`.
- **Derived set.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 106 commands, and all 106 were run.
The first pass ended with 103 at exit 0 and 3 at exit 3 (`PREREQUISITE
NOT MET`, no build output): `spec check:skill-examples`,
`check:dual-build-cjs-loads` and `check:i18n`. Their prerequisite builds
were run (turbo, `--concurrency=2`), and all three then exited 0.
`check:i18n` reported "all bundles in sync". `check:skill-examples`
reported "262 prose examples type-check".
- **Reconciliation.** `dispatch-gates --ran` reports "106 derived, 106
run, 0 NOT-MEASURED, 0 UNRUN", exit 0.
- **Local-only gates.** `pnpm check:dev-prereqs` exited 0, and so did
`pnpm check:skill-frame-freshness`.
- **Left to CI.** The repo-wide farm (`Lint & Repo Gates`, `TypeScript
Type Check`, `Test Core`, `Build Core`, `Build Docs`) is not repeated
locally.
## Acceptance notes
- `docs/adr/0126-packaged-metadata-customization-model.md:205` still
lists `metadata_type` as (`'flow'`, `'permission'`, …). It is a governed
surface and outside this card, so it is left as is. The docblock this PR
changes is the field's runtime description.
- Not covered here, and recorded only as an observation: the `/data`
door's 409 for the name guard is asserted by the error class's declared
`code`/`status` and by the plugin-sharing tests. This PR adds no
end-to-end HTTP pin for it.
- **Deviation from the claim's file surface.** The claim listed three
files plus a changeset. The dispatch asked for the runtime door to read
the contract type in the same PR when it can, so
`packages/runtime/src/domains/catalog-activation.ts` is touched too. The
change is type-only.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01B2EeooonSXrANpkpgUSfFZ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6bc8cc6 commit dfab597
5 files changed
Lines changed: 119 additions & 4 deletions
File tree
- .changeset
- content/docs/permissions
- packages
- platform-objects/src/system
- runtime/src/domains
- spec/src/contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
242 | 242 | | |
243 | 243 | | |
244 | 244 | | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
245 | 319 | | |
246 | 320 | | |
247 | 321 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
111 | 112 | | |
112 | 113 | | |
113 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
114 | 119 | | |
115 | | - | |
116 | | - | |
117 | | - | |
| 120 | + | |
118 | 121 | | |
119 | 122 | | |
120 | 123 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
614 | 614 | | |
615 | 615 | | |
616 | 616 | | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
617 | 642 | | |
618 | 643 | | |
619 | 644 | | |
| |||
0 commit comments