Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/13458-manifest-permissions-string-list-retired.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
'@objectstack/spec': minor
'@objectstack/runtime': patch
'@objectstack/plugin-security': patch
---

feat(spec)!: a package manifest's `permissions` no longer takes a flat list of permission strings — the structured `{ services, hooks, network, fs }` block is the only form (#13458)

Clause-②: no (narrowing)

<!-- adr-0087: registered manifest-permissions-string-list-removed, manifest-permissions-string-list-retired -->

**BREAKING** — an accept-set narrowing on a published authoring surface, shipped as `minor` under the launch-window convention for accept-set narrowings (Changesets pre mode is not yet in on `main`).

`ManifestPermissionsSchema` was a union of a flat `string[]` and the structured ADR-0025 §3.2 block. Nothing ever acted on the list: the loader registers the consented `grantedPermissions` set from the environment artifact with the permission enforcer, never the manifest's request, and the only code that met a list on a manifest was two reports saying it had been skipped. The marketplace install disclosure reads only the four lists, so a list was shown to an installer as "Requests no special permissions." (ADR-0049 enforce-or-remove). `ManifestPermissionsSchema` is now the structured block itself.

### FROM → TO

| before | what to write instead |
| --- | --- |
| `permissions: ['system.user.read', 'system.data.write']` | `permissions: { services: [...], hooks: [...], network: [...], fs: [...] }`, naming the platform services the plugin resolves, the lifecycle hooks it registers, the network hosts it reaches and the filesystem paths it touches. |
| `permissions: []` | delete `permissions`: absence is the spelling for "requests nothing". |
| a list on an app manifest that ships no code | delete `permissions`. An app's record access is its permission SETS, in the stack's own top-level `permissions` collection. |

**The one-line fix: replace every manifest `permissions` list with the structured block, or delete it.** A permission string has no mechanical mapping onto the four lists, so the translation is done by hand. `os migrate meta --from 17` lists the mechanical edits for existing sources; apply them by hand.

**What an author now sees.** Writing a list fails `tsc` (the key's type is the block), and `os validate`, `os build`, `os plugin build` and `defineStack` refuse it at `manifest.permissions` with the block's own answer: `Expected the plugin permission block { services?, hooks?, network?, fs? }, received a flat list.`, followed by the prescription. Every structured block that parsed before still parses, unchanged.

### The retirement kit

- **Schema.** `ManifestPermissionsSchema` is `PluginPermissionsSchema`, by identity, and both export names stay. The block answers a list with its prescription on its own error map, the bare-array pattern `ListViewExportOptionsSchema` uses. The block also carries `EnvironmentArtifactSchema.grantedPermissions` values, so the answer is worded true there too, where a list was never legal.
- **D2 conversion `manifest-permissions-string-list-removed`** (step 18, retired from the load path): a lossless delete of an all-string list from the stack's `manifest` and every `packages[].manifest`. The notice carries the dropped strings. A built artifact replays it at the artifact door, so an artifact built while the list was legal still boots. It never touches the structured block, an array of objects, or the top-level ADR-0090 permission-set collection.
- **D3 entry `manifest-permissions-string-list-retired`** carries the judgement the delete cannot make: what each dropped string meant in services, hooks, hosts and paths.
- **Liveness.** `manifest.permissions` stays `live` on corrected evidence. Its consumer is the marketplace install disclosure, and it refuses nothing at load. The four keys are now drilled.
- **The two skip reports reworded.** `AppPlugin`'s security registrar and `@objectstack/plugin-security`'s audience-binding reconciler both report a manifest-stage `permissions` they cannot read as permission sets. They now name the flat list as the retired legacy form. They behave as before.

**Measured producers: none outside tests.** On origin/main e67ba80049, no manifest in `examples/`, `apps/`, `packages/`, `skills/` or `content/docs/` writes a list. The exceptions are five `@objectstack/spec` `manifest.test.ts` fixtures, re-triaged here, and the skip-report tests of `@objectstack/runtime` and `@objectstack/plugin-security`, which hand a list to an unparsed bundle on purpose and still pass. The same instrument finds those five fixtures, which is its control. At the objectui pin `a58626c88dc8`, nothing reads `manifest.permissions` (control: 91 `manifest.(id|name|version)` reads), and the install disclosure reads the structured block alone. Deployed and cloud-held manifests NOT MEASURED.
8 changes: 5 additions & 3 deletions content/docs/protocol/kernel/plugin-spec.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -631,9 +631,11 @@ consent request; what the runtime enforces is the *granted* set.

### Declared Permissions

`ManifestSchema.permissions` accepts either the legacy flat `string[]` or the structured
`PluginPermissionsSchema` block — four keys, and no `system` list
(`packages/spec/src/kernel/manifest.zod.ts`):
`ManifestSchema.permissions` takes the structured `PluginPermissionsSchema` block — four
keys, and no `system` list (`packages/spec/src/kernel/manifest.zod.ts`). Its legacy flat
`string[]` form was retired in `@objectstack/spec` 17: nothing ever read the list, a list
is refused at parse with the migration prescription, and `os migrate meta --from 17` lists
where to delete one — translating each string into the four lists is done by hand.

```typescript
permissions: {
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/api/package-api-assembled.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ Installed package with runtime lifecycle state
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down
12 changes: 5 additions & 7 deletions content/docs/references/api/package-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Install package request
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down Expand Up @@ -192,7 +192,7 @@ Install package request
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand All @@ -212,8 +212,6 @@ Install package request

### Nested Shape: `PackageInstallBody[option 2].permissions`

Structured plugin permission grants (ADR-0025 §3.2)

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **services** | `string[]` | optional | Platform services the plugin may resolve (e.g. "object", "http") |
Expand Down Expand Up @@ -311,7 +309,7 @@ Install package request
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down Expand Up @@ -444,7 +442,7 @@ Upgrade package request
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down Expand Up @@ -539,7 +537,7 @@ Resolve dependencies request
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/api/protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1934,7 +1934,7 @@ Install package request
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down
18 changes: 1 addition & 17 deletions content/docs/references/kernel/manifest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const result = ManifestSchema.parse(data);
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand All @@ -56,8 +56,6 @@ const result = ManifestSchema.parse(data);

### Nested Shape: `Manifest.permissions`

Structured plugin permission grants (ADR-0025 §3.2)

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **services** | `string[]` | optional | Platform services the plugin may resolve (e.g. "object", "http") |
Expand Down Expand Up @@ -128,18 +126,6 @@ A navigation contribution: a package injecting nav items into an app it does not

## ManifestPermissions

### Union Options

This schema accepts one of the following structures:

#### Option 1

Type: `string[]`

---

#### Option 2

Structured plugin permission grants (ADR-0025 §3.2)

### Properties
Expand All @@ -151,8 +137,6 @@ Structured plugin permission grants (ADR-0025 §3.2)
| **network** | `string[]` | optional | Network hosts the plugin may reach (e.g. "api.acme.com") |
| **fs** | `string[]` | optional | Filesystem paths the plugin may access |

---


---

Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/kernel/package-registry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ Install package request
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down Expand Up @@ -296,7 +296,7 @@ Installed package with runtime lifecycle state
| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
| **name** | `string` | ✅ | Human-readable package name |
| **description** | `string` | optional | Package description |
| **permissions** | `string[] \| { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
Expand Down
Loading
Loading