Skip to content

Commit e170b0a

Browse files
Sam Altmanclaude
andauthored
feat(security): lock package-declared permission sets at the save door; clone to customize (#11702)
Maintainer ruling 2026-08-24 (verbatim, untranslated): 「同意 第一步(创业阶段,Salesforce 式)」 — step 1: lock the base, clone to customize. A Studio/API save targeting a package-declared permission set is refused at the server, with a message that names the sanctioned path. Previously the data door translated the write and left the refusal to the metadata protocol's ADR-0005 tier gate — which is exactly what the documented OS_METADATA_WRITABLE=permission operator hatch switches off, so with the hatch open there was no refusal at all and the save minted a sys_metadata overlay of a packaged set that boot reconciliation then re-projected on every boot, forever. Provenance is decided from the engine SchemaRegistry — the one source this plugin already calls "package-declared" — never from a name-keyed page over sys_permission_set, whose unscoped cap truncates into a false "absent" (#11518, untouched here). Fail-closed: a read that cannot answer refuses the save. Not narrowed: ordinary org-owned sets, the activate/deactivate column writes, and a managed_by:'package' row with no artifact behind it (ADR-0094 D5-R's surviving allowRuntimeCreate neighbour). Each is pinned as a control. Existing forks get a detection READING at boot — count and names, warned loudly, reaping nothing. It reads sys_metadata directly rather than `customized`, which is forced false on the exact confounded shape the field report measured. Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4 Co-authored-by: Claude <noreply@anthropic.com>
1 parent b040ed3 commit e170b0a

9 files changed

Lines changed: 1619 additions & 13 deletions
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
'@objectstack/plugin-security': minor
3+
---
4+
5+
Lock package-declared permission sets at the save door; clone to customize (#11513)
6+
7+
Maintainer ruling of 2026-08-24, recorded verbatim and untranslated:
8+
「同意 第一步(创业阶段,Salesforce 式)」 — step 1 of the mainstream-platform
9+
comparison: lock the base, clone to customize.
10+
11+
A Studio/API save that targets a **package-declared** permission set is now
12+
**refused at the server**, with a message that names the sanctioned path — clone
13+
it and edit the clone. Previously the data door translated the write into a
14+
metadata write and left the refusal entirely to the metadata protocol's ADR-0005
15+
tier gate. That gate is exactly what the documented
16+
`OS_METADATA_WRITABLE=permission` operator hatch switches off, so on a
17+
deployment running with the hatch there was no refusal at all: the save minted a
18+
`sys_metadata` overlay of a packaged set, and boot reconciliation re-projected
19+
that overlay onto the record on every boot, unconditionally, forever — the set
20+
froze at the fork and every future package upgrade of it was ignored, silently.
21+
22+
**Clone-to-customize** is the sanctioned path and is unchanged: the clone is an
23+
ordinary org-owned set (`managed_by: 'admin'`, no `package_id`, so no upgrade
24+
linkage), and upgrades keep flowing to the package-declared base untouched.
25+
26+
**Existing forks** get a **detection reading** at boot — count *and names*,
27+
warned loudly, saying outright that nothing was reaped. It reads `sys_metadata`
28+
directly rather than the `customized` column, which is forced `false` on the
29+
exact confounded shape the field report measured (a genuinely package-declared
30+
set whose row's `managed_by` predates provenance tracking). Nothing is reaped,
31+
merged or migrated: disposition of an existing fork is a follow-up reading for
32+
the maintainer, and the per-set remedy remains the explicit, audited
33+
"Discard Overlay" action a human invokes.
34+
35+
Behaviour deliberately NOT narrowed:
36+
37+
- an **ordinary org-owned** set is still fully editable (pinned as a control —
38+
a lock that refuses everything would satisfy the refusal pin perfectly);
39+
- the **activate / deactivate** actions still write their column: a bare
40+
`{ active }` patch is row state, not a customization of the definition;
41+
- a `managed_by: 'package'` row with **no artifact behind it** — published
42+
through the metadata door (ADR-0070) and materialized by the ADR-0086 P2
43+
path — keeps editing in place. That is ADR-0094 D5-R's surviving
44+
`allowRuntimeCreate` neighbour, and `managed_by` is measurably not the
45+
artifact-provenance fact. Provenance is read from the engine SchemaRegistry,
46+
the one source this plugin already calls "package-declared".
47+
48+
Provenance is **fail-closed**: a read that cannot answer refuses the save rather
49+
than accepting it, and the read is not a name-keyed page over
50+
`sys_permission_set`, so it cannot be truncated into a false "not packaged".

‎packages/plugins/plugin-security/src/index.ts‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,29 @@ export type {
116116
PermissionSetOverlayDiscardDeps,
117117
PermissionSetOverlayDiscardResult,
118118
} from './permission-set-overlay-discard.js';
119+
// [maintainer ruling 2026-08-24 — 「同意 第一步(创业阶段,Salesforce 式)」]
120+
// Lock the base, clone to customize: the write-door provenance rule and its
121+
// refusals, plus the DETECTION READING for overlays that already exist.
122+
// ⛔ The reading reaps nothing — disposition of existing forks is a follow-up
123+
// reading for the maintainer.
124+
export {
125+
ENV_PROJECTION_MARKER,
126+
classifyPackagedPermissionSet,
127+
assertPermissionSetNotPackageDeclared,
128+
PackagedPermissionSetLockedError,
129+
PackagedPermissionSetProvenanceUnknownError,
130+
} from './packaged-permission-set-lock.js';
131+
export type {
132+
PackagedSetVerdict,
133+
LayeredProbe,
134+
} from './packaged-permission-set-lock.js';
135+
export {
136+
OVERLAY_PAGE_LIMIT,
137+
detectPackagedPermissionSetOverlays,
138+
reportPackagedPermissionSetOverlays,
139+
} from './packaged-permission-set-overlay-detection.js';
140+
export type {
141+
PackagedPermissionSetOverlayFinding,
142+
PackagedPermissionSetOverlayReading,
143+
OverlayDetectionOptions,
144+
} from './packaged-permission-set-overlay-detection.js';

0 commit comments

Comments
 (0)