@openship/protocol 0.0.2 → 0.0.3

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "openship": "1.0",
3
3
  "package": "@openship/protocol",
4
- "packageVersion": "0.0.2",
4
+ "packageVersion": "0.0.3",
5
5
  "source": "https://github.com/openshipdev/openship/tree/main/skills/openship",
6
- "sourceCommit": "9d6c35e78bae95a368220beea241a214aa0f24f3"
6
+ "sourceCommit": "dc28f02cd0608b2669c1f16135e4b04a59c96773"
7
7
  }
@@ -2,8 +2,14 @@
2
2
  "openship": "1.0",
3
3
  "capability": "discovery",
4
4
  "project": { "name": "Broken MCP", "description": "The MCP URL is not absolute HTTPS." },
5
+ "agent": {
6
+ "summary": "OpenShip exposes capabilities for a running project.",
7
+ "instructions": "Fetch and read agent.skill before using any capability.",
8
+ "skill": "https://example.com/openship/file/skills/openship/SKILL.md"
9
+ },
5
10
  "capabilities": {
6
11
  "sources": {
12
+ "description": "Retrieve and verify the exact source snapshot published by this deployment.",
7
13
  "manifest": "https://example.com/openship/manifest.json",
8
14
  "bundle": "https://example.com/openship/bundle.json",
9
15
  "mcp": "/mcp"
@@ -2,12 +2,17 @@
2
2
  "openship": "1.0",
3
3
  "capability": "discovery",
4
4
  "project": { "name": "Broken", "description": "Sources is missing." },
5
+ "agent": {
6
+ "summary": "OpenShip exposes capabilities for a running project.",
7
+ "instructions": "Fetch and read agent.skill before using any capability.",
8
+ "skill": "https://example.com/openship/file/skills/openship/SKILL.md"
9
+ },
5
10
  "capabilities": {
6
11
  "changes": {
12
+ "description": "Submit a patch against a published source digest.",
7
13
  "policy": "https://example.com/openship/policy.json",
8
14
  "submit": "https://example.com/openship/changes",
9
15
  "status": "https://example.com/openship/changes/{changeId}"
10
16
  }
11
17
  }
12
18
  }
13
-
@@ -2,9 +2,15 @@
2
2
  "openship": "1.0",
3
3
  "capability": "discovery",
4
4
  "project": { "name": "Example", "description": "An example OpenShip project." },
5
- "skill": "https://example.com/openship/file/skills/openship/SKILL.md",
5
+ "agent": {
6
+ "summary": "OpenShip lets this running project publish verifiable source code and optionally accept isolated changes or describe its complete system.",
7
+ "instructions": "Fetch and read agent.skill before interpreting or using any advertised capability. Resolve relative links in the skill against the skill URL.",
8
+ "skill": "https://example.com/openship/file/skills/openship/SKILL.md"
9
+ },
10
+ "page": "https://example.com/openship",
6
11
  "capabilities": {
7
12
  "sources": {
13
+ "description": "Retrieve and verify the exact source snapshot published by this deployment.",
8
14
  "manifest": "https://example.com/openship/manifest.json",
9
15
  "bundle": "https://example.com/openship/bundle.json",
10
16
  "mcp": "https://mcp.example.com/mcp",
@@ -12,6 +18,7 @@
12
18
  "archive": "https://example.com/openship/source.tar.gz"
13
19
  },
14
20
  "changes": {
21
+ "description": "Submit a patch against the published source digest and inspect an isolated candidate result.",
15
22
  "policy": "https://example.com/openship/policy.json",
16
23
  "submit": "https://example.com/openship/changes",
17
24
  "status": "https://example.com/openship/changes/{changeId}"
@@ -13,6 +13,7 @@ A producer MAY advertise an absolute HTTPS Streamable HTTP endpoint as `capabili
13
13
  {
14
14
  "capabilities": {
15
15
  "sources": {
16
+ "description": "Retrieve and verify the exact source snapshot published by this deployment.",
16
17
  "manifest": "https://example.com/openship/manifest.json",
17
18
  "bundle": "https://example.com/openship/bundle.json",
18
19
  "mcp": "https://mcp.example.com/mcp"
@@ -56,9 +56,15 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
56
56
  "name": "Example",
57
57
  "description": "A short description of the running project."
58
58
  },
59
- "skill": "https://example.com/openship/file/skills/openship/SKILL.md",
59
+ "agent": {
60
+ "summary": "OpenShip lets this running project publish verifiable source code and optionally accept isolated changes or describe its complete system.",
61
+ "instructions": "Fetch and read agent.skill before interpreting or using any advertised capability. Resolve relative links in the skill against the skill URL.",
62
+ "skill": "https://example.com/openship/file/skills/openship/SKILL.md"
63
+ },
64
+ "page": "https://example.com/openship",
60
65
  "capabilities": {
61
66
  "sources": {
67
+ "description": "Retrieve and verify the exact source snapshot published by this deployment.",
62
68
  "manifest": "https://example.com/openship/manifest.json",
63
69
  "bundle": "https://example.com/openship/bundle.json",
64
70
  "mcp": "https://mcp.example.com/mcp",
@@ -67,11 +73,13 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
67
73
  "instructions": "https://example.com/openship/agent.txt"
68
74
  },
69
75
  "changes": {
76
+ "description": "Submit a patch against the published source digest and inspect an isolated candidate result.",
70
77
  "policy": "https://example.com/openship/policy.json",
71
78
  "submit": "https://example.com/openship/changes",
72
79
  "status": "https://example.com/openship/changes/{changeId}"
73
80
  },
74
81
  "systems": {
82
+ "description": "Retrieve a self-contained description of this project’s source, architecture, infrastructure, and agent context.",
75
83
  "document": "https://example.com/openship/system.json"
76
84
  }
77
85
  }
@@ -80,20 +88,46 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
80
88
 
81
89
  Requirements:
82
90
 
83
- - `project`, `capabilities`, and `capabilities.sources` are REQUIRED.
91
+ - `project`, `agent`, `capabilities`, and `capabilities.sources` are REQUIRED.
84
92
  - `project.name` and `project.description` are REQUIRED.
93
+ - `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
+ - `agent.skill` MUST identify the OpenShip skill entry point. Relative links in that skill MUST be resolved against the skill URL.
95
+ - Every advertised capability MUST contain a standalone `description` explaining what the capability lets an agent do.
85
96
  - `sources.manifest` and `sources.bundle` are REQUIRED.
86
97
  - `sources.mcp` is OPTIONAL and, when present, advertises the OpenShip Sources MCP binding.
87
- - `changes`, `systems`, and `skill` are OPTIONAL and MUST be omitted when unavailable.
98
+ - `changes` and `systems` are OPTIONAL and MUST be omitted when unavailable.
99
+ - `page` is OPTIONAL and, when present, MUST be an absolute HTTPS URL for a human- and agent-readable presentation of this OpenShip origin.
88
100
  - Changes MUST NOT be advertised without Sources.
89
101
  - `file` and `status` are URI templates with exactly the named expansion.
90
102
  - A project implementing Sources and Changes but not Systems simply omits `capabilities.systems`.
91
103
 
92
104
  See [schemas/discovery.schema.json](schemas/discovery.schema.json).
93
105
 
106
+ ### Optional `/openship` presentation route
107
+
108
+ A website MAY serve `GET /openship` and advertise its absolute URL in discovery as `page`. This
109
+ route is a presentation aid for agents and humans. It does not replace
110
+ `/.well-known/openship.json`, and its absence does not affect conformance.
111
+
112
+ When implemented, the route MUST:
113
+
114
+ - identify itself as the project’s OpenShip page and explain OpenShip in plain language;
115
+ - show the project name and description and list every advertised capability with its meaning;
116
+ - tell an unfamiliar agent to start with discovery and read `agent.skill` before acting;
117
+ - link to discovery, the skill, and every advertised capability document or instruction URL;
118
+ - for Sources, show the current snapshot digest, file count, and byte count from the Manifest;
119
+ - for Changes, state whether submissions are currently accepted and make clear that a candidate result is not a production deployment; and
120
+ - distinguish explanatory page content from the authoritative JSON documents.
121
+
122
+ The route MAY use HTML or plain text. It MUST be publicly readable without authentication and
123
+ SHOULD keep its essential explanation and links available as semantic text rather than requiring
124
+ client-side interaction.
125
+
94
126
  ## Access and transport
95
127
 
96
- Discovery, Sources, and Systems reads MUST NOT require cookies, credentials, custom headers, or query parameters. Changes writes MAY require authorization or payment disclosed by the Changes policy.
128
+ Discovery, `agent.skill`, the skill’s referenced documents, Sources, and Systems reads MUST NOT
129
+ require cookies, credentials, custom headers, or query parameters and MUST allow cross-origin reads.
130
+ Changes writes MAY require authorization or payment disclosed by the Changes policy.
97
131
 
98
132
  JSON responses MUST use UTF-8. Public reads SHOULD support transport compression. A producer MAY offer additional representations, but the advertised JSON representation remains normative.
99
133
 
@@ -3,7 +3,7 @@
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", "capabilities"],
6
+ "required": ["openship", "capability", "project", "agent", "capabilities"],
7
7
  "properties": {
8
8
  "openship": { "const": "1.0" },
9
9
  "capability": { "const": "discovery" },
@@ -16,15 +16,26 @@
16
16
  },
17
17
  "additionalProperties": true
18
18
  },
19
- "skill": { "$ref": "#/$defs/httpsUrl" },
19
+ "agent": {
20
+ "type": "object",
21
+ "required": ["summary", "instructions", "skill"],
22
+ "properties": {
23
+ "summary": { "type": "string", "minLength": 1 },
24
+ "instructions": { "type": "string", "minLength": 1 },
25
+ "skill": { "$ref": "#/$defs/httpsUrl" }
26
+ },
27
+ "additionalProperties": true
28
+ },
29
+ "page": { "$ref": "#/$defs/httpsUrl" },
20
30
  "capabilities": {
21
31
  "type": "object",
22
32
  "required": ["sources"],
23
33
  "properties": {
24
34
  "sources": {
25
35
  "type": "object",
26
- "required": ["manifest", "bundle"],
36
+ "required": ["description", "manifest", "bundle"],
27
37
  "properties": {
38
+ "description": { "type": "string", "minLength": 1 },
28
39
  "manifest": { "$ref": "#/$defs/httpsUrl" },
29
40
  "bundle": { "$ref": "#/$defs/httpsUrl" },
30
41
  "mcp": { "$ref": "#/$defs/httpsUrl" },
@@ -36,8 +47,9 @@
36
47
  },
37
48
  "changes": {
38
49
  "type": "object",
39
- "required": ["policy", "submit", "status"],
50
+ "required": ["description", "policy", "submit", "status"],
40
51
  "properties": {
52
+ "description": { "type": "string", "minLength": 1 },
41
53
  "policy": { "$ref": "#/$defs/httpsUrl" },
42
54
  "submit": { "$ref": "#/$defs/httpsUrl" },
43
55
  "status": { "type": "string", "pattern": "^https://.+\\{changeId\\}.+$|^https://.+/\\{changeId\\}$" }
@@ -46,8 +58,11 @@
46
58
  },
47
59
  "systems": {
48
60
  "type": "object",
49
- "required": ["document"],
50
- "properties": { "document": { "$ref": "#/$defs/httpsUrl" } },
61
+ "required": ["description", "document"],
62
+ "properties": {
63
+ "description": { "type": "string", "minLength": 1 },
64
+ "document": { "$ref": "#/$defs/httpsUrl" }
65
+ },
51
66
  "additionalProperties": true
52
67
  }
53
68
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openship/protocol",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
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
@@ -8,7 +8,8 @@ export const OPENSHIP_MCP_FILE_RESOURCE_TEMPLATE: "openship://sources/file{?path
8
8
  export interface SourceFileMetadata { path: string; size: number; sha256: string; encoding: OpenShipEncoding; mediaType: string; type: "file" | "symlink"; target?: string; [key: string]: unknown }
9
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
10
  export interface SourcesBundle { openship: "1.0"; capability: "sources"; digest: string; files: Record<string, { encoding: OpenShipEncoding; content: string; [key: string]: unknown }>; [key: string]: unknown }
11
- export interface DiscoveryDocument { openship: "1.0"; capability: "discovery"; project: { name: string; description: string; [key: string]: unknown }; capabilities: { sources: { manifest: string; bundle: string; mcp?: string; [key: string]: unknown }; systems?: { document: string; [key: string]: unknown }; changes?: { policy: string; submit: string; status: string; [key: string]: unknown }; [key: string]: unknown }; [key: string]: unknown }
11
+ 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 }
12
13
  export type SystemsNodeKind = "Root" | "Host" | "Container" | "Process" | "Library";
13
14
  export type SystemsNodeOwnership = "first_party" | "third_party";
14
15
  export interface SystemsNodeMetadata { ownership: SystemsNodeOwnership; [key: string]: unknown }
package/src/index.js CHANGED
@@ -179,24 +179,43 @@ export function validateSources(manifestValue, bundleValue, options = {}) {
179
179
 
180
180
  export function validateDiscovery(value) {
181
181
  const discovery = envelope(value, "discovery");
182
+ const absoluteUrl = (value, path) => {
183
+ const raw = string(value, path);
184
+ let url;
185
+ try { url = new URL(raw); } catch { fail(path, "must be an absolute HTTPS URL"); }
186
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1";
187
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) fail(path, "must be an absolute HTTPS URL outside local development");
188
+ return raw;
189
+ };
182
190
  const project = object(discovery.project, "$.project");
183
191
  string(project.name, "$.project.name");
184
192
  string(project.description, "$.project.description");
193
+ const agent = object(discovery.agent, "$.agent");
194
+ string(agent.summary, "$.agent.summary");
195
+ string(agent.instructions, "$.agent.instructions");
196
+ absoluteUrl(agent.skill, "$.agent.skill");
197
+ if (discovery.page !== undefined) absoluteUrl(discovery.page, "$.page");
185
198
  const capabilities = object(discovery.capabilities, "$.capabilities");
186
199
  const sources = object(capabilities.sources, "$.capabilities.sources");
187
- const absoluteUrl = (value, path) => {
188
- const url = string(value, path);
189
- try { new URL(url); } catch { fail(path, "must be an absolute URL"); }
190
- return url;
191
- };
200
+ string(sources.description, "$.capabilities.sources.description");
192
201
  for (const key of ["manifest", "bundle"]) {
193
- const url = string(sources[key], `$.capabilities.sources.${key}`);
194
- try { new URL(url); } catch { fail(`$.capabilities.sources.${key}`, "must be an absolute URL"); }
202
+ absoluteUrl(sources[key], `$.capabilities.sources.${key}`);
203
+ }
204
+ for (const key of ["mcp", "archive", "instructions"]) {
205
+ if (sources[key] !== undefined) absoluteUrl(sources[key], `$.capabilities.sources.${key}`);
206
+ }
207
+ if (sources.file !== undefined) {
208
+ absoluteUrl(sources.file, "$.capabilities.sources.file");
209
+ if (!String(sources.file).includes("{path}")) fail("$.capabilities.sources.file", "must contain {path}");
210
+ }
211
+ if (capabilities.systems) {
212
+ const systems = object(capabilities.systems, "$.capabilities.systems");
213
+ string(systems.description, "$.capabilities.systems.description");
214
+ absoluteUrl(systems.document, "$.capabilities.systems.document");
195
215
  }
196
- if (sources.mcp !== undefined) absoluteUrl(sources.mcp, "$.capabilities.sources.mcp");
197
- if (capabilities.systems) absoluteUrl(object(capabilities.systems, "$.capabilities.systems").document, "$.capabilities.systems.document");
198
216
  if (capabilities.changes) {
199
217
  const changes = object(capabilities.changes, "$.capabilities.changes");
218
+ string(changes.description, "$.capabilities.changes.description");
200
219
  for (const key of ["policy", "submit", "status"]) absoluteUrl(changes[key], `$.capabilities.changes.${key}`);
201
220
  if (!String(changes.status).includes("{changeId}")) fail("$.capabilities.changes.status", "must contain {changeId}");
202
221
  }