Skip to content

Commit d2b140a

Browse files
authored
feat(components): define manifest v1 (#367)
* feat(components): define manifest v1 * fix(ci): support Python 3.10 in version sync
1 parent 32ee55d commit d2b140a

12 files changed

Lines changed: 2431 additions & 3 deletions

.github/workflows/ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ jobs:
5252
if: matrix.python == '3.12'
5353
run: python -m pytest -q --cov=brigade --cov-report=term --cov-fail-under=78
5454

55+
component-manifest-provenance:
56+
runs-on: ubuntu-latest
57+
permissions:
58+
contents: read
59+
steps:
60+
- uses: actions/checkout@v5
61+
- uses: actions/setup-python@v6
62+
with:
63+
python-version: "3.12"
64+
- name: Verify component manifest provenance
65+
run: python scripts/verify_component_manifest_provenance.py
66+
env:
67+
GITHUB_TOKEN: ${{ github.token }}
68+
5569
content-guard:
5670
runs-on: ubuntu-latest
5771
steps:

docs/component-manifest-policy.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Component manifest v1 policy
2+
3+
Brigade ships a standalone component manifest contract for Phase 1 native tools. It is separate
4+
from `brigade.station.v1` and `station_manifest.load`.
5+
6+
## Phase 1 components
7+
8+
| Component id | Executable | Native release status |
9+
| --- | --- | --- |
10+
| `graphtrail` | `graphtrail` | pinned to GraphTrail commit `64fcd2f9`, assets unpublished |
11+
| `graphtrail-mcp` | `graphtrail-mcp` | pinned to GraphTrail commit `64fcd2f9`, assets unpublished |
12+
| `miseledger` | `miseledger` | pinned to MiseLedger v0.6.0 |
13+
| `sessionfind` | `sessionfind` | pinned to MiseLedger v0.6.0 |
14+
15+
GraphTrail components record an immutable 40-character lowercase git SHA in `component_revision`.
16+
Published components also record the GitHub release tag in `source.release_tag`. The engine revision
17+
and release tag are independent: GraphTrail may pin a commit SHA while release assets use a semantic
18+
tag such as `v0.1.0`. Issue #354 will update the GraphTrail commit SHA and add its release tag and
19+
assets.
20+
21+
MiseLedger components record the release tag in both `component_revision` and `source.release_tag`
22+
(`v0.6.0` today).
23+
24+
## Platform matrix
25+
26+
All Phase 1 components share one fixed support matrix:
27+
28+
- `linux-amd64`
29+
- `linux-arm64`
30+
- `darwin-amd64`
31+
- `darwin-arm64`
32+
- `windows-amd64`
33+
34+
Unsupported host platforms fail with the resolved key and the supported keys. Requesting an
35+
unpublished component/platform pair (for example GraphTrail on any platform today) raises an
36+
`unsupported-component-platform` diagnostic. Brigade never invents a filename and never falls
37+
back to Cargo builds.
38+
39+
## Asset filenames
40+
41+
Go-style platform keys drive asset names:
42+
43+
- Linux and macOS: `<executable>-<platform-key>` with no suffix (`miseledger-linux-amd64`)
44+
- Windows: same pattern with a `.exe` suffix (`miseledger-windows-amd64.exe`)
45+
46+
Each asset records `asset_name`, `byte_size`, lowercase 64-hex `sha256`, and an immutable
47+
`download_url` whose final path segment equals `asset_name` over HTTPS with no query or fragment.
48+
The JSON Schema constrains URL shape and Go-style asset names; Brigade runtime validation also
49+
requires the download URL final segment to match `asset_name` exactly.
50+
51+
## Schema and runtime validation
52+
53+
`docs/component-manifest-v1.schema.json` is the structural contract for manifest authorship and
54+
review. `brigade.component_manifest.load` enforces the same platform matrix, asset naming rules,
55+
and download URL invariants at runtime, including exact `supported_platforms` order, full-matrix
56+
published assets, and empty or complete unpublished assets. Unknown component ids remain a soft
57+
diagnostic; malformed known components are a hard failure.
58+
59+
CI runs `scripts/verify_component_manifest_provenance.py` in a separate job from `./scripts/verify`.
60+
That script reads the bundled manifest, calls the GitHub Releases tag API with `urllib`, compares
61+
each published asset's name, `byte_size`, `browser_download_url`, and API `sha256:<hex>` digest,
62+
cross-checks every manifest `sha256`/name pair against the release `checksums.txt`, and verifies
63+
that each published component's `source.release_tag` matches the GitHub release tag embedded in
64+
asset `download_url` values. It never compares `component_revision` to the release tag and never
65+
downloads native binaries. Local runs work without `GITHUB_TOKEN`; CI passes `github.token` for
66+
rate-limit headroom.
67+
68+
## User-local path invariants
69+
70+
Components install under the user data root, never under a repo `.brigade` directory:
71+
72+
- Data root defaults: Linux `XDG_DATA_HOME` or `~/.local/share`, macOS
73+
`~/Library/Application Support`, Windows `%LOCALAPPDATA%`
74+
- Cache root defaults: Linux `XDG_CACHE_HOME` or `~/.cache`, macOS `~/Library/Caches`,
75+
Windows `%LOCALAPPDATA%`
76+
77+
Layout:
78+
79+
- `<data-root>/brigade/components/` - component metadata directory
80+
- `<data-root>/brigade/bin/<executable>` - managed executables
81+
- `<data-root>/brigade/installed.json` - install state (future phases)
82+
- `<cache-root>/brigade/components/<sha256>/<asset_name>` - verified download cache
83+
84+
Brigade does not relocate `.graphtrail/graphtrail.db` or MiseLedger archive paths.
85+
86+
## Forward compatibility
87+
88+
- Unknown `schema_version` values are a hard failure naming received and supported versions.
89+
- Unknown component ids listed in a v1 manifest are ignored for known-component operations and
90+
emit a deterministic diagnostic. The manifest is not rejected.
91+
- Malformed known components or assets are a hard failure naming component, platform, and field.
92+
93+
## Phase 1 boundaries
94+
95+
Issue #353 defines schema, pins, path invariants, and validation only. Downloading, unpacking,
96+
rollback, and managed-catalog rewrites are reserved for later issues.
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://brigade.tools/schemas/component-manifest-v1.schema.json",
4+
"title": "Brigade component manifest v1",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"required": [
8+
"schema_version",
9+
"brigade_version",
10+
"manifest_revision",
11+
"supported_platforms",
12+
"components"
13+
],
14+
"properties": {
15+
"schema_version": {"const": 1},
16+
"brigade_version": {"type": "string", "minLength": 1},
17+
"manifest_revision": {"type": "string", "minLength": 1},
18+
"supported_platforms": {
19+
"const": [
20+
"linux-amd64",
21+
"linux-arm64",
22+
"darwin-amd64",
23+
"darwin-arm64",
24+
"windows-amd64"
25+
]
26+
},
27+
"components": {
28+
"type": "object",
29+
"required": ["graphtrail", "graphtrail-mcp", "miseledger", "sessionfind"],
30+
"properties": {
31+
"graphtrail": {
32+
"allOf": [
33+
{
34+
"type": "object",
35+
"additionalProperties": false,
36+
"required": ["component_revision", "source", "executable", "assets"],
37+
"properties": {
38+
"component_revision": {"$ref": "#/$defs/graphtrail_component_revision"},
39+
"executable": {"const": "graphtrail"},
40+
"source": {},
41+
"assets": {}
42+
}
43+
},
44+
{
45+
"oneOf": [
46+
{
47+
"properties": {
48+
"source": {"$ref": "#/$defs/unpublished_source"},
49+
"assets": {"$ref": "#/$defs/empty_assets"}
50+
},
51+
"required": ["source", "assets"]
52+
},
53+
{
54+
"properties": {
55+
"source": {"$ref": "#/$defs/published_source"},
56+
"assets": {"$ref": "#/$defs/published_assets"}
57+
},
58+
"required": ["source", "assets"]
59+
}
60+
]
61+
}
62+
]
63+
},
64+
"graphtrail-mcp": {
65+
"allOf": [
66+
{
67+
"type": "object",
68+
"additionalProperties": false,
69+
"required": ["component_revision", "source", "executable", "assets"],
70+
"properties": {
71+
"component_revision": {"$ref": "#/$defs/graphtrail_component_revision"},
72+
"executable": {"const": "graphtrail-mcp"},
73+
"source": {},
74+
"assets": {}
75+
}
76+
},
77+
{
78+
"oneOf": [
79+
{
80+
"properties": {
81+
"source": {"$ref": "#/$defs/unpublished_source"},
82+
"assets": {"$ref": "#/$defs/empty_assets"}
83+
},
84+
"required": ["source", "assets"]
85+
},
86+
{
87+
"properties": {
88+
"source": {"$ref": "#/$defs/published_source"},
89+
"assets": {"$ref": "#/$defs/published_assets"}
90+
},
91+
"required": ["source", "assets"]
92+
}
93+
]
94+
}
95+
]
96+
},
97+
"miseledger": {
98+
"allOf": [
99+
{"$ref": "#/$defs/known_component"},
100+
{
101+
"properties": {
102+
"executable": {"const": "miseledger"},
103+
"assets": {"$ref": "#/$defs/published_assets"},
104+
"source": {"$ref": "#/$defs/published_source"}
105+
}
106+
}
107+
]
108+
},
109+
"sessionfind": {
110+
"allOf": [
111+
{"$ref": "#/$defs/known_component"},
112+
{
113+
"properties": {
114+
"executable": {"const": "sessionfind"},
115+
"assets": {"$ref": "#/$defs/published_assets"},
116+
"source": {"$ref": "#/$defs/published_source"}
117+
}
118+
}
119+
]
120+
}
121+
},
122+
"additionalProperties": true
123+
}
124+
},
125+
"$defs": {
126+
"known_component": {
127+
"type": "object",
128+
"additionalProperties": false,
129+
"required": ["component_revision", "source", "executable", "assets"],
130+
"properties": {
131+
"component_revision": {"type": "string", "minLength": 1},
132+
"source": {
133+
"type": "object",
134+
"additionalProperties": false,
135+
"required": ["repository"],
136+
"properties": {
137+
"repository": {"type": "string", "pattern": "^[^/]+/[^/]+$"},
138+
"release_tag": {"type": "string", "minLength": 1}
139+
}
140+
},
141+
"executable": {"type": "string", "minLength": 1},
142+
"assets": {
143+
"type": "object",
144+
"propertyNames": {
145+
"pattern": "^(linux|darwin|windows)-(amd64|arm64)$"
146+
},
147+
"additionalProperties": {"$ref": "#/$defs/asset"}
148+
}
149+
}
150+
},
151+
"graphtrail_component_revision": {
152+
"type": "string",
153+
"pattern": "^[0-9a-f]{40}$"
154+
},
155+
"published_source": {
156+
"type": "object",
157+
"additionalProperties": false,
158+
"required": ["repository", "release_tag"],
159+
"properties": {
160+
"repository": {"type": "string", "pattern": "^[^/]+/[^/]+$"},
161+
"release_tag": {"type": "string", "minLength": 1}
162+
}
163+
},
164+
"unpublished_source": {
165+
"type": "object",
166+
"additionalProperties": false,
167+
"required": ["repository"],
168+
"properties": {
169+
"repository": {"type": "string", "pattern": "^[^/]+/[^/]+$"}
170+
}
171+
},
172+
"empty_assets": {
173+
"type": "object",
174+
"additionalProperties": false,
175+
"maxProperties": 0
176+
},
177+
"published_assets": {
178+
"type": "object",
179+
"required": [
180+
"linux-amd64",
181+
"linux-arm64",
182+
"darwin-amd64",
183+
"darwin-arm64",
184+
"windows-amd64"
185+
],
186+
"properties": {
187+
"linux-amd64": {"$ref": "#/$defs/unix_asset"},
188+
"linux-arm64": {"$ref": "#/$defs/unix_asset"},
189+
"darwin-amd64": {"$ref": "#/$defs/unix_asset"},
190+
"darwin-arm64": {"$ref": "#/$defs/unix_asset"},
191+
"windows-amd64": {"$ref": "#/$defs/windows_asset"}
192+
},
193+
"additionalProperties": false
194+
},
195+
"asset": {
196+
"type": "object",
197+
"additionalProperties": false,
198+
"required": ["asset_name", "byte_size", "sha256", "download_url"],
199+
"properties": {
200+
"asset_name": {"type": "string", "minLength": 1, "pattern": "^[^/\\\\]+$"},
201+
"byte_size": {"type": "integer", "minimum": 1},
202+
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
203+
"download_url": {
204+
"type": "string",
205+
"pattern": "^https://[^?#]+/[^/?#]+$"
206+
}
207+
}
208+
},
209+
"unix_asset": {
210+
"allOf": [
211+
{"$ref": "#/$defs/asset"},
212+
{
213+
"properties": {
214+
"asset_name": {
215+
"pattern": "^[a-z0-9-]+-(linux|darwin)-(amd64|arm64)$"
216+
}
217+
}
218+
}
219+
]
220+
},
221+
"windows_asset": {
222+
"allOf": [
223+
{"$ref": "#/$defs/asset"},
224+
{
225+
"properties": {
226+
"asset_name": {
227+
"pattern": "^[a-z0-9-]+-windows-amd64\\.exe$"
228+
}
229+
}
230+
}
231+
]
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)