Skip to content

Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces#3

Merged
huangyiirene merged 4 commits into
mainfrom
copilot/set-up-copilot-instructions
Jan 18, 2026
Merged

Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces#3
huangyiirene merged 4 commits into
mainfrom
copilot/set-up-copilot-instructions

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 18, 2026

Establishes the foundational type system and conventions for the ObjectStack ecosystem. This defines the "Constitution" - shared interfaces, validation schemas, and directory conventions used by ObjectOS, ObjectStudio, ObjectCloud, and third-party plugins.

Core Components

  • Manifest Schema (src/schemas/manifest.zod.ts)

    • Zod-first schema with type inference for package configuration
    • Defines package metadata (id, type, version), permissions, menu structure, entity patterns, and extension points
    • Validates app/plugin/driver/module packages
  • Plugin Runtime Interface (src/types/plugin.ts)

    • Lifecycle contract: onInstall, onEnable, onDisable
    • PluginContext provides ql (ObjectQLClient), os (ObjectOSKernel), and logger
    • Defines ObjectQL query/mutation interface and kernel event bus
  • Directory Conventions (src/constants/paths.ts)

    • Hardcoded paths: src/schemas, src/triggers, src/client/pages, assets
    • Standard files: objectstack.config.ts, src/index.ts
    • Type-safe path constants for runtime and tooling

Usage

import { ManifestSchema, ObjectStackPlugin, PKG_CONVENTIONS } from '@objectstack/spec';

// Validate package manifest
const manifest = ManifestSchema.parse({
  id: 'com.example.crm',
  version: '1.0.0',
  type: 'plugin',
  permissions: ['system.user.read']
});

// Implement plugin
export default function(): ObjectStackPlugin {
  return {
    async onInstall(ctx) { /* setup */ },
    async onEnable(ctx) { /* start */ },
    async onDisable(ctx) { /* cleanup */ }
  };
}

All types include comprehensive TSDoc for IntelliSense. No runtime dependencies except Zod. Universal compatibility (Node.js/Browser/Electron).

Original prompt

This section details on the original issue you should resolve

<issue_title>✨ Set up Copilot instructions</issue_title>
<issue_description>📜 ObjectStack Protocol & Specification Context
Role: You are the Chief Architect and Standards Committee for the ObjectStack Ecosystem.
Mission: Define the "Constitution" of the system. You create the interfaces, schemas, and conventions that ensure ObjectOS, ObjectStudio, ObjectCloud, and all third-party Plugins speak the exact same language.
Guiding Principle: "Strict Types, No Logic."
This repository contains NO database connections, NO UI components, and NO runtime business logic. It contains only:

  • TypeScript Interfaces (Shared types).
  • JSON Schemas / Zod Schemas (Validation rules).
  • Constants (Convention configurations).
  1. The "Manifest" Standard (Core Responsibility)
    You define what a "Package" looks like in ObjectStack.
  • Schema Location: src/schemas/manifest.zod.ts (Export to JSON Schema).
  • Key Definition: The ObjectStackManifest interface.
    • id: Unique identifier (e.g., com.example.crm).
    • type: app | plugin | driver | module.
    • permissions: Array of permission strings requested (e.g., ["system.user.read"]).
    • menus: Navigation structure injection.
    • entities: Glob patterns for ObjectQL files (e.g., ["./src/schema/*.gql"]).
    • extensions: Extension points (e.g., contributions to the UI).
  1. Directory Conventions (Law of Location)
    You define "Where things must be". Hardcode these paths so CLI and Runtime match perfectly.
  • File: src/constants/paths.ts
  • Rules:
    • Schemas MUST be in src/schemas.
    • Server triggers MUST be in src/triggers.
    • Client pages MUST be in src/client/pages.
    • Assets MUST be in assets.
  1. Runtime Interfaces (The Contract)
    You define the interface that every plugin must implement to be loaded by ObjectOS.
  • File: src/types/plugin.ts
  • Interface: ObjectStackPlugin
    • onInstall(ctx: PluginContext): Promise
    • onEnable(ctx: PluginContext): Promise
    • onDisable(ctx: PluginContext): Promise
  • Context: PluginContext
    • Must expose ql (ObjectQLClient), os (ObjectOSKernel), logger.
  1. Coding Rules for AI
    A. Zod First Strategy
    When defining schemas (like the Manifest), ALWAYS use Zod first.
  • Why: Zod allows us to infer the TypeScript type (z.infer) AND generate the JSON Schema for the VS Code extension/CLI validator from a single source of truth.
    B. Universal Compatibility
  • The code generated here must run in Node.js (CLI/OS), Browser (Studio/UI), and Electron.
  • Do not import Node.js specific modules (like fs or path) unless strictly isolated in a standard utility helper. Ideally, keep it pure JS/TS.
    C. Documentation is Code
    Since this is the protocol, every interface property must have TSDoc comments (/** ... */). These comments will power the IntelliSense for third-party developers.
  1. Mock Examples (Reference)
    Example: Defining the Manifest Schema (Zod)
    import { z } from 'zod';

