From 0618dae3f96fa385b53394203325f1d605155a78 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Feb 2026 05:55:01 +0000
Subject: [PATCH 1/2] Initial plan
From 2320d56ffb29c5202ea27f776affde55d33d5a72 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Feb 2026 06:01:00 +0000
Subject: [PATCH 2/2] Add version schema utilities to shared protocol
Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
---
content/docs/references/shared/index.mdx | 1 +
content/docs/references/shared/meta.json | 3 +-
.../json-schema/shared/ReleaseChannel.json | 7 +
.../json-schema/shared/SemanticVersion.json | 7 +
.../json-schema/shared/VersionConstraint.json | 7 +
.../json-schema/shared/VersionMetadata.json | 7 +
.../spec/json-schema/shared/VersionRange.json | 7 +
packages/spec/src/shared/index.ts | 1 +
packages/spec/src/shared/version.test.ts | 277 ++++++++++++++++++
packages/spec/src/shared/version.zod.ts | 199 +++++++++++++
10 files changed, 515 insertions(+), 1 deletion(-)
create mode 100644 packages/spec/json-schema/shared/ReleaseChannel.json
create mode 100644 packages/spec/json-schema/shared/SemanticVersion.json
create mode 100644 packages/spec/json-schema/shared/VersionConstraint.json
create mode 100644 packages/spec/json-schema/shared/VersionMetadata.json
create mode 100644 packages/spec/json-schema/shared/VersionRange.json
create mode 100644 packages/spec/src/shared/version.test.ts
create mode 100644 packages/spec/src/shared/version.zod.ts
diff --git a/content/docs/references/shared/index.mdx b/content/docs/references/shared/index.mdx
index 96d553be1..bbf0016b3 100644
--- a/content/docs/references/shared/index.mdx
+++ b/content/docs/references/shared/index.mdx
@@ -11,5 +11,6 @@ This section contains all protocol schemas for the shared layer of ObjectStack.
+
diff --git a/content/docs/references/shared/meta.json b/content/docs/references/shared/meta.json
index 5ad22d319..032a67262 100644
--- a/content/docs/references/shared/meta.json
+++ b/content/docs/references/shared/meta.json
@@ -3,6 +3,7 @@
"pages": [
"http",
"identifiers",
- "mapping"
+ "mapping",
+ "version"
]
}
\ No newline at end of file
diff --git a/packages/spec/json-schema/shared/ReleaseChannel.json b/packages/spec/json-schema/shared/ReleaseChannel.json
new file mode 100644
index 000000000..aaa5ec45d
--- /dev/null
+++ b/packages/spec/json-schema/shared/ReleaseChannel.json
@@ -0,0 +1,7 @@
+{
+ "$ref": "#/definitions/ReleaseChannel",
+ "definitions": {
+ "ReleaseChannel": {}
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/shared/SemanticVersion.json b/packages/spec/json-schema/shared/SemanticVersion.json
new file mode 100644
index 000000000..2a906e9e2
--- /dev/null
+++ b/packages/spec/json-schema/shared/SemanticVersion.json
@@ -0,0 +1,7 @@
+{
+ "$ref": "#/definitions/SemanticVersion",
+ "definitions": {
+ "SemanticVersion": {}
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/shared/VersionConstraint.json b/packages/spec/json-schema/shared/VersionConstraint.json
new file mode 100644
index 000000000..7784609f3
--- /dev/null
+++ b/packages/spec/json-schema/shared/VersionConstraint.json
@@ -0,0 +1,7 @@
+{
+ "$ref": "#/definitions/VersionConstraint",
+ "definitions": {
+ "VersionConstraint": {}
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/shared/VersionMetadata.json b/packages/spec/json-schema/shared/VersionMetadata.json
new file mode 100644
index 000000000..3ead4d68e
--- /dev/null
+++ b/packages/spec/json-schema/shared/VersionMetadata.json
@@ -0,0 +1,7 @@
+{
+ "$ref": "#/definitions/VersionMetadata",
+ "definitions": {
+ "VersionMetadata": {}
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/shared/VersionRange.json b/packages/spec/json-schema/shared/VersionRange.json
new file mode 100644
index 000000000..a6f8e89b8
--- /dev/null
+++ b/packages/spec/json-schema/shared/VersionRange.json
@@ -0,0 +1,7 @@
+{
+ "$ref": "#/definitions/VersionRange",
+ "definitions": {
+ "VersionRange": {}
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/src/shared/index.ts b/packages/spec/src/shared/index.ts
index efa0e42b5..db1d9a953 100644
--- a/packages/spec/src/shared/index.ts
+++ b/packages/spec/src/shared/index.ts
@@ -6,3 +6,4 @@
export * from './identifiers.zod';
export * from './mapping.zod';
export * from './http.zod';
+export * from './version.zod';
diff --git a/packages/spec/src/shared/version.test.ts b/packages/spec/src/shared/version.test.ts
new file mode 100644
index 000000000..28c8e5d61
--- /dev/null
+++ b/packages/spec/src/shared/version.test.ts
@@ -0,0 +1,277 @@
+import { describe, it, expect } from 'vitest';
+import {
+ SemanticVersionSchema,
+ VersionRangeSchema,
+ VersionConstraintSchema,
+ ReleaseChannelSchema,
+ VersionMetadataSchema,
+} from './version.zod';
+
+describe('Version Schemas', () => {
+ describe('SemanticVersionSchema', () => {
+ describe('Valid versions', () => {
+ it('should accept basic semantic versions', () => {
+ expect(() => SemanticVersionSchema.parse('0.0.0')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('0.1.0')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('0.0.1')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('10.20.30')).not.toThrow();
+ });
+
+ it('should accept versions with prerelease', () => {
+ expect(() => SemanticVersionSchema.parse('1.0.0-alpha')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0-alpha.1')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0-beta.2')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0-rc.1')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0-0.3.7')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0-x.7.z.92')).not.toThrow();
+ });
+
+ it('should accept versions with build metadata', () => {
+ expect(() => SemanticVersionSchema.parse('1.0.0+20130313144700')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0+exp.sha.5114f85')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0+21AF26D3-117B344092BD')).not.toThrow();
+ });
+
+ it('should accept versions with prerelease and build metadata', () => {
+ expect(() => SemanticVersionSchema.parse('1.0.0-alpha+001')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0-beta+exp.sha.5114f85')).not.toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.0-rc.1+build.123')).not.toThrow();
+ });
+
+ it('should accept large version numbers', () => {
+ expect(() => SemanticVersionSchema.parse('999.999.999')).not.toThrow();
+ });
+ });
+
+ describe('Invalid versions', () => {
+ it('should reject versions missing components', () => {
+ expect(() => SemanticVersionSchema.parse('1')).toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0')).toThrow();
+ });
+
+ it('should reject versions with v prefix', () => {
+ expect(() => SemanticVersionSchema.parse('v1.0.0')).toThrow();
+ expect(() => SemanticVersionSchema.parse('V1.0.0')).toThrow();
+ });
+
+ it('should reject versions with leading zeros', () => {
+ expect(() => SemanticVersionSchema.parse('01.0.0')).toThrow();
+ expect(() => SemanticVersionSchema.parse('1.01.0')).toThrow();
+ expect(() => SemanticVersionSchema.parse('1.0.01')).toThrow();
+ });
+
+ it('should reject versions with trailing dots', () => {
+ expect(() => SemanticVersionSchema.parse('1.0.0.')).toThrow();
+ });
+
+ it('should reject non-numeric versions', () => {
+ expect(() => SemanticVersionSchema.parse('a.b.c')).toThrow();
+ expect(() => SemanticVersionSchema.parse('1.a.0')).toThrow();
+ });
+
+ it('should reject empty or invalid strings', () => {
+ expect(() => SemanticVersionSchema.parse('')).toThrow();
+ expect(() => SemanticVersionSchema.parse('invalid')).toThrow();
+ });
+ });
+ });
+
+ describe('VersionRangeSchema', () => {
+ it('should accept exact versions', () => {
+ expect(() => VersionRangeSchema.parse('1.0.0')).not.toThrow();
+ });
+
+ it('should accept caret ranges', () => {
+ expect(() => VersionRangeSchema.parse('^1.0.0')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('^0.2.3')).not.toThrow();
+ });
+
+ it('should accept tilde ranges', () => {
+ expect(() => VersionRangeSchema.parse('~1.0.0')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('~1.2')).not.toThrow();
+ });
+
+ it('should accept comparison operators', () => {
+ expect(() => VersionRangeSchema.parse('>=1.0.0')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('>1.0.0')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('<=2.0.0')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('<2.0.0')).not.toThrow();
+ });
+
+ it('should accept wildcards', () => {
+ expect(() => VersionRangeSchema.parse('*')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('1.x')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('1.0.*')).not.toThrow();
+ });
+
+ it('should accept range expressions', () => {
+ expect(() => VersionRangeSchema.parse('>=1.0.0 <2.0.0')).not.toThrow();
+ expect(() => VersionRangeSchema.parse('1.0.0 - 2.0.0')).not.toThrow();
+ });
+
+ it('should reject empty strings', () => {
+ expect(() => VersionRangeSchema.parse('')).toThrow();
+ });
+ });
+
+ describe('VersionConstraintSchema', () => {
+ it('should accept valid constraints', () => {
+ const constraint1 = VersionConstraintSchema.parse({
+ operator: '>=',
+ version: '1.0.0',
+ });
+ expect(constraint1.operator).toBe('>=');
+ expect(constraint1.version).toBe('1.0.0');
+
+ const constraint2 = VersionConstraintSchema.parse({
+ operator: '^',
+ version: '2.1.0',
+ });
+ expect(constraint2.operator).toBe('^');
+ expect(constraint2.version).toBe('2.1.0');
+ });
+
+ it('should accept all valid operators', () => {
+ const operators = ['=', '>', '>=', '<', '<=', '^', '~'];
+ operators.forEach((op) => {
+ expect(() =>
+ VersionConstraintSchema.parse({
+ operator: op,
+ version: '1.0.0',
+ })
+ ).not.toThrow();
+ });
+ });
+
+ it('should reject invalid operators', () => {
+ expect(() =>
+ VersionConstraintSchema.parse({
+ operator: '!=',
+ version: '1.0.0',
+ })
+ ).toThrow();
+ });
+
+ it('should reject invalid versions', () => {
+ expect(() =>
+ VersionConstraintSchema.parse({
+ operator: '>=',
+ version: 'invalid',
+ })
+ ).toThrow();
+ });
+ });
+
+ describe('ReleaseChannelSchema', () => {
+ it('should accept all valid channels', () => {
+ const channels = ['stable', 'beta', 'alpha', 'nightly', 'canary'];
+ channels.forEach((channel) => {
+ expect(() => ReleaseChannelSchema.parse(channel)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid channels', () => {
+ expect(() => ReleaseChannelSchema.parse('production')).toThrow();
+ expect(() => ReleaseChannelSchema.parse('dev')).toThrow();
+ expect(() => ReleaseChannelSchema.parse('test')).toThrow();
+ });
+ });
+
+ describe('VersionMetadataSchema', () => {
+ it('should accept minimal version metadata', () => {
+ const metadata = VersionMetadataSchema.parse({
+ version: '1.0.0',
+ });
+ expect(metadata.version).toBe('1.0.0');
+ expect(metadata.channel).toBe('stable'); // default
+ });
+
+ it('should accept complete version metadata', () => {
+ const metadata = VersionMetadataSchema.parse({
+ version: '1.2.3',
+ channel: 'beta',
+ buildNumber: '12345',
+ gitCommit: 'a1b2c3d4e5f',
+ publishedAt: '2024-01-15T10:30:00Z',
+ });
+
+ expect(metadata.version).toBe('1.2.3');
+ expect(metadata.channel).toBe('beta');
+ expect(metadata.buildNumber).toBe('12345');
+ expect(metadata.gitCommit).toBe('a1b2c3d4e5f');
+ expect(metadata.publishedAt).toBe('2024-01-15T10:30:00Z');
+ });
+
+ it('should accept version metadata with custom metadata', () => {
+ const versionData = VersionMetadataSchema.parse({
+ version: '1.2.3',
+ metadata: {
+ platform: 'linux',
+ arch: 'x64',
+ },
+ });
+
+ expect(versionData.metadata).toEqual({
+ platform: 'linux',
+ arch: 'x64',
+ });
+ });
+
+ it('should use default channel when not specified', () => {
+ const metadata = VersionMetadataSchema.parse({
+ version: '1.0.0',
+ });
+ expect(metadata.channel).toBe('stable');
+ });
+
+ it('should accept valid datetime for publishedAt', () => {
+ expect(() =>
+ VersionMetadataSchema.parse({
+ version: '1.0.0',
+ publishedAt: '2024-01-15T10:30:00Z',
+ })
+ ).not.toThrow();
+
+ expect(() =>
+ VersionMetadataSchema.parse({
+ version: '1.0.0',
+ publishedAt: '2024-01-15T10:30:00.123Z',
+ })
+ ).not.toThrow();
+ });
+
+ it('should reject invalid datetime for publishedAt', () => {
+ expect(() =>
+ VersionMetadataSchema.parse({
+ version: '1.0.0',
+ publishedAt: 'invalid-date',
+ })
+ ).toThrow();
+
+ expect(() =>
+ VersionMetadataSchema.parse({
+ version: '1.0.0',
+ publishedAt: '2024-01-15',
+ })
+ ).toThrow();
+ });
+
+ it('should reject invalid version', () => {
+ expect(() =>
+ VersionMetadataSchema.parse({
+ version: 'invalid',
+ })
+ ).toThrow();
+ });
+
+ it('should reject invalid channel', () => {
+ expect(() =>
+ VersionMetadataSchema.parse({
+ version: '1.0.0',
+ channel: 'invalid',
+ })
+ ).toThrow();
+ });
+ });
+});
diff --git a/packages/spec/src/shared/version.zod.ts b/packages/spec/src/shared/version.zod.ts
new file mode 100644
index 000000000..fa9afd3b2
--- /dev/null
+++ b/packages/spec/src/shared/version.zod.ts
@@ -0,0 +1,199 @@
+import { z } from 'zod';
+
+/**
+ * Version Schemas
+ *
+ * Standardized version schemas for package versioning across ObjectStack.
+ * Supports semantic versioning (SemVer) and other common versioning patterns.
+ *
+ * Used by:
+ * - system/manifest.zod.ts (Package versions)
+ * - system/plugin.zod.ts (Plugin versions)
+ * - hub/marketplace.zod.ts (Marketplace versions)
+ *
+ * @see https://semver.org/ - Semantic Versioning 2.0.0
+ */
+
+// ==========================================
+// Semantic Version (SemVer)
+// ==========================================
+
+/**
+ * Semantic Version Schema (SemVer 2.0.0)
+ *
+ * Validates semantic version strings in the format: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
+ *
+ * **Format:** `X.Y.Z` where X, Y, and Z are non-negative integers
+ * - X = MAJOR version (incompatible API changes)
+ * - Y = MINOR version (backwards-compatible functionality)
+ * - Z = PATCH version (backwards-compatible bug fixes)
+ *
+ * Optional:
+ * - PRERELEASE: `-alpha.1`, `-beta.2`, `-rc.1`
+ * - BUILD: `+20130313144700`, `+exp.sha.5114f85`
+ *
+ * @example Valid versions
+ * - '1.0.0'
+ * - '0.1.0'
+ * - '2.1.3'
+ * - '1.0.0-alpha'
+ * - '1.0.0-alpha.1'
+ * - '1.0.0-0.3.7'
+ * - '1.0.0-x.7.z.92'
+ * - '1.0.0+20130313144700'
+ * - '1.0.0-beta+exp.sha.5114f85'
+ *
+ * @example Invalid versions
+ * - '1' (missing minor and patch)
+ * - '1.0' (missing patch)
+ * - 'v1.0.0' (no 'v' prefix allowed)
+ * - '1.0.0.' (trailing dot)
+ */
+export const SemanticVersionSchema = z
+ .string()
+ .regex(
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,
+ {
+ message: 'Version must follow semantic versioning format (e.g., "1.0.0", "1.0.0-alpha.1", "1.0.0+build.123")',
+ }
+ )
+ .describe('Semantic version (SemVer 2.0.0)');
+
+export type SemanticVersion = z.infer;
+
+// ==========================================
+// Version Range
+// ==========================================
+
+/**
+ * Version Range Schema
+ *
+ * Supports common version range patterns used in package dependencies.
+ *
+ * **Patterns:**
+ * - Exact: `1.0.0`
+ * - Caret: `^1.0.0` (compatible with version, allows changes that do not modify the left-most non-zero digit)
+ * - Tilde: `~1.0.0` (allows patch-level changes)
+ * - Wildcard: `1.0.*` or `1.*` or `*`
+ * - Comparison: `>=1.0.0`, `>1.0.0`, `<=2.0.0`, `<2.0.0`
+ * - Range: `>=1.0.0 <2.0.0` or `1.0.0 - 2.0.0`
+ *
+ * @example Valid ranges
+ * - '1.0.0' (exact)
+ * - '^1.0.0' (caret - allows 1.x.x)
+ * - '~1.0.0' (tilde - allows 1.0.x)
+ * - '>=1.0.0' (greater than or equal)
+ * - '*' (any version)
+ * - '1.x' (any 1.x.x version)
+ */
+export const VersionRangeSchema = z
+ .string()
+ .min(1, { message: 'Version range cannot be empty' })
+ .describe('Version range (e.g., "^1.0.0", ">=1.0.0 <2.0.0", "*")');
+
+export type VersionRange = z.infer;
+
+// ==========================================
+// Version Comparison
+// ==========================================
+
+/**
+ * Version Constraint Schema
+ *
+ * Represents a version constraint with an operator and version.
+ *
+ * @example
+ * ```typescript
+ * {
+ * operator: '>=',
+ * version: '1.0.0'
+ * }
+ * ```
+ */
+export const VersionConstraintSchema = z.object({
+ /**
+ * Comparison operator
+ */
+ operator: z.enum(['=', '>', '>=', '<', '<=', '^', '~']).describe('Comparison operator'),
+
+ /**
+ * Version to compare against
+ */
+ version: SemanticVersionSchema.describe('Version to compare against'),
+});
+
+export type VersionConstraint = z.infer;
+
+// ==========================================
+// Release Channel
+// ==========================================
+
+/**
+ * Release Channel Enum
+ *
+ * Defines the stability/maturity level of a release.
+ * Used for versioning and update strategies.
+ */
+export const ReleaseChannelSchema = z.enum([
+ 'stable', // Production-ready releases
+ 'beta', // Feature-complete but may have bugs
+ 'alpha', // Early preview, unstable
+ 'nightly', // Daily builds from main branch
+ 'canary', // Bleeding edge, may be broken
+]);
+
+export type ReleaseChannel = z.infer;
+
+// ==========================================
+// Version Metadata
+// ==========================================
+
+/**
+ * Version Metadata Schema
+ *
+ * Extended version information including channel, build info, and timestamps.
+ *
+ * @example
+ * ```typescript
+ * {
+ * version: '1.2.3',
+ * channel: 'stable',
+ * buildNumber: '12345',
+ * gitCommit: 'a1b2c3d',
+ * publishedAt: '2024-01-15T10:30:00Z'
+ * }
+ * ```
+ */
+export const VersionMetadataSchema = z.object({
+ /**
+ * Semantic version string
+ */
+ version: SemanticVersionSchema.describe('Semantic version'),
+
+ /**
+ * Release channel
+ */
+ channel: ReleaseChannelSchema.default('stable').describe('Release channel'),
+
+ /**
+ * Build number (optional)
+ */
+ buildNumber: z.string().optional().describe('Build number'),
+
+ /**
+ * Git commit SHA (optional)
+ */
+ gitCommit: z.string().optional().describe('Git commit SHA'),
+
+ /**
+ * Publication timestamp (ISO 8601)
+ */
+ publishedAt: z.string().datetime().optional().describe('Publication timestamp (ISO 8601)'),
+
+ /**
+ * Custom metadata
+ */
+ metadata: z.record(z.string(), z.any()).optional().describe('Custom metadata'),
+});
+
+export type VersionMetadata = z.infer;