@openship/protocol 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,3 +17,5 @@ The package contains the exact canonical `skills/openship` schemas, examples, an
17
17
  Version 0.1.0 replaces legacy Systems with `systemsVersion: "2.0"`: `system.layers`, `system.refinements`, and optional `system.instances`. Sources and Changes retain their 1.0 formats. Consumers must migrate; `validateSystems` explicitly rejects the legacy graph. Each layer is independently renderable, while node IDs and shared context span the entire system.
18
18
 
19
19
  Version 0.1.1 adds optional `system.domains` entries (`id`, `name`, `nodeIds`, optional `description`). Membership may overlap and span layers. Documents without domains remain valid; `openship: "1.0"` and `systemsVersion: "2.0"` do not change.
20
+
21
+ Version 0.2.0 replaces `project.description` with four required non-empty strings in discovery and Sources manifests: `productDescription`, `productSummary`, `technicalDescription`, and `technicalSummary`. Descriptions are plain text with a maximum of 120 Unicode code points each. Summaries contain Markdown; the technical summary includes implementation, technology, and deployment instructions. Old description-only documents are rejected. The envelope remains `openship: "1.0"` and Systems remains `systemsVersion: "2.0"`; all providers and consumers must upgrade together.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "openship": "1.0",
3
3
  "package": "@openship/protocol",
4
- "packageVersion": "0.1.1",
4
+ "packageVersion": "0.2.0",
5
5
  "source": "https://github.com/openshipdev/openship/tree/main/skills/openship",
6
- "sourceCommit": "fad5993fea4e1a21d95ec76d5ec89621364822b8"
6
+ "sourceCommit": "4915be31602a94653da5e56c22f0609736668ea0"
7
7
  }
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "openship": "1.0",
3
3
  "capability": "discovery",