export const ManifestSchema = z.object({
id: z.string().describe("Unique package identifier (reverse domain style)"),
version: z.string().regex(/^\d+.\d+.\d+$/),
type: z.enum(['app', 'plugin', 'driver']),
menus: z.array(z.object({
label: z.string(),
path: z.string(),
icon: z.string().optional()
})).optional()
});

export type ObjectStackManifest = z.infer;

Example: Defining Directory Constants
export const PKG_CONVENTIONS = {
// The Source of Truth for where the Engine looks for files
DIRS: {
SCHEMA: 'src/schemas',
SERVER: 'src/server',
CLIENT: 'src/client'
},
FILES: {
MANIFEST: 'objectstack.config.ts',
ENTRY: 'src/index.ts'
}
} as const;</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits January 18, 2026 08:52
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Copilot AI changed the title [WIP] Set up Copilot instructions for ObjectStack Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces Jan 18, 2026
Copilot AI requested a review from huangyiirene January 18, 2026 08:57
@huangyiirene huangyiirene marked this pull request as ready for review January 18, 2026 09:20
@huangyiirene huangyiirene merged commit a116aef into main Jan 18, 2026
1 check failed
Copilot AI added a commit that referenced this pull request Jan 25, 2026
- Added Chinese meta files for all website protocol sections
- Updated references meta files to include website protocol
- Updated README to document the 6 core protocol modules
- Added Website Protocol as module #3 with preview release date March 2026

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
xuyushun441-sys pushed a commit that referenced this pull request May 22, 2026
Introduces an opt-in path in ObjectStackProtocolImplementation.saveMetaItem
that writes overlay metadata through SysMetadataRepository.put instead of
the raw engine, so writes append to the change-log and emit HMR seq events.

Behavioural changes (all behind options.useRepositoryWritePath /
OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1):
- saveMetaItem request gained optional parentVersion (If-Match) and
  actor fields. ConflictError -> 409 metadata_conflict.
- Plural type aliases (views, dashboards, ...) normalized to singular
  before the repo's overlay-allowlist gate (rubber-duck #5).
- Object-registry mutation moved AFTER successful put() so a conflict
  does not leave the in-memory registry stale (rubber-duck #3 invariant
  test added).

Repo/test-fake fixes uncovered by rubber-duck review:
- SysMetadataRepository.put/delete now update/delete by row id because
  the engine's strict .update requires id or multi:true (rubber-duck #1).
