Filed by the repo:cloud execution seat (seat post #6026, session session_017Wu48ZSjk7HJjyJmGNBtUk, R35) as the framework half that #14034's measurement (comment on that card, 2026-09-03) showed must land first. Triage: domain:* and type are yours — expected domain:spec; Clause-② yes (a new key on a published contract face) ⇒ CONTRACT_REVIEW_TIER. Dedup: no open objectstack card declares a granted-permissions field on the environment artifact (granted across packages/spec/src returns zero permission declarations at pin 655b106c; the requested set is manifest.zod.ts:290-291).
Ruling this executes
#11333 option A (2026-08-30 「同意」) and the #13457 batch ruling (2026-09-01 「同意」): the consented four-class permission set {services, hooks, network, fs} rides the plugin artifact contract; cloud (owner of the install-consent flow) writes it at consent-compile time, objectstack's loader consumes it from the environment-local carrier at materialize time (ADR-0003 / cloud ADR-0007: sys_package_installation is never read on a runtime path).
Measured on cloud 2f469233 × pin 655b106c (details on #14034)
- The consent flow writes the granted set to exactly one place:
sys_package_installation.granted_permissions (cloud routes/package-install.ts:640-652, written :667/:678), preserving absent vs empty (:651-653 emits it only when consent-bearing).
- The compiled environment artifact envelope (cloud
routes/cloud.ts:747-757: schemaVersion, environmentId, commitId, checksum, metadata, functions, manifest, builtAt, builtWith, runtime) does not carry it; the marketplace-install merge loop reads the install row but consumes only enabled / package_version_id / with_sample_data.
- The env-local carrier (
env-artifact-cache.ts:88-102) stores JSON.stringify(artifact) verbatim ⇒ once the envelope carries the key, the carrier carries it for free — the ruling's carrier assumption holds.
- The consumer at the pin wants a per-plugin map:
registerGrantedPermissions(pluginName, PluginPermissions) (packages/core/src/security/plugin-permission-enforcer.ts:102-111), value shape PluginPermissionsSchema (packages/spec/src/kernel/manifest.zod.ts:32-44, .strict()).
- Pin-lag / silent-stripping:
EnvironmentArtifactSchema (packages/spec/src/system/environment-artifact.zod.ts:83) and ManifestSchema (manifest.zod.ts:158) are plain z.object, so an undeclared key written by cloud is stripped at objectstack's artifact door (packages/metadata/src/plugin.ts:900-925, parse at :908/:915). A cloud-first landing would fail silently. ⇒ spec first, pin bump, cloud second.
The ask (spec half)
Declare one top-level envelope key on EnvironmentArtifactSchema:
grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional()
Coupling
Refs: #14034 · #11333 · #13457 · #13458 · ADR-0003 / cloud ADR-0007 · ADR-0025.
Filed by the
repo:cloudexecution seat (seat post #6026, sessionsession_017Wu48ZSjk7HJjyJmGNBtUk, R35) as the framework half that #14034's measurement (comment on that card, 2026-09-03) showed must land first. Triage:domain:*and type are yours — expecteddomain:spec; Clause-② yes (a new key on a published contract face) ⇒CONTRACT_REVIEW_TIER. Dedup: no open objectstack card declares a granted-permissions field on the environment artifact (grantedacrosspackages/spec/srcreturns zero permission declarations at pin655b106c; the requested set ismanifest.zod.ts:290-291).Ruling this executes
#11333 option A (2026-08-30 「同意」) and the #13457 batch ruling (2026-09-01 「同意」): the consented four-class permission set
{services, hooks, network, fs}rides the plugin artifact contract; cloud (owner of the install-consent flow) writes it at consent-compile time, objectstack's loader consumes it from the environment-local carrier at materialize time (ADR-0003 / cloud ADR-0007:sys_package_installationis never read on a runtime path).Measured on cloud
2f469233× pin655b106c(details on #14034)sys_package_installation.granted_permissions(cloudroutes/package-install.ts:640-652, written:667/:678), preserving absent vs empty (:651-653emits it only when consent-bearing).routes/cloud.ts:747-757:schemaVersion, environmentId, commitId, checksum, metadata, functions, manifest, builtAt, builtWith, runtime) does not carry it; the marketplace-install merge loop reads the install row but consumes onlyenabled/package_version_id/with_sample_data.env-artifact-cache.ts:88-102) storesJSON.stringify(artifact)verbatim ⇒ once the envelope carries the key, the carrier carries it for free — the ruling's carrier assumption holds.registerGrantedPermissions(pluginName, PluginPermissions)(packages/core/src/security/plugin-permission-enforcer.ts:102-111), value shapePluginPermissionsSchema(packages/spec/src/kernel/manifest.zod.ts:32-44,.strict()).EnvironmentArtifactSchema(packages/spec/src/system/environment-artifact.zod.ts:83) andManifestSchema(manifest.zod.ts:158) are plainz.object, so an undeclared key written by cloud is stripped at objectstack's artifact door (packages/metadata/src/plugin.ts:900-925, parse at:908/:915). A cloud-first landing would fail silently. ⇒ spec first, pin bump, cloud second.The ask (spec half)
Declare one top-level envelope key on
EnvironmentArtifactSchema:id(recommended on [repo:cloud] The plugin artifact contract carries granted_permissions — ruled producer half of #11333 Phase 1 #14034: it is the identity the enforcer is queried with —AppPluginderives the kernel plugin name frombundle.manifest.id; keying on the control-planepackage_idwould need a second name→package resolution path, the shape A multi-package artifact serializes its metadata twice — the flattened top level and everypackages[i]body carry the same definitions #14512's triage rejected). Pin the residual risk: a package whose manifest id differs from itspackage_id.{}= consent-bearing and consented to nothing — the distinction cloud already writes and the only signal Phase 1 of #11333: wire granted_permissions into PluginPermissionEnforcer (F4) as the load-time gate #13457 clause 1.3 can decide on. ⛔ Do not collapse.manifest.zod.ts:19-31pointing at it; pin tests in the artifact schema's test file (declared key survives parse; unknown sibling still stripped).Coupling
repo:cloud, the cloud write) ispm:blockedon this card; after it lands and cloud's.objectstack-shacovers it, the cloud half is: write the map in the install loop that already holds the row, add it to the envelope literal, tests;env-artifact-cache.tsneeds no change.ArtifactKernelFactory) lives in cloud'sobjectos-runtime, so the consumer half spans both repos (objectstack owns the enforcer seam, cloud owns the call).Refs: #14034 · #11333 · #13457 · #13458 · ADR-0003 / cloud ADR-0007 · ADR-0025.