Skip to content

feat: ADR-0025 plugin package distribution — framework F1–F5#1487

Merged
xuyushun441-sys merged 5 commits into
mainfrom
feat/adr-0025-f1-manifest-schema
Jun 1, 2026
Merged

feat: ADR-0025 plugin package distribution — framework F1–F5#1487
xuyushun441-sys merged 5 commits into
mainfrom
feat/adr-0025-f1-manifest-schema

Conversation

@xuyushun441-sys
Copy link
Copy Markdown
Contributor

Implements the framework-side prerequisites (F1–F5) that unblock the cloud control plane's plugin distribution work (cloud PD0–PD3 are already merged against a stopgap mirror of these shapes). Shapes/format/payloads are aligned byte-for-byte with cloud so the cloud swap is a one-line import.

Commits

  • F1 feat(spec) — structured plugin manifest schema. ManifestSchema gains structured permissions {services,hooks,network,fs} (backward-compatible union with legacy string[]), runtime (node|sandbox|worker), packaging (bundled|manifest-deps), integrity, and engines {platform,protocol} (protocol-first); legacy engine retained. New schemas/types exported from @objectstack/spec/kernel — the authoritative shapes cloud mirrors.
  • F2 feat(cli)os plugin build + .osplugin. esbuild bundle (externalizing @objectstack/*), per-file integrity as sha256-<base64> (SRI) — the canonical format the runtime/cloud verify — and a dependency-free, reproducible ustar+gzip writer. Validates the manifest against the F1 schema.
  • F3 feat(core,cli) — Ed25519 signature contract + os plugin sign. core/security/plugin-artifact-signature.ts mirrors cloud's package-signing.ts exactly: ed25519:<keyId>:<base64url>, publisher sig over artifact bytes, platform counter-sign over [package_id,version,blob_key,signature].join("\n"), combined verifyPluginArtifact (ADR §3.7). os plugin sign emits a detached sidecar over the exact bytes cloud verifies.
  • F4 feat(core) — enforce install-time granted_permissions. registerGrantedPermissions() / buildPermissionsFromGrants() turn cloud's persisted consent set into the runtime PluginPermissions that SecurePluginContext checks (least-privilege default).
  • F5 feat(spec) — plugin fields on the uploadArtifact contract (kind, signature, expectedChecksum; result versionId/listingStatus/signatureVerified). Additive + optional.

Verification

  • spec: 6609 tests · core: 275 · cli: 138 — all green.
  • Cross-compat tests assert cloud-contract alignment (signature format/payload, integrity SRI vector) and an end-to-end build → sign → verify against the exact artifact bytes.

Landing in cloud (after merge)

pnpm bump-framework → delete cloud's STOPGAP mirrors → import the F1 schemas from @objectstack/spec/kernel and re-export the shared F3 contract from @objectstack/core/security.

🤖 Generated with Claude Code

os-zhuang and others added 5 commits June 2, 2026 02:14
Extend ManifestSchema with the authoritative plugin-distribution shapes
so the cloud control plane can drop its stopgap mirror and import the
canonical schemas.

- PluginPermissionsSchema: structured { services, hooks, network, fs }
  (.strict()); ADR-0025 §3.2
- PluginEnginesSchema: { platform, protocol } (protocol-first, §3.10 #3)
- PluginRuntimeSchema: node | sandbox | worker (trust tier, §3.6)
- PluginPackagingSchema: bundled | manifest-deps (§3.3)
- PluginIntegritySchema: Record<path, digest> (§3.2)

ManifestSchema.permissions becomes a backward-compatible union of the
legacy string[] and the structured block; new optional runtime /
packaging / integrity / engines fields added. Legacy engine:{objectstack}
retained and superseded by engines.

Shapes match cloud's stopgap (service-cloud/src/plugin-artifact.ts) so
cloud's swap is a one-line import from @objectstack/spec/kernel.

Verified: tsc clean, 6609 spec tests pass, exports surface in
dist/kernel, runtime parse smoke (legacy + structured + strict reject).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the build half of the plugin distribution pipeline (ADR-0025 §3.4):

- src/utils/osplugin.ts — dependency-free packaging primitives:
  - sriDigest(): canonical per-file integrity string `sha256-<base64>`
    (matches ADR §3.2's example; the format cloud/runtime align to).
  - computeIntegrity(): builds the manifest `integrity` map (excludes the
    manifest itself + SIGNATURE; deterministic key order).
  - createTar()/createTarGz(): reproducible ustar+gzip writer (mtime pinned
    to 0, sorted entries) so any tar reader can unpack the artifact and
    identical inputs yield byte-identical blobs.

- src/commands/plugin/build.ts — `os plugin build`:
  1. validate objectstack.plugin.json against the canonical ManifestSchema
     (@objectstack/spec/kernel — F1), failing fast with zod diagnostics;
  2. esbuild-bundle the entry to dist/index.mjs, externalizing
     @objectstack/* (and declared deps for packaging: manifest-deps);
  3. compute per-file integrity + emit the compiled manifest;
  4. pack dist/ (+assets, +package.json/lockfile for manifest-deps,
     +SIGNATURE placeholder) into <id>-<version>.osplugin.

Signing is a separate step; this emits an unsigned artifact.

Tests (6, all green; full CLI suite 143 green): SRI vector, integrity
exclusion+ordering, ustar round-trip with valid checksums, gzip validity,
reproducibility, and an end-to-end build that bundles a fixture plugin and
reads the .osplugin back — exercising F1's schema through the CLI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(ADR-0025 F3)

Land the canonical signature half of the plugin distribution pipeline,
byte-for-byte aligned with the cloud control plane's package-signing so
the two never drift.

core/src/security/plugin-artifact-signature.ts — the shared Ed25519
detached-signature contract:
  - format `ed25519:<keyId>:<base64url>`; sign/verify via node:crypto
    (`sign(null,…)`/`verify(null,…)`), keyId as the rotation handle.
  - verifyPublisherSignature(): publisher sig over raw artifact bytes,
    keyId-resolved key, mirroring cloud's publish-time policy
    (no sig → unverified-but-ok; malformed/unknown-key/mismatch → not ok).
  - counterSignPayload()/verifyPlatformSignature(): platform counter-sign
    over [package_id, version, blob_key, signature].join("\n") — identical
    to cloud's payload.
  - verifyPluginArtifact(): runs both trust chains at load time
    (ADR §3.7); requirePlatform=false for first-party/local builds.
  Exported from @objectstack/core/security.

cli plugin/sign.ts — `os plugin sign <artifact> --key <pem> [--key-id]`:
  detached publisher signature over the EXACT artifact bytes (the bytes
  cloud verifies at publish), written to a `<artifact>.sig` sidecar, with
  a self-verify guard. Completes build → sign → publish.

plugin-loader.ts: replace the placeholder verifyPluginSignature with an
honest check — artifact-bytes/counter-sign verification belongs at
materialize time (no artifact bytes exist at loadPlugin()), so the loader
now validates signature well-formedness via parseSignature and fails fast
on a malformed value, pointing at verifyPluginArtifact for the real chains.

Verified: core 269 tests (incl. 14 signature: format, determinism, tamper,
cloud-contract alignment, publisher policy, counter-sign, combined chains,
KeyObject), cli 138 (incl. build→sign→verify e2e against the exact bytes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bridge the cloud control plane's persisted consent into runtime
enforcement. `PluginPermissionEnforcer.registerGrantedPermissions()` and
`buildPermissionsFromGrants()` turn the structured grant set cloud writes
to `sys_package_installation.granted_permissions`
(`{ services, hooks, network, fs }`, ADR §3.2) into the runtime
`PluginPermissions` bag that `SecurePluginContext` checks.

Matching: exact value, glob (`*` / `**`), or wildcard `*`; network grants
match the request URL's host; `fs` governs read and write; a null/empty
grant set denies everything (least privilege). This enforces what was
GRANTED at install, not merely what the manifest declared — the right
default for distributed third-party plugins.

Verified: 6 new tests (service/hook/fs/network matching, least-privilege
default, enforcer + SecurePluginContext gating); full core suite 275 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend UploadArtifactInput/Result so the IPackageService upload path
carries code-bearing `.osplugin` plugins alongside metadata packages,
aligned with the cloud control plane's publish flow:

- Input: `kind` ('metadata' | 'plugin'), detached `signature`
  (`ed25519:<keyId>:<base64url>`), `expectedChecksum` (artifact sha256).
- Result: `versionId`, `listingStatus` (e.g. pending_review), and
  `signatureVerified`.

All additive + optional — metadata packages are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Error Error Jun 1, 2026 7:03pm

Request Review

@xuyushun441-sys xuyushun441-sys merged commit f950b43 into main Jun 1, 2026
10 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants