Repository navigation
Commit 301eec6
Fixes #23268
Clause-②: no
Executes ruling of record `6159300530` on #23268, letter A (the
maintainer's 「23268 同意」): `externalSharingModel` is retired from
`packages/spec` on the v18 line, ADR-0049 enforce-or-remove. The
ADR-0090 D11 sentence is a separate Tier H PR (see "Companion" below);
this PR touches no governed surface.
## What the key was, and why it goes
ADR-0090 D11 declared `externalSharingModel` as a second org-wide
default for EXTERNAL (portal / partner) principals. Re-measured on
`origin/main` `e4ec6ec1a` before this branch:
- `git grep -n "externalSharingModel" origin/main -- 'packages/**.ts'
':!*.test.ts' ':!packages/spec/**'`: no row-pipeline reader. The only
readers were authoring checks of the key against itself (the publish
linter's alias and wider-than-internal branches, and the
`plugin-security` posture gate's env-tighten comparison), plus the
`metadata-core` keep table and comments. Positive control on the same
tree: `git grep -c "sharingModel" origin/main --
packages/plugins/plugin-security/src` is non-zero.
- No runtime principal is external:
`packages/core/src/security/assemble-execution-context.ts` assembles
`audience: undefined`.
- The authorization kernel's `EXTERNAL` rung (ADR-0095 / ADR-0099) is
explicitly shared rows only, never OWD-derived.
- Authored instances in this repository: two showcase objects
(`showcase_account`, `showcase_announcement`).
## The retirement kit (the `spec-property-retirement` playbook, surface
by surface)
- **Tombstone.** `retiredKey()` on `ObjectSchema` (a `strictObject`),
`packages/spec/src/data/object.zod.ts`. The prescription opens with ``
`object.externalSharingModel` ``, says it never had an effect,
prescribes "Delete the key", names what does work (`sharingModel`,
explicit shares, share links via `publicSharing`) and closes with the
house `os migrate meta --from 17` sentence. The route is the tombstone,
not the strict deletion: `tsc` fails at every typed authoring site, and
the refusal carries the prescription.
- **Neighbouring text.** The `tenancy.crossTenantAccess` retirement hint
no longer points at the key (ruling item 1). The `publicSharing`
guidance entry that refuses a mis-nested
`publicSharing.externalSharingModel` now says the key is removed,
instead of steering the author "one level up" to a dead key.
`publicSharing` itself is untouched (ruling item 3). The `audience` doc
comment in `kernel/execution-context.zod.ts` changes one sentence.
- **D2 conversion `object-external-sharing-model-removed`.** Protocol
18, `retiredFromLoadPath`, `retiredAfter: '17.7.0'`, order 64. Inserted
in `MAJOR_18_CONVERSIONS` by identifier (between `metricFiltersRemoved`
and `objectGridDefaultFiltersRemoved`) and defined directly above the
latter's definition. It strips the key from every `objects[]` entry, one
notice per object. Fixture: one object carrying it, one canonical
control.
- **Registration.** `RETIRED_KEYS_BY_MAJOR[18]` gains
`data/Object:externalSharingModel`
(`entries/retired-keys/18.data__Object__externalSharingModel.ts`). D3
entry `object-external-sharing-model-retired` (`entries/semantic/`),
with its `STEP18_RATIONALE` fragment (order 96).
- **Ledger.** `liveness/object.json`: the row goes `planned` (it still
pointed at #2696, which is done) to `dead`, kept as a tombstone row with
`verifiedAt` 2026-10-14; the README `object` row records it;
`state-counts/object.md` regenerated (53 live, 1 dead, 54 classified).
- **Generated.** `authorable-surface/data.json`
(`data/Object:externalSharingModel [RETIRED]`) and three reference pages
(`references/data/object.mdx`, `references/api/metadata.mdx`,
`references/system/migration.mdx`), regenerated by `check:generated
--fix`. The four ratchets other than `authorable-surface` do not move,
as expected for a key-level tombstone on a def that still emits.
- **Lint (`@objectstack/lint`).**
`security-external-wider-than-internal` and its exported
`SECURITY_EXTERNAL_WIDER` are removed; `security-owd-alias` no longer
reads the external key; `OWD_WIDTH` (its only user) goes; comments in
`authoring-rules.ts`, `rule-explanations.ts` and
`scripts/check-doc-security-posture.mjs` follow.
- **Posture gate (`@objectstack/plugin-security`).** R1
(env-tighten-only) compares `sharingModel` alone; its external half goes
with the key.
- **Examples.** `showcase_account` and `showcase_announcement` drop the
key; `security/index.ts`, `security/positions.ts`, `coverage.ts` and
`docs/showcase_tour_security.md` stop describing an external dial.
- **Docs.** `permissions/sharing-rules.mdx` (the external-dial section
becomes "External principals — no external org-wide default"),
`permissions/authorization.mdx` (three passages),
`protocol/objectql/security.mdx`, `docs/design/permission-model.md` (two
passages), plus the regenerated reference pages.
`content/docs/releases/**` is not touched.
- **QA item.** `docs/qa/platform-checklist/areas/access-security.json`
`access-security.owd-save-gate` revision 5: the external clauses go (the
Studio external-wider clause; the external-wider refusal, admit and
shipped-declarations clauses are rewritten to the tombstone's 422 on
every door, draft included, the admit without the key, and "the showcase
ships no retired key"). The packaged-object seal clauses are unchanged.
`surface` moves `mixed` to `api` because no browser step remains.
- **Comments.** `metadata-protocol/src/protocol.ts` (one comment line in
the authoring-gate docblock),
`plugin-audit/src/objects/sys-comment.object.ts` and its pin's
prescription text.
- **Changeset.** `.changeset/23268-external-sharing-model-retired.md`:
`@objectstack/spec` `minor` with the BREAKING banner, the FROM to TO
table and the one-line fix; `@objectstack/lint` `minor` (the removed
export); `@objectstack/plugin-security` `patch`; one ADR-0087
disposition, `registered object-external-sharing-model-removed,
object-external-sharing-model-retired`.
## Census class: `registered`, with a mechanical strip
Stored metadata can carry the key: Studio's object settings offered the
select, and `PUT /api/v1/meta/object` persisted it until this release
(`saveMetaItem` validated it against the closed enum). So the class is
`registered`, not `not-required`: `applyConversionsToStoredItem` replays
the D2 strip over a stored `object` row as `{ objects: [row] }`, pinned
in the new test.
**For the cloud seat (ruling item 5):** cloud's v18 stored preview, `os
migrate meta --stored`, lists the tenant rows carrying
`externalSharingModel` before the upgrade; the replayed strip heals them
on read.
## Pins that moved, and why each still bears load
- **New pin**
`packages/spec/src/data/object-external-sharing-model-retirement.test.ts`
(repo project; listed in `vitest.repo-tests.json`), 18 tests: the
tombstone at the schema, the `object` registry binding,
`ObjectSchema.create()` and `defineStack` (`STACK_SCHEMA_INVALID` /
422), every old value refused, the control, the walked shape, the
mis-nested `publicSharing` guidance, the `tsc` channel
(`@ts-expect-error`, compiled by `check:test-typecheck`); the D2 strip
at the stored-row seam and the boot door (with a lit control),
per-object notices, idempotence, retirement from the load path; the
three registrations; and a tree-scoped absence walk over the radius
`@objectstack/spec#test` already declares (the same five roots as the
precedents; `check:cross-package-test-inputs` green). Exclusions are
listed in the file with a reason each: the declaration file, the
`metadata-core` keep table (ADR-0106 D1 classifies every key of the
walked shape), the REST refusal pin, and four ledger-advisory witnesses
(below).
- **REST door** `packages/rest/src/meta-object-owd-gate.test.ts`: the
external-wider 422 cases flip to the substance of the new semantics: a
body carrying the key, with a pair the old rule ADMITTED, is refused
`422 INVALID_METADATA` with the prescription on the active, `?package=`
and `?mode=draft` doors, and nothing is stored. The draft-promotion pin
is kept on `security-owd-unset`; the package-author carve-out pin is
kept on an OWD-less body; legal-pair rows become the three OWD values.
- **Posture gate** `object-posture-gate.test.ts`: the R2-retirement and
external R1 cases go; R1's internal cases and the wiring pin stay.
- **Lint.** `validate-security-posture.test.ts`: the rule's two tests
and reachability fixtures go; the `obj` receiver's read-key list drops
the key, which is the structural pin that the linter reads nothing
external; push-site count 18 to 16. `lint-liveness-properties.test.ts`:
the key is now a `dead` row, and a `dead` verdict warns on an unparsed
intake without an opt-in, so the "the ledger loaded" witnesses keep it;
the real-ledger case flips from the planned branch to the dead branch
(rule id, default hint, `row.status === 'dead'`); the
planned-with-opt-in control moves to `page.print`; the opt-in
anti-vacuity is narrowed because no shipped opt-in lacks an authorHint
any more (the class stays held by the synthetic case).
- **CLI.** `validate-lint-mapping-connector-source.test.ts`
(integration) and `validate-json-warning-parity.e2e.test.ts` (nightly
tier) authored the key through `defineStack`, which now refuses it;
their warned-row control moves to `tool.outputSchema` (`experimental`).
`i18n-flow-liveness-gate.test.ts`: comment only.
- **Spec.** `object.test.ts` (the `crossTenantAccess` hint asserts
"explicit shares" and not the key), `solution-blueprint.test.ts` (the
undeclared sibling becomes `publicSharing`),
`metadata-form-zod-reconciliation.test.ts` (the `planned` omit row goes:
tombstones are skipped by the detector),
`retired-key-removal-version.pin.test.ts` (JOIN gains the conversion).
- **Dogfood** `guest-grants-channel.dogfood.test.ts`: the one fixture
object that declared the key is removed with its grant, endpoint and
seed (28 of 28 pass with `expression-conformance`).
Error message pin sweep: `git grep -n
"security-external-wider-than-internal\|SECURITY_EXTERNAL_WIDER\|owd_external_wider"`
outside history files now hits only prose that records the retirement,
the published skill reference below, ADRs, and pending changesets.
## Evidence (head `1060ed3e7`, after merging `origin/main` `04a314873`
through `scripts/pm/os-regen-merge.sh`)
All exit 0 unless noted.
- `pnpm --filter @objectstack/spec build`, then `check:generated`: all
14 artifacts up to date.
- `@objectstack/spec` local project in three shards before the second
merge: 225 / 7,366, 224 / 6,477 (1 todo), 224 / 6,619 plus one red fixed
in `retired-key-removal-version.pin.test.ts` (JOIN), whose re-run is 1 /
20 green. After the merge: conversions, migrations, `object.test.ts` and
that pin, 20 / 956.
- `@objectstack/spec` repo project in two shards: 30 / 470 and 29 / 471.
NOT MEASURED: `scripts/build-schemas-check-mode.test.ts`, excluded
because it alone exceeds the foreground cap (the precedent's reading);
`check:authorable-surface` is green. After the merge: the new pin,
`retired-key-migrate-sentence.test.ts` and
`conversions-major18-merge.test.ts`, 3 / 45.
- `@objectstack/lint` in three shards: 46 / 2,606, 46 / 1,862, and 46
files with 3 suites red on `Cannot find package '@objectstack/spec/ui'`
while a concurrent gate rebuilt `packages/spec/dist`; those 3 re-run 3 /
33. After the merge: the two posture and liveness files, 2 / 251.
- `@objectstack/plugin-security`: 185 files, 3,772 passed, 45 skipped.
`@objectstack/rest` `meta-object-owd-gate.test.ts` 11 / 11 (before and
after the merge). `@objectstack/metadata-core`
`object-schema-fls-references.test.ts` 52 / 52.
`@objectstack/plugin-audit` `sys-comment-retired-fields.test.ts` 8 / 8.
`@objectstack/example-showcase` 32 / 411. `@objectstack/dogfood` (guest
grants + expression conformance) 2 / 28.
- `@objectstack/cli`: unit `i18n-flow-liveness-gate.test.ts` 8 / 8;
integration `validate-lint-mapping-connector-source.test.ts` 3 / 3;
nightly tier (`OS_TEST_TIERS=nightly`)
`validate-json-warning-parity.e2e.test.ts` 3 / 3.
- `typecheck`: spec (including `check:test-typecheck`, which compiles
the pin's `@ts-expect-error`), lint, plugin-security, plugin-audit,
rest, cli, dogfood, example-showcase.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths) derives 125 commands at
`1060ed3e7`; the run and its `--ran` reconciliation are in the report on
the card. On the first full run (head `a4633774e`), 119 of 125 exited 0.
`check:platform-checklist` exits 1 with 8 findings that are identical,
line for line, on `e4ec6ec1a` (7 absent `#symbol` anchors in
`metadata-protocol` / `service-storage` citations plus the
`attachments-storage.json` anchor floor); none is in a file this diff
touches, and the new anchor this diff cites
(`object.zod.ts#OBJECT_EXTERNAL_SHARING_MODEL_REMOVED`) resolves. Four
exited 3 (PREREQUISITE NOT MET) and three were re-run green after
building their closures: `check:i18n` (10 packages in sync),
`check:i18n-coverage` (14 configs, none new), `check:skill-examples`
(263 examples). On the final head `1060ed3e7` the full set ran: 125
derived, 125 run, 124 exit 0 (`check:dual-build-cjs-loads` included);
`check:platform-checklist` exit 1 with the 8 findings above; `--ran`:
125 accounted, 0 NOT-MEASURED. *(Corrected by the seat from the dev's
report `6164903067`.)* `check-plugin-teardown-shape --self-test` exited
3 on a shallow clone missing its pinned fixture commit; after fetching
that commit it passes (48 cases).
- ESLint, as a proven narrowing rather than the repo-wide `pnpm lint`.
Population: `eslint.config.mjs` lints
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`.
`--no-inline-config --format json` over the 36 changed lintable files at
`1060ed3e7`: 36 files, 0 errors, 0 warnings. Invariance: the config
enables no type-aware linting (no `parserOptions.project`, no
`projectService`) and the diff touches neither the config nor a
baseline, so no verdict on an untouched file can move.
- Changed lines: 1,334 (+884 / -450, 52 files), under the 3,000
human-merge line. No governed surface is touched.
## Ablations (one-shot, through `scripts/ablation-replace.mjs`, each
restored by blob equal to HEAD with an empty `git diff HEAD`;
spec-source imports, no `dist/` involved)
- The tombstone replaced by the old `z.enum([...]).optional()` (anchor 1
to 0): 7 of the pin's 18 tests went red (schema refusal, every value,
registry binding, `create()`, `defineStack`, the tsc channel's parse
leg, the boot door), 11 stayed green.
- `externalSharingModel: 'private'` planted on `showcase_announcement`
(anchor 1 to 0): exactly the tree-scoped walk went red, naming
`examples/app-showcase/src/data/objects/announcement.object.ts`. A first
attempt whose replacement still contained the anchor was refused by the
tool as a no-op and re-run with a landing replacement.
## Studio
The objectui follow-up (the object settings select and the Ext badge) is
the PM's card (ruling item 4), not in this diff. Until it lands, a
Studio author who picks a value in that select gets the 422 with the
prescription on save, the same refusal pinned above; a stored row
carrying the key is healed on read by the replayed strip. The console
reads the key from an untyped draft, so nothing in objectui stops
compiling: measured at the `.objectui-sha` pin `1fb1ced3`, every read is
`typeof draft.externalSharingModel === 'string'` or on an untyped record
or an `as any` fixture.
## Companion
The ADR-0090 D11 sentence (ruling item 2) is a separate Tier H draft PR
touching only
`docs/adr/0090-permission-model-v2-concept-convergence.md`; that PR
refers to the card and leaves its state to this one.
## Acceptance notes
- **Bound kept:** no `docs/adr/**`, `skills/**`, `.claude/**`,
`.objectui-sha` or `content/docs/releases/**`.
- **Not edited, now stale (governed):**
`skills/objectstack-data/references/lint-rules.md:23` lists
`security-external-wider-than-internal` as a live rule. `skills/**` is
Tier H; a one-row deletion for whoever touches that file next.
- **Not edited, history:** ADR-0086, ADR-0094 and ADR-0138 mention the
key or the rule as part of their own records; the pending
`.changeset/pre/22161-lint-slice-2-one-line.md` and
`.changeset/23224-guest-grants-channel.md` describe the key as it was
when they were written.
- **The out-of-repo population is NOT MEASURED,** including cloud's
stored rows (the stored preview above lists them).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 05ab9ef commit 301eec6
52 files changed
Lines changed: 884 additions & 450 deletions
File tree
- .changeset
- content/docs
- permissions
- protocol/objectql
- references
- api
- data
- system
- docs
- design
- qa/platform-checklist/areas
- examples/app-showcase/src
- data/objects
- docs
- security
- packages
- cli/test
- lint
- scripts
- src
- metadata-protocol/src
- plugins
- plugin-audit/src/objects
- plugin-security/src
- qa/dogfood/test
- rest/src
- spec
- authorable-surface
- liveness
- state-counts
- src
- ai
- conversions
- data
- kernel
- migrations
- entries
- retired-keys
- semantic
- system
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| |||
506 | 506 | | |
507 | 507 | | |
508 | 508 | | |
509 | | - | |
510 | | - | |
| 509 | + | |
511 | 510 | | |
512 | 511 | | |
513 | 512 | | |
| |||
526 | 525 | | |
527 | 526 | | |
528 | 527 | | |
529 | | - | |
| 528 | + | |
530 | 529 | | |
531 | 530 | | |
532 | 531 | | |
| |||
535 | 534 | | |
536 | 535 | | |
537 | 536 | | |
538 | | - | |
539 | | - | |
540 | | - | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
541 | 542 | | |
542 | 543 | | |
543 | 544 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
147 | 138 | | |
148 | 139 | | |
149 | 140 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
404 | 404 | | |
405 | 405 | | |
406 | 406 | | |
407 | | - | |
| 407 | + | |
408 | 408 | | |
409 | 409 | | |
410 | 410 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
969 | 969 | | |
970 | 970 | | |
971 | 971 | | |
972 | | - | |
| 972 | + | |
973 | 973 | | |
974 | 974 | | |
975 | 975 | | |
| |||
0 commit comments