- sys_metadata.checksum column widened from 64 -> 71 chars to hold the
  sha256: prefix produced by hashSpec() (rubber-duck #2).
- Three test fake engines extended to support both overlay-tuple and
  id-based where lookups.

333/333 objectql tests pass.

Deferred to PR-10d.4: REST plumbing for parentVersion/actor
(rubber-duck #6), race-window retry for omitted parentVersion
(rubber-duck #4), default flag flip + legacy path removal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys pushed a commit that referenced this pull request May 23, 2026
…s (PR #3)

Resolves the gap left by PR #2: cache + storage adapters accepted an
optional MetricsRegistry but their respective Plugin classes never
forwarded one, so any host that registered observability via the
dispatcher saw zero cache/storage data.

Adds:

  packages/observability/src/service-names.ts
    OBSERVABILITY_METRICS_SERVICE = 'observability:metrics'
    OBSERVABILITY_ERRORS_SERVICE  = 'observability:errors'

  packages/runtime/src/observability/observability-service-plugin.ts
    ObservabilityServicePlugin — registers the host's MetricsRegistry
    and ErrorReporter under the canonical names. Defaults each to its
    respective Noop exporter so the services are always present.
    Also exports resolveMetrics() / resolveErrorReporter() helpers for
    consumers inside the runtime package.

CacheServicePlugin + StorageServicePlugin:
  - new `metrics?: MetricsRegistry` option (escape hatch for tests)
  - canonical resolution chain at init():
      option override → observability:metrics service → NoopMetricsRegistry
  - StorageServicePlugin's `buildAdapterFromValues` (the settings
    live-rebuild path) now also threads metrics into the freshly built
    adapter, so adapter swaps don't drop instrumentation
  - log line now reports the resolved registry class name for diagnostics

Helpers (resolveMetrics) are inlined as private functions in each
service to avoid a circular dep (services must not depend on runtime).
Constants live in @objectstack/observability which both services
already depend on.

New tests:
  - cache-service-plugin.metrics.test.ts (4 tests, resolution chain + override precedence)
  - storage-service-plugin.metrics.test.ts (4 tests, incl. settings-rebuild path)
  - observability-service-plugin.test.ts  (3 tests, registration + defaults)

All 22 service-cache + 48 service-storage + 282 runtime tests pass
(2 pre-existing i18n failures in app-plugin.test.ts on main unchanged).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys pushed a commit that referenced this pull request May 29, 2026
…s/skills

Persist package enable/disable (#2):
- SchemaRegistry.setInitialDisabledPackageIds() seeds a disabled-id set that
  installPackage honors, so every registration path (boot artifact, marketplace
  rehydrate, local import) applies persisted disable uniformly — no fragile
  post-boot reapply hook.
- New runtime/package-state-store.ts persists the disabled set to
  <OS_HOME>/package-state/<environmentId>.json, keyed per environment.
- AppPlugin.init seeds the registry before the manifest is decomposed.
- handlePackages enable/disable persist the new state.

Round-trip tools/skills on export & import (#3):
- PLURAL_TO_SINGULAR (spec) gains tools->tool, skills->skill (drives export
  via assemblePackageManifest and the auto-derived reverse map).
- engine.registerApp metadataArrayKeys consume tools/skills on import.
- metadata ARTIFACT_FIELD_TO_TYPE gains tools->tool (skills already present).
- ObjectStackDefinition gains a top-level tools field beside agents/skills.
Covers metadata round-trip/visibility; executable ToolRegistry wiring is out
of scope.

Docs: ADR-0016 §§9.5–9.8 updated (disable now persists; tools/skills round-trip).

Verified live: disable -> restart -> stays disabled & app hidden; enable
clears state & app returns. registerApp materializes tools/skills.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys added a commit that referenced this pull request Jun 1, 2026
* feat(spec): structured plugin manifest schema (ADR-0025 F1)

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>

* feat(cli): `os plugin build` + .osplugin packaging (ADR-0025 F2)

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>

* feat(core,cli): Ed25519 plugin signature contract + `os plugin sign` (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>

* feat(core): enforce install-time granted_permissions (ADR-0025 F4)

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>

* feat(spec): plugin fields on uploadArtifact contract (ADR-0025 F5)

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>

---------

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Set up Copilot instructions

3 participants