4
- "project": { "name": "Broken MCP", "description": "The MCP URL is not absolute HTTPS." },
4
+ "project": {
5
+ "name": "Broken MCP",
6
+ "productDescription": "The MCP URL is not absolute HTTPS.",
7
+ "productSummary": "# Product\n\nThe MCP URL is not absolute HTTPS.",
8
+ "technicalDescription": "A source snapshot published through OpenShip.",
9
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
10
+ },
5
11
  "agent": {
6
12
  "summary": "OpenShip exposes capabilities for a running project.",
7
13
  "instructions": "Fetch and read agent.skill before using any capability.",
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "openship": "1.0",
3
3
  "capability": "discovery",
4
- "project": { "name": "Broken", "description": "Sources is missing." },
4
+ "project": {
5
+ "name": "Broken",
6
+ "productDescription": "Sources is missing.",
7
+ "productSummary": "# Product\n\nSources is missing.",
8
+ "technicalDescription": "A source snapshot published through OpenShip.",
9
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
10
+ },
5
11
  "agent": {
6
12
  "summary": "OpenShip exposes capabilities for a running project.",
7
13
  "instructions": "Fetch and read agent.skill before using any capability.",
@@ -2,10 +2,25 @@
2
2
  "openship": "1.0",
3
3
  "capability": "sources",
4
4
  "digest": "sha256:1d322539e651a3c2d7c51eb8b33160627395b9601bff909e599771b806d9e565",
5
- "project": { "name": "Broken", "description": "Contains an unsafe path." },
6
- "totals": { "files": 1, "bytes": 1 },
5
+ "project": {
6
+ "name": "Broken",
7
+ "productDescription": "Contains an unsafe path.",
8
+ "productSummary": "# Product\n\nContains an unsafe path.",
9
+ "technicalDescription": "A source snapshot published through OpenShip.",
10
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
11
+ },
12
+ "totals": {
13
+ "files": 1,
14
+ "bytes": 1
15
+ },
7
16
  "files": [
8
- { "path": "../secret", "size": 1, "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "encoding": "utf-8", "mediaType": "text/plain", "type": "file" }
17
+ {
18
+ "path": "../secret",
19
+ "size": 1,
20
+ "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
21
+ "encoding": "utf-8",
22
+ "mediaType": "text/plain",
23
+ "type": "file"
24
+ }
9
25
  ]
10
26
  }
11
-
@@ -8,7 +8,10 @@
8
8
  "digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
9
9
  "project": {
10
10
  "name": "Broken ownership",
11
- "description": "A Systems node with non-conformant ownership."
11
+ "productDescription": "A Systems node with non-conformant ownership.",
12
+ "productSummary": "# Product\n\nA Systems node with non-conformant ownership.",
13
+ "technicalDescription": "A source snapshot published through OpenShip.",
14
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
12
15
  },
13
16
  "totals": {
14
17
  "files": 0,
@@ -8,7 +8,10 @@
8
8
  "digest": "sha256:1d322539e651a3c2d7c51eb8b33160627395b9601bff909e599771b806d9e565",
9
9
  "project": {
10
10
  "name": "Broken",
11
- "description": "The runtime edge targets a Library."
11
+ "productDescription": "The runtime edge targets a Library.",
12
+ "productSummary": "# Product\n\nThe runtime edge targets a Library.",
13
+ "technicalDescription": "A source snapshot published through OpenShip.",
14
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
12
15
  },
13
16
  "totals": {
14
17
  "files": 0,
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "openship": "1.0",
3
3
  "capability": "discovery",
4
- "project": { "name": "Example", "description": "An example OpenShip project." },
4
+ "project": {
5
+ "name": "Example",
6
+ "productDescription": "An example OpenShip project.",
7
+ "productSummary": "# Product\n\nAn example OpenShip project.",
8
+ "technicalDescription": "A source snapshot published through OpenShip.",
9
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
10
+ },
5
11
  "agent": {
6
12
  "summary": "OpenShip lets this running project publish verifiable source code and optionally accept isolated changes or describe its complete system.",
7
13
  "instructions": "Fetch and read agent.skill before interpreting or using any advertised capability. Resolve relative links in the skill against the skill URL.",
@@ -3,8 +3,17 @@
3
3
  "capability": "sources",
4
4
  "generatedAt": "2026-08-21T12:00:00.000Z",
5
5
  "digest": "sha256:1d322539e651a3c2d7c51eb8b33160627395b9601bff909e599771b806d9e565",
6
- "project": { "name": "Example", "description": "An example OpenShip project." },
7
- "totals": { "files": 2, "bytes": 53 },
6
+ "project": {
7
+ "name": "Example",
8
+ "productDescription": "An example OpenShip project.",
9
+ "productSummary": "# Product\n\nAn example OpenShip project.",
10
+ "technicalDescription": "A source snapshot published through OpenShip.",
11
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
12
+ },
13
+ "totals": {
14
+ "files": 2,
15
+ "bytes": 53
16
+ },
8
17
  "files": [
9
18
  {
10
19
  "path": "app/page.js",
@@ -24,4 +33,3 @@
24
33
  }
25
34
  ]
26
35
  }
27
-
@@ -8,7 +8,10 @@
8
8
  "digest": "sha256:1d322539e651a3c2d7c51eb8b33160627395b9601bff909e599771b806d9e565",
9
9
  "project": {
10
10
  "name": "Example",
11
- "description": "An example OpenShip project."
11
+ "productDescription": "An example OpenShip project.",
12
+ "productSummary": "# Product\n\nAn example OpenShip project.",
13
+ "technicalDescription": "A source snapshot published through OpenShip.",
14
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
12
15
  },
13
16
  "totals": {
14
17
  "files": 2,
@@ -8,7 +8,10 @@
8
8
  "digest": "sha256:1d322539e651a3c2d7c51eb8b33160627395b9601bff909e599771b806d9e565",
9
9
  "project": {
10
10
  "name": "Example",
11
- "description": "An example OpenShip project."
11
+ "productDescription": "An example OpenShip project.",
12
+ "productSummary": "# Product\n\nAn example OpenShip project.",
13
+ "technicalDescription": "A source snapshot published through OpenShip.",
14
+ "technicalSummary": "# Implementation\n\nRetrieve the source bundle, install its dependencies, and follow its deployment documentation."
12
15
  },
13
16
  "totals": {
14
17
  "files": 2,
@@ -32,7 +32,10 @@ remain normative and required.
32
32
  "digest": "sha256:1c413f...",
33
33
  "project": {
34
34
  "name": "Example",
35
- "description": "An example project.",
35
+ "productDescription": "An example product.",
36
+ "productSummary": "## Product\n\nWhat this product does and the problem it solves.",
37
+ "technicalDescription": "A web application.",
38
+ "technicalSummary": "## Implementation\n\nTechnology and deployment instructions.",
36
39
  "homepage": "https://example.com",
37
40
  "repository": "https://github.com/example/example",
38
41
  "license": "MIT"
@@ -51,7 +54,7 @@ remain normative and required.
51
54
  }
52
55
  ```
53
56
 
54
- Required members are `openship`, `capability`, `digest`, `project`, `totals`, and `files`. Project name and description are required. Repository, commit, stack, structure, setup, runtime, environment-variable names, ignore rules, generation time, lineage, and other metadata are optional.
57
+ Required members are `openship`, `capability`, `digest`, `project`, `totals`, and `files`. Project name and all four descriptive fields (`productDescription`, `productSummary`, `technicalDescription`, `technicalSummary`) are required non-empty strings. Descriptions are plain text of at most 120 Unicode code points each; summaries are Markdown. Product fields explain what the product does and the problem it solves. Technical fields explain implementation, technology, and deployment. The legacy `project.description` field is replaced without a fallback. Repository, commit, stack, structure, setup, runtime, environment-variable names, ignore rules, generation time, lineage, and other metadata are optional.
55
58
 
56
59
  See [schemas/sources-manifest.schema.json](schemas/sources-manifest.schema.json).
57
60
 
@@ -38,7 +38,11 @@ Any positive number of layers is supported. Each is an explicit graph, not a gen
38
38
 
39
39
  ## Nodes, containment and sources
40
40
 
41
- Node kinds are `Root`, `Block`, `Store`, `Host`, `Container`, `Process`, and `Library`. Block describes a capability; Store describes persistent data. The other kinds describe a system boundary, execution environment, grouped runtime, executable component, and reusable dependency respectively.
41
+ Node kinds are `Root`, `Block`, `Store`, `Host`, `Container`, `Process`, `Contract`, and `Library`. Block describes a capability; Store describes persistent data. Root, Host, Container, Process, and Library describe a system boundary, execution environment, grouped runtime, executable component, and reusable dependency respectively.
42
+
43
+ A **Contract** is a Process running on consensus: its execution and state transitions are governed by the consensus rules of its network. “Smart” is implied. Use `Contract` for consensus-executed components and `Process` for ordinary runtimes, including RPC servers and transaction relayers. Contract follows the same containment, connection, refinement, configuration, and instance-binding rules as Process; database `state` remains exclusive to Store bindings. The network can be described by a parent Host and configuration or metadata.
44
+
45
+ `Contract` extends the node-kind vocabulary without changing the `openship: "1.0"` or `systemsVersion: "2.0"` envelope. Consumers with the previous closed vocabulary must update before accepting documents containing Contract.
42
46
 
43
47
  ```json
44
48
  {
@@ -54,7 +54,10 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
54
54
  "capability": "discovery",
55
55
  "project": {
56
56
  "name": "Example",
57
- "description": "A short description of the running project."
57
+ "productDescription": "A short description of what the product does.",
58
+ "productSummary": "## Product\n\nWhat the product does and the problem it solves.",
59
+ "technicalDescription": "A short description of the implementation.",
60
+ "technicalSummary": "## Implementation\n\nTechnologies, architecture, and deployment instructions."
58
61
  },
59
62
  "agent": {
60
63
  "summary": "OpenShip lets this running project publish verifiable source code and optionally accept isolated changes or describe its complete system.",
@@ -89,7 +92,7 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
89
92
  Requirements:
90
93
 
91
94
  - `project`, `agent`, `capabilities`, and `capabilities.sources` are REQUIRED.
92
- - `project.name` and `project.description` are REQUIRED.
95
+ - `project.name`, `project.productDescription`, `project.productSummary`, `project.technicalDescription`, and `project.technicalSummary` are REQUIRED non-empty strings. The descriptions are plain text, each at most 120 Unicode code points. The summaries are Markdown: productSummary explains the product and problem it solves; technicalSummary explains implementation, underlying technology, and deployment. These fields replace project.description; there is no legacy fallback.
93
96
  - `agent.summary`, `agent.instructions`, and `agent.skill` are REQUIRED. The summary MUST explain OpenShip in standalone plain language. The instructions MUST explicitly direct an unfamiliar agent to fetch and read `agent.skill` before interpreting or using the capability links.
94
97
  - `agent.skill` MUST identify the OpenShip skill entry point. Relative links in that skill MUST be resolved against the skill URL.
95
98
  - Every advertised capability MUST contain a standalone `description` explaining what the capability lets an agent do.
@@ -3,65 +3,162 @@
3
3
  "$id": "https://openship.dev/schemas/discovery.schema.json",
4
4
  "title": "OpenShip v1 discovery",
5
5
  "type": "object",
6
- "required": ["openship", "capability", "project", "agent", "capabilities"],
6
+ "required": [
7
+ "openship",
8
+ "capability",
9
+ "project",
10
+ "agent",
11
+ "capabilities"
12
+ ],
7
13
  "properties": {
8
- "openship": { "const": "1.0" },
9
- "capability": { "const": "discovery" },
14
+ "openship": {
15
+ "const": "1.0"
16
+ },
17
+ "capability": {
18
+ "const": "discovery"
19
+ },
10
20
  "project": {
11
21
  "type": "object",
12
- "required": ["name", "description"],
22
+ "required": [
23
+ "name",
24
+ "productDescription",
25
+ "productSummary",
26
+ "technicalDescription",
27
+ "technicalSummary"
28
+ ],
13
29
  "properties": {
14
- "name": { "type": "string", "minLength": 1 },
15
- "description": { "type": "string", "minLength": 1 }
30
+ "name": {
31
+ "type": "string",
32
+ "minLength": 1
33
+ },
34
+ "productDescription": {
35
+ "type": "string",
36
+ "minLength": 1,
37
+ "maxLength": 120
38
+ },
39
+ "productSummary": {
40
+ "type": "string",
41
+ "minLength": 1,
42
+ "description": "Markdown text."
43
+ },
44
+ "technicalDescription": {
45
+ "type": "string",
46
+ "minLength": 1,
47
+ "maxLength": 120
48
+ },
49
+ "technicalSummary": {
50
+ "type": "string",
51
+ "minLength": 1,
52
+ "description": "Markdown text."
53
+ }
16
54
  },
17
55
  "additionalProperties": true
18
56
  },
19
57
  "agent": {
20
58
  "type": "object",
21
- "required": ["summary", "instructions", "skill"],
59
+ "required": [
60
+ "summary",
61
+ "instructions",
62
+ "skill"
63
+ ],
22
64
  "properties": {
23
- "summary": { "type": "string", "minLength": 1 },
24
- "instructions": { "type": "string", "minLength": 1 },
25
- "skill": { "$ref": "#/$defs/httpsUrl" }
65
+ "summary": {
66
+ "type": "string",
67
+ "minLength": 1
68
+ },
69
+ "instructions": {
70
+ "type": "string",
71
+ "minLength": 1
72
+ },
73
+ "skill": {
74
+ "$ref": "#/$defs/httpsUrl"
75
+ }
26
76
  },
27
77
  "additionalProperties": true
28
78
  },
29
- "page": { "$ref": "#/$defs/httpsUrl" },
79
+ "page": {
80
+ "$ref": "#/$defs/httpsUrl"
81
+ },
30
82
  "capabilities": {
31
83
  "type": "object",
32
- "required": ["sources"],
84
+ "required": [
85
+ "sources"
86
+ ],
33
87
  "properties": {
34
88
  "sources": {
35
89
  "type": "object",
36
- "required": ["description", "manifest", "bundle"],
90
+ "required": [
91
+ "description",
92
+ "manifest",
93
+ "bundle"
94
+ ],
37
95
  "properties": {
38
- "description": { "type": "string", "minLength": 1 },
39
- "manifest": { "$ref": "#/$defs/httpsUrl" },
40
- "bundle": { "$ref": "#/$defs/httpsUrl" },
41
- "mcp": { "$ref": "#/$defs/httpsUrl" },
42
- "file": { "type": "string", "pattern": "^https://.+\\{path\\}.+$|^https://.+/\\{path\\}$" },
43
- "archive": { "$ref": "#/$defs/httpsUrl" },
44
- "instructions": { "$ref": "#/$defs/httpsUrl" }
96
+ "description": {
97
+ "type": "string",
98
+ "minLength": 1
99
+ },
100
+ "manifest": {
101
+ "$ref": "#/$defs/httpsUrl"
102
+ },
103
+ "bundle": {
104
+ "$ref": "#/$defs/httpsUrl"
105
+ },
106
+ "mcp": {
107
+ "$ref": "#/$defs/httpsUrl"
108
+ },
109
+ "file": {
110
+ "type": "string",
111
+ "pattern": "^https://.+\\{path\\}.+$|^https://.+/\\{path\\}$"
112
+ },
113
+ "archive": {
114
+ "$ref": "#/$defs/httpsUrl"
115
+ },
116
+ "instructions": {
117
+ "$ref": "#/$defs/httpsUrl"
118
+ }
45
119
  },
46
120
  "additionalProperties": true
47
121
  },
48
122
  "changes": {
49
123
  "type": "object",
50
- "required": ["description", "policy", "submit", "status"],
124
+ "required": [
125
+ "description",
126
+ "policy",
127
+ "submit",
128
+ "status"
129
+ ],
51
130
  "properties": {
52
- "description": { "type": "string", "minLength": 1 },
53
- "policy": { "$ref": "#/$defs/httpsUrl" },
54
- "submit": { "$ref": "#/$defs/httpsUrl" },
55
- "status": { "type": "string", "pattern": "^https://.+\\{changeId\\}.+$|^https://.+/\\{changeId\\}$" }
131
+ "description": {
132
+ "type": "string",
133
+ "minLength": 1
134
+ },
135
+ "policy": {
136
+ "$ref": "#/$defs/httpsUrl"
137
+ },
138
+ "submit": {
139
+ "$ref": "#/$defs/httpsUrl"
140
+ },
141
+ "status": {
142
+ "type": "string",
143
+ "pattern": "^https://.+\\{changeId\\}.+$|^https://.+/\\{changeId\\}$"
144
+ }
56
145
  },
57
146
  "additionalProperties": true
58
147
  },
59
148
  "systems": {
60
149
  "type": "object",
61
- "required": ["description", "document"],
150
+ "required": [
151
+ "description",
152
+ "document"
153
+ ],
62
154
  "properties": {
63
- "description": { "type": "string", "minLength": 1 },
64
- "document": { "$ref": "#/$defs/httpsUrl" }
155
+ "description": {
156
+ "type": "string",
157
+ "minLength": 1
158
+ },
159
+ "document": {
160
+ "$ref": "#/$defs/httpsUrl"
161
+ }
65
162
  },
66
163
  "additionalProperties": true
67
164
  }
@@ -70,7 +167,11 @@
70
167
  }
71
168
  },
72
169
  "$defs": {
73
- "httpsUrl": { "type": "string", "format": "uri", "pattern": "^https://" }
170
+ "httpsUrl": {
171
+ "type": "string",
172
+ "format": "uri",
173
+ "pattern": "^https://"
174
+ }
74
175
  },
75
176
  "additionalProperties": true
76
177
  }
@@ -3,74 +3,261 @@
3
3
  "$id": "https://openship.dev/schemas/sources-manifest.schema.json",
4
4
  "title": "OpenShip Sources v1 manifest",
5
5
  "type": "object",
6
- "required": ["openship", "capability", "digest", "project", "totals", "files"],
6
+ "required": [
7
+ "openship",
8
+ "capability",
9
+ "digest",
10
+ "project",
11
+ "totals",
12
+ "files"
13
+ ],
7
14
  "properties": {
8
- "openship": { "const": "1.0" },
9
- "capability": { "const": "sources" },
10
- "generatedAt": { "type": "string", "format": "date-time" },
11
- "digest": { "$ref": "#/$defs/digest" },
12
- "parent": { "$ref": "#/$defs/digest" },
15
+ "openship": {
16
+ "const": "1.0"
17
+ },
18
+ "capability": {
19
+ "const": "sources"
20
+ },
21
+ "generatedAt": {
22
+ "type": "string",
23
+ "format": "date-time"
24
+ },
25
+ "digest": {
26
+ "$ref": "#/$defs/digest"
27
+ },
28
+ "parent": {
29
+ "$ref": "#/$defs/digest"
30
+ },
13
31
  "project": {
14
32
  "type": "object",
15
- "required": ["name", "description"],
33
+ "required": [
34
+ "name",
35
+ "productDescription",
36
+ "productSummary",
37
+ "technicalDescription",
38
+ "technicalSummary"
39
+ ],
16
40
  "properties": {
17
- "name": { "type": "string", "minLength": 1 },
18
- "description": { "type": "string", "minLength": 1 },
19
- "homepage": { "type": "string", "format": "uri" },
20
- "repository": { "type": "string", "format": "uri" },
21
- "license": { "type": "string", "minLength": 1 }
41
+ "name": {
42
+ "type": "string",
43
+ "minLength": 1
44
+ },
45
+ "homepage": {
46
+ "type": "string",
47
+ "format": "uri"
48
+ },
49
+ "repository": {
50
+ "type": "string",
51
+ "format": "uri"
52
+ },
53
+ "license": {
54
+ "type": "string",
55
+ "minLength": 1
56
+ },
57
+ "productDescription": {
58
+ "type": "string",
59
+ "minLength": 1,
60
+ "maxLength": 120
61
+ },
62
+ "productSummary": {
63
+ "type": "string",
64
+ "minLength": 1,
65
+ "description": "Markdown text."
66
+ },
67
+ "technicalDescription": {
68
+ "type": "string",
69
+ "minLength": 1,
70
+ "maxLength": 120
71
+ },
72
+ "technicalSummary": {
73
+ "type": "string",
74
+ "minLength": 1,
75
+ "description": "Markdown text."
76
+ }
22
77
  },
23
78
  "additionalProperties": true
24
79
  },
25
80
  "commit": {
26
81
  "type": "object",
27
- "required": ["sha", "dirty"],
82
+ "required": [
83
+ "sha",
84
+ "dirty"
85
+ ],
28
86
  "properties": {
29
- "sha": { "type": "string", "minLength": 1 },
30
- "ref": { "type": "string" },
31
- "committedAt": { "type": "string", "format": "date-time" },
32
- "dirty": { "type": "boolean" }
87
+ "sha": {
88
+ "type": "string",
89
+ "minLength": 1
90
+ },
91
+ "ref": {
92
+ "type": "string"
93
+ },
94
+ "committedAt": {
95
+ "type": "string",
96
+ "format": "date-time"
97
+ },
98
+ "dirty": {
99
+ "type": "boolean"
100
+ }
33
101
  },
34
102
  "additionalProperties": true
35
103
  },
36
- "stack": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
37
- "structure": { "type": "array", "items": { "type": "object", "required": ["path", "purpose"], "properties": { "path": { "type": "string" }, "purpose": { "type": "string" } }, "additionalProperties": true } },
38
- "setup": { "type": "object", "additionalProperties": true },
39
- "env": { "type": "array", "items": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" }, "uniqueItems": true },
40
- "ignore": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
41
- "ignoreNames": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
42
- "fileSet": { "type": "string", "minLength": 1 },
104
+ "stack": {
105
+ "type": "array",
106
+ "items": {
107
+ "type": "string"
108
+ },
109
+ "uniqueItems": true
110
+ },
111
+ "structure": {
112
+ "type": "array",
113
+ "items": {
114
+ "type": "object",
115
+ "required": [
116
+ "path",
117
+ "purpose"
118
+ ],
119
+ "properties": {
120
+ "path": {
121
+ "type": "string"
122
+ },
123
+ "purpose": {
124
+ "type": "string"
125
+ }
126
+ },
127
+ "additionalProperties": true
128
+ }
129
+ },
130
+ "setup": {
131
+ "type": "object",
132
+ "additionalProperties": true
133
+ },
134
+ "env": {
135
+ "type": "array",
136
+ "items": {
137
+ "type": "string",
138
+ "pattern": "^[A-Z][A-Z0-9_]*$"
139
+ },
140
+ "uniqueItems": true
141
+ },
142
+ "ignore": {
143
+ "type": "array",
144
+ "items": {
145
+ "type": "string"
146
+ },
147
+ "uniqueItems": true
148
+ },
149
+ "ignoreNames": {
150
+ "type": "array",
151
+ "items": {
152
+ "type": "string"
153
+ },
154
+ "uniqueItems": true
155
+ },
156
+ "fileSet": {
157
+ "type": "string",
158
+ "minLength": 1
159
+ },
43
160
  "totals": {
44
161
  "type": "object",
45
- "required": ["files", "bytes"],
162
+ "required": [
163
+ "files",
164
+ "bytes"
165
+ ],
46
166
  "properties": {
47
- "files": { "type": "integer", "minimum": 0 },
48
- "bytes": { "type": "integer", "minimum": 0 }
167
+ "files": {
168
+ "type": "integer",
169
+ "minimum": 0
170
+ },
171
+ "bytes": {
172
+ "type": "integer",
173
+ "minimum": 0
174
+ }
49
175
  },
50
176
  "additionalProperties": true
51
177
  },
52
178
  "files": {
53
179
  "type": "array",
54
- "items": { "$ref": "#/$defs/file" }
180
+ "items": {
181
+ "$ref": "#/$defs/file"
182
+ }
55
183
  }
56
184
  },
57
185
  "$defs": {
58
- "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
59
- "path": { "type": "string", "minLength": 1, "maxLength": 512, "pattern": "^(?!/)(?!.*\\\\)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*//)[^\\u0000]+$" },
186
+ "digest": {
187
+ "type": "string",
188
+ "pattern": "^sha256:[0-9a-f]{64}$"
189
+ },
190
+ "path": {
191
+ "type": "string",
192
+ "minLength": 1,
193
+ "maxLength": 512,
194
+ "pattern": "^(?!/)(?!.*\\\\)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*//)[^\\u0000]+$"
195
+ },
60
196
  "file": {
61
197
  "type": "object",
62
- "required": ["path", "size", "sha256", "encoding", "mediaType", "type"],
198
+ "required": [
199
+ "path",
200
+ "size",
201
+ "sha256",
202
+ "encoding",
203
+ "mediaType",
204
+ "type"
205
+ ],
63
206
  "properties": {
64
- "path": { "$ref": "#/$defs/path" },
65
- "size": { "type": "integer", "minimum": 0 },
66
- "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
67
- "encoding": { "enum": ["utf-8", "base64"] },
68
- "mediaType": { "type": "string", "minLength": 1 },
69
- "type": { "enum": ["file", "symlink"] },
70
- "target": { "$ref": "#/$defs/path" }
207
+ "path": {
208
+ "$ref": "#/$defs/path"
209
+ },
210
+ "size": {
211
+ "type": "integer",
212
+ "minimum": 0
213
+ },
214
+ "sha256": {
215
+ "type": "string",
216
+ "pattern": "^[0-9a-f]{64}$"
217
+ },
218
+ "encoding": {
219
+ "enum": [
220
+ "utf-8",
221
+ "base64"
222
+ ]
223
+ },
224
+ "mediaType": {
225
+ "type": "string",
226
+ "minLength": 1
227
+ },
228
+ "type": {
229
+ "enum": [
230
+ "file",
231
+ "symlink"
232
+ ]
233
+ },
234
+ "target": {
235
+ "$ref": "#/$defs/path"
236
+ }
71
237
  },
72
238
  "allOf": [
73
- { "if": { "properties": { "type": { "const": "symlink" } }, "required": ["type"] }, "then": { "properties": { "target": { "$ref": "#/$defs/path" } }, "required": ["target"] } }
239
+ {
240
+ "if": {
241
+ "properties": {
242
+ "type": {
243
+ "const": "symlink"
244
+ }
245
+ },
246
+ "required": [
247
+ "type"
248
+ ]
249
+ },
250
+ "then": {
251
+ "properties": {
252
+ "target": {
253
+ "$ref": "#/$defs/path"
254
+ }
255
+ },
256
+ "required": [
257
+ "target"
258
+ ]
259
+ }
260
+ }
74
261
  ],
75
262
  "additionalProperties": true
76
263
  }
@@ -124,6 +124,7 @@
124
124
  "Host",
125
125
  "Container",
126
126
  "Process",
127
+ "Contract",
127
128
  "Library",
128
129
  "Block",
129
130
  "Store"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openship/protocol",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Canonical OpenShip 1.0 types, validators, consumer helpers, schemas, and skill assets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export interface Project { name: string; productDescription: string; productSummary: string; technicalDescription: string; technicalSummary: string; [key: string]: unknown }
1
2
  export type OpenShipEncoding = "utf-8" | "base64";
2
3
  export type OpenShipCapability = "discovery" | "sources" | "changes" | "systems";
3
4
  export type OpenShipMcpOperation = "manifest" | "read";
@@ -6,11 +7,11 @@ export const OPENSHIP_MCP_TOOL_NAME: "openship";
6
7
  export const OPENSHIP_MCP_MANIFEST_RESOURCE_URI: "openship://sources/manifest";
7
8
  export const OPENSHIP_MCP_FILE_RESOURCE_TEMPLATE: "openship://sources/file{?path}";
8
9
  export interface SourceFileMetadata { path: string; size: number; sha256: string; encoding: OpenShipEncoding; mediaType: string; type: "file" | "symlink"; target?: string; [key: string]: unknown }
9
- export interface SourcesManifest { openship: "1.0"; capability: "sources"; digest: string; project: { name: string; description: string; [key: string]: unknown }; totals: { files: number; bytes: number; [key: string]: unknown }; files: SourceFileMetadata[]; [key: string]: unknown }
10
+ export interface SourcesManifest { openship: "1.0"; capability: "sources"; digest: string; project: Project; totals: { files: number; bytes: number; [key: string]: unknown }; files: SourceFileMetadata[]; [key: string]: unknown }
10
11
  export interface SourcesBundle { openship: "1.0"; capability: "sources"; digest: string; files: Record<string, { encoding: OpenShipEncoding; content: string; [key: string]: unknown }>; [key: string]: unknown }
11
12
  export interface DiscoveryAgent { summary: string; instructions: string; skill: string; [key: string]: unknown }
12
- export interface DiscoveryDocument { openship: "1.0"; capability: "discovery"; project: { name: string; description: string; [key: string]: unknown }; agent: DiscoveryAgent; page?: string; capabilities: { sources: { description: string; manifest: string; bundle: string; mcp?: string; [key: string]: unknown }; systems?: { description: string; document: string; [key: string]: unknown }; changes?: { description: string; policy: string; submit: string; status: string; [key: string]: unknown }; [key: string]: unknown }; [key: string]: unknown }
13
- export type SystemsNodeKind = "Root" | "Block" | "Store" | "Host" | "Container" | "Process" | "Library";
13
+ export interface DiscoveryDocument { openship: "1.0"; capability: "discovery"; project: Project; agent: DiscoveryAgent; page?: string; capabilities: { sources: { description: string; manifest: string; bundle: string; mcp?: string; [key: string]: unknown }; systems?: { description: string; document: string; [key: string]: unknown }; changes?: { description: string; policy: string; submit: string; status: string; [key: string]: unknown }; [key: string]: unknown }; [key: string]: unknown }
14
+ export type SystemsNodeKind = "Root" | "Block" | "Store" | "Host" | "Container" | "Process" | "Contract" | "Library";
14
15
  export type SystemsNodeOwnership = "first_party" | "third_party";
15
16
  export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
16
17
  export interface SystemsConfiguration { name: string; description: string; required: boolean; sensitive?: boolean; value?: JsonValue; secretRef?: { nodeId: string; key: string }; }
package/src/index.js CHANGED
@@ -130,7 +130,10 @@ export function validateSourcesManifest(value) {
130
130
  if (!digestPattern.test(manifest.digest)) fail("$.digest", "must be a sha256 digest");
131
131
  const project = object(manifest.project, "$.project");
132
132
  string(project.name, "$.project.name");
133
- string(project.description, "$.project.description");
133
+ for (const key of ["productDescription", "productSummary", "technicalDescription", "technicalSummary"]) {
134
+ const value = string(project[key], `$.project.${key}`);
135
+ if (key.endsWith("Description") && [...value].length > 120) fail(`$.project.${key}`, "must be at most 120 Unicode characters");
136
+ }
134
137
  const totals = object(manifest.totals, "$.totals");
135
138
  if (!Number.isInteger(totals.files) || totals.files < 0) fail("$.totals.files", "must be a non-negative integer");
136
139
  if (!Number.isInteger(totals.bytes) || totals.bytes < 0) fail("$.totals.bytes", "must be a non-negative integer");
@@ -189,7 +192,10 @@ export function validateDiscovery(value) {
189
192
  };
190
193
  const project = object(discovery.project, "$.project");
191
194
  string(project.name, "$.project.name");
192
- string(project.description, "$.project.description");
195
+ for (const key of ["productDescription", "productSummary", "technicalDescription", "technicalSummary"]) {
196
+ const value = string(project[key], `$.project.${key}`);
197
+ if (key.endsWith("Description") && [...value].length > 120) fail(`$.project.${key}`, "must be at most 120 Unicode characters");
198
+ }
193
199
  const agent = object(discovery.agent, "$.agent");
194
200
  string(agent.summary, "$.agent.summary");
195
201
  string(agent.instructions, "$.agent.instructions");
@@ -299,7 +305,7 @@ export function validateSystems(value, options = {}) {
299
305
  const path = `${at}.nodes[${i}]`, node = object(rawNode, path);
300
306
  identifier(node.id, `${path}.id`);
301
307
  if (nodeById.has(node.id)) fail(`${path}.id`, "must be globally unique");
302
- if (!["Root", "Block", "Store", "Host", "Container", "Process", "Library"].includes(node.kind)) fail(`${path}.kind`, "invalid node kind");
308
+ if (!["Root", "Block", "Store", "Host", "Container", "Process", "Contract", "Library"].includes(node.kind)) fail(`${path}.kind`, "invalid node kind");
303
309
  string(node.name, `${path}.name`);
304
310
  const metadata = object(node.metadata, `${path}.metadata`);
305
311
  if (!["first_party", "third_party"].includes(metadata.ownership)) fail(`${path}.metadata.ownership`, "must be first_party or third_party");