Skip to content

Commit 1e743b5

Browse files
committed
Add new marketplace changes
1 parent 2f13173 commit 1e743b5

File tree

21 files changed

+2142
-0
lines changed

21 files changed

+2142
-0
lines changed

openspec/CHANGE_ORDER.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ These are derived extensions of the same 2026-02-15 plan and are required to ope
7474
|--------|-------|---------------|----------|------------|
7575
| marketplace | 01 | ✅ marketplace-01-central-module-registry (implemented 2026-02-22; archived) | [#214](https://github.com/nold-ai/specfact-cli/issues/214) | #208 |
7676
| marketplace | 02 | marketplace-02-advanced-marketplace-features | [#215](https://github.com/nold-ai/specfact-cli/issues/215) | #214 |
77+
| marketplace | 03 | marketplace-03-publisher-identity | [#327](https://github.com/nold-ai/specfact-cli/issues/327) | #215 (marketplace-02) |
78+
| marketplace | 04 | marketplace-04-revocation | [#328](https://github.com/nold-ai/specfact-cli/issues/328) | marketplace-03 |
79+
| marketplace | 05 | marketplace-05-registry-federation | [#329](https://github.com/nold-ai/specfact-cli/issues/329) | marketplace-03 |
7780

7881
### Module migration (UX grouping and extraction)
7982

@@ -219,6 +222,9 @@ Set these in GitHub so issue dependencies are explicit. Optional dependencies ar
219222
| [#213](https://github.com/nold-ai/specfact-cli/issues/213) | arch-07 schema extensions | arch-04 ✅ (already implemented) |
220223
| [#214](https://github.com/nold-ai/specfact-cli/issues/214) | marketplace-01 registry | #208 |
221224
| [#215](https://github.com/nold-ai/specfact-cli/issues/215) | marketplace-02 advanced features | #214 |
225+
| [#327](https://github.com/nold-ai/specfact-cli/issues/327) | marketplace-03 publisher identity | #215 |
226+
| [#328](https://github.com/nold-ai/specfact-cli/issues/328) | marketplace-04 revocation | marketplace-03 (#327) |
227+
| [#329](https://github.com/nold-ai/specfact-cli/issues/329) | marketplace-05 registry federation | marketplace-03 (#327) |
222228
| [#173](https://github.com/nold-ai/specfact-cli/issues/173) | backlog-core-02 interactive create | #116 |
223229
| [#220](https://github.com/nold-ai/specfact-cli/issues/220) | backlog-scrum-01 standup | #116 |
224230
| [#170](https://github.com/nold-ai/specfact-cli/issues/170) | backlog-scrum-02 sprint planning | #116 |
@@ -319,6 +325,9 @@ Dependencies flow left-to-right; a wave may start once all its hard blockers are
319325
- backlog-safe-02 (needs backlog-safe-01; integrates with scrum/kanban via bridge registry)
320326
- module-migration-01-categorize-and-group (needs marketplace-02; adds category metadata + group commands)
321327
- module-migration-02-bundle-extraction (needs module-migration-01; moves module source to bundle packages, publishes to marketplace registry)
328+
- marketplace-03-publisher-identity (needs marketplace-02; can run parallel with module-migration-01/02/03)
329+
- marketplace-04-revocation (needs marketplace-03; must land before external publisher onboarding)
330+
- marketplace-05-registry-federation (needs marketplace-03)
322331

323332
- **Wave 4 — Ceremony layer + module slimming** (needs Wave 3):
324333
- ceremony-cockpit-01 ✅ (probes installed backlog-* modules at runtime; no hard deps but best after Wave 3)
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# Design: Publisher Identity and Module Trust Chain
2+
3+
## Context
4+
5+
marketplace-01 established Ed25519 publisher signing infrastructure. marketplace-02 adds multi-registry support and a trust level per registry. What is missing is a publisher identity layer: who is a publisher, what tier are they, and how does the CLI verify their identity at install time without a live accounts database.
6+
7+
**Current State:**
8+
9+
- `crypto_validator.py`: `validate_module()` has an `official` tier branch; publisher = `nold-ai` (string comparison)
10+
- No structured publisher record; no publisher key lookup from a signed index
11+
- No `community` or `verified` tier handling
12+
- `custom_registries.py`: registry trust level exists (from marketplace-02) but not linked to publisher tier
13+
14+
**Constraints:**
15+
16+
- NOLD AI root key must be bundled at build time (offline-first; no runtime CA lookup)
17+
- Trust index fetch must cache gracefully (7-day TTL fallback for CDN failures)
18+
- Backward compatible: existing `official` tier install path must not be touched
19+
- `specfact-cli-modules/` repo is separate — CLI reads from its deployed trust index URL, not from local files
20+
- Must not introduce a server-side accounts database in Phase 1
21+
22+
## Goals / Non-Goals
23+
24+
**Goals:**
25+
26+
- Structured publisher identity with three tiers (official, verified, community)
27+
- CLI verifies publisher attestation + registry endorsement at install time
28+
- Trust tier displayed in search/info output
29+
- Trust override flags with audit logging (offline-safe)
30+
31+
**Non-Goals:**
32+
33+
- Publisher self-registration UI (specfact.io-backend-phase1 — separate change, separate repo)
34+
- Registry federation / external registry certificates (marketplace-05)
35+
- Revocation infrastructure (marketplace-04)
36+
- Paid module gating (marketplace-06, requires legal entity)
37+
38+
## Architecture
39+
40+
### Trust Layer (`src/specfact_cli/trust/`)
41+
42+
Three modules with clear separation of concerns:
43+
44+
```text
45+
trust/
46+
key_store.py — NOLD AI root public key (Ed25519), bundled at build time
47+
publisher_registry.py — fetch + cache publishers/index.json; verify NOLD AI signature
48+
resolver.py — tier resolution order; install gate logic; audit logging
49+
```
50+
51+
`resolver.py` calls `crypto_validator.py` for low-level Ed25519 operations — does NOT duplicate crypto.
52+
53+
### Trust Resolution Sequence
54+
55+
```text
56+
specfact module install @mycompany/specfact-jira-sync
57+
58+
├─ trust/publisher_registry.py: fetch publishers/index.json (cache 7d)
59+
├─ key_store.py: verify NOLD AI signature over publishers/index.json
60+
├─ Resolve publisher_id from module's module-package.yaml publisher block
61+
├─ Fetch publisher record from index (publisher_id → tier + public_key)
62+
├─ crypto_validator.py: verify publisher Ed25519 signature on bundle
63+
├─ trust/resolver.py: resolve effective tier (publisher tier ∩ registry tier)
64+
65+
├─ official → install without prompt
66+
├─ verified → install without prompt
67+
├─ community → prompt unless --trust-community (log to audit)
68+
└─ unregistered → block unless --trust-unregistered (log to audit)
69+
```
70+
71+
### crypto_validator.py Extension Strategy
72+
73+
The existing `official` branch is preserved unchanged. New branches added alongside:
74+
75+
```python
76+
# BEFORE (migration-02): single official check
77+
if publisher == "nold-ai":
78+
validate_official(bundle, signature)
79+
80+
# AFTER (marketplace-03): tier dispatch, official path unchanged
81+
match tier:
82+
case "official":
83+
validate_official(bundle, signature) # unchanged
84+
case "verified":
85+
validate_verified(bundle, publisher_record, signature) # new
86+
case "community":
87+
validate_community(bundle, publisher_record, signature) # new
88+
case _:
89+
raise UnregisteredPublisherError(publisher)
90+
```
91+
92+
### Publisher Record Format
93+
94+
`module-package.yaml` structured `publisher:` block (marketplace-03 format):
95+
96+
```yaml
97+
publisher:
98+
publisher_id: pub_abc123
99+
handle: mycompany
100+
tier: verified
101+
public_key_fingerprint: sha256:abcdef...
102+
publisher_signature: "<Ed25519 sig over name+version+sha256>"
103+
```
104+
105+
CLI accepts both the legacy `publisher: nold-ai` string (migration-02) and the structured block during the transition window. Format detected at parse time.
106+
107+
### Registry Endorsement Countersignature
108+
109+
`registry/index.json` entry gains a `registry_signature` field (NOLD AI countersig over `name+version+publisher_id+checksum_sha256`). This is **distinct** from the publisher's `signature_ed25519`. Both coexist:
110+
111+
```json
112+
{
113+
"name": "specfact-jira-sync",
114+
"version": "1.0.0",
115+
"publisher_id": "pub_abc123",
116+
"tier": "verified",
117+
"checksum_sha256": "abcdef...",
118+
"signature_ed25519": "<publisher sig — from migration-02>",
119+
"registry_signature": "<NOLD AI countersig — new in marketplace-03>"
120+
}
121+
```
122+
123+
`scripts/publish-module.py` adds the countersig step after existing publisher signing.
124+
125+
## Decisions
126+
127+
### Decision 1: Trust Index Caching Strategy
128+
129+
**Options:**
130+
131+
- A: Always fetch from CDN, fail hard if unavailable
132+
- B: Cache with TTL, serve stale with warning
133+
- C: Cache only, no online refresh
134+
135+
Choice: B (7-day TTL cache, serve stale with staleness warning)
136+
137+
**Rationale:**
138+
139+
- Offline-first constraint: CLI must work without internet during runs
140+
- 7-day staleness acceptable for publisher index (revocation handled by marketplace-04)
141+
- Warning informs user when cache is stale without blocking install
142+
143+
### Decision 2: Root Key Bundling
144+
145+
**Options:**
146+
147+
- A: Fetch root key from well-known URL at runtime
148+
- B: Bundle root key in CLI package at build time
149+
- C: Store in user config (~/.specfact/)
150+
151+
Choice: B (bundled at build time)
152+
153+
**Rationale:**
154+
155+
- Offline-first: no network required for key verification
156+
- Tamper-evident: key changes require a CLI release (auditable)
157+
- Acceptable key rotation cadence: quarterly, requires CLI update
158+
159+
### Decision 3: Audit Log Location
160+
161+
**Choice**: `~/.specfact/module-audit.log` — append-only, human-readable, line-per-install
162+
163+
Captures: timestamp, module, tier, action (installed/blocked/prompted/accepted), flag used.
164+
165+
## Sequence Diagrams
166+
167+
### Install with Publisher Attestation
168+
169+
```text
170+
User CLI trust/ crypto_validator CDN
171+
│ │ │ │ │
172+
│ install @org/mod │ │ │ │
173+
│──────────────────>│ │ │ │
174+
│ │ fetch publishers/ │ │ │
175+
│ │─────────────────> │ │ │
176+
│ │ │ GET /trust/publishers/index.json │
177+
│ │ │──────────────────────────────────>│
178+
│ │ │<──────────────────────────────────│
179+
│ │ │ verify NOLD AI sig │
180+
│ │ │────────────────────> │
181+
│ │ │<──────────────────── │
182+
│ │ resolve publisher │ │ │
183+
│ │<────────────────- │ │ │
184+
│ │ verify bundle sig │ │ │
185+
│ │────────────────────────────────────────> │
186+
│ │<──────────────────────────────────────── │
187+
│ │ resolve tier │ │ │
188+
│ │─────────────────> │ │ │
189+
│ │ tier=verified │ │ │
190+
│ │<─────────────────│ │ │
191+
│ install proceeds │ │ │ │
192+
│<──────────────────│ │ │ │
193+
```
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Change: Publisher Identity and Module Trust Chain
2+
3+
## Why
4+
5+
marketplace-02 provides multi-registry support and dependency resolution, but modules carry no publisher attestation beyond a simple `publisher: nold-ai` string (introduced by module-migration-02). To enable a verified third-party module ecosystem, the CLI needs a CA-style publisher identity system: NOLD AI vouches for publisher identity and module integrity, but not for module content or behaviour. Publishers host their own artifacts; NOLD AI hosts only the trust index.
6+
7+
Without publisher attestation and registry endorsement countersignatures, the CLI cannot distinguish between official, verified-community, and unregistered modules — making safe third-party module installation impossible.
8+
9+
## What Changes
10+
11+
- **NEW**: `src/specfact_cli/trust/` — trust orchestration layer with three modules:
12+
- `resolver.py` — trust tier resolution order (`official > verified > community > unregistered`)
13+
- `publisher_registry.py` — fetch, cache, and verify `publishers/index.json` from trust index
14+
- `key_store.py` — NOLD AI root public key bundle (Ed25519) embedded at CLI build time
15+
- **MODIFY**: `src/specfact_cli/registry/crypto_validator.py` — extend `validate_module()` to add `verified` and `community` tier branches alongside the existing `official` branch from module-migration-02; add publisher record lookup from trust layer; do NOT replace the `official` path
16+
- **MODIFY**: `src/specfact_cli/modules/module_registry/src/` — trust verification at install time (call trust layer before download); trust tier display in `specfact module search` and `specfact module info` output; `--trust-community` and `--trust-unregistered` flags with audit logging to `~/.specfact/module-audit.log`
17+
- **MODIFY**: `scripts/publish-module.py` — add NOLD AI registry endorsement countersignature step after existing publisher signing step
18+
- **NEW**: `scripts/sign-publishers.py` — signs `publishers/index.json` with NOLD AI root key (run by CI on merge to specfact-cli-modules)
19+
- **NEW**: `docs/guides/publisher-trust.md` — user-facing guide on trust tiers, verification, install flags
20+
- **MODIFY**: `docs/reference/module-commands.md` — document trust tier output, new flags
21+
- **MODIFY**: `docs/_layouts/default.html` — add publisher-trust guide to sidebar navigation
22+
23+
**Backward compatibility**: Fully additive. The existing `official` tier path in `crypto_validator.py` is preserved unchanged. The transition from `publisher: nold-ai` (string, migration-02) to the structured `publisher:` block is handled by a format-detection branch in the parser — the CLI accepts both during a transition window.
24+
25+
**Rollback plan**: Revert trust/ module import, restore pre-marketplace-03 `crypto_validator.py` — all existing install flows remain unchanged.
26+
27+
## Capabilities
28+
29+
### New Capabilities
30+
31+
- `publisher-identity`: `publishers/index.json` schema definition, JSON Schema validation, structured publisher records (publisher_id, handle, tier, github_org, domain, public_key)
32+
- `module-trust-chain`: structured `publisher:` block in `module-package.yaml` (Level 2 publisher attestation); `registry_signature` NOLD AI countersig in `registry/index.json` entries (Level 3 registry endorsement); NOLD AI root key bundle in CLI build
33+
- `trust-resolution`: tier resolution order enforcement (`official > verified > community > unregistered`) at install time; `--trust-community` / `--trust-unregistered` flags; audit logging; tier badges in search/info output
34+
35+
### Modified Capabilities
36+
37+
- `module-security`: extend `validate_module()` in `crypto_validator.py` to add `verified` and `community` tier branches (spec delta only — extends existing capability from marketplace-01/migration-02)
38+
39+
## Impact
40+
41+
- **Affected code**:
42+
- `src/specfact_cli/trust/` (new: resolver.py, publisher_registry.py, key_store.py)
43+
- `src/specfact_cli/registry/crypto_validator.py` (modify: extend tier branches)
44+
- `src/specfact_cli/modules/module_registry/src/` (modify: trust verification + display)
45+
- `scripts/publish-module.py` (modify: add registry endorsement countersig step)
46+
- `scripts/sign-publishers.py` (new: CI signing script)
47+
- **Affected specs**: New specs for `publisher-identity`, `module-trust-chain`, `trust-resolution`; delta spec for `module-security`
48+
- **Affected documentation**:
49+
- `docs/guides/publisher-trust.md` (new)
50+
- `docs/reference/module-commands.md` (update: flags, trust display)
51+
- `docs/_layouts/default.html` (navigation update)
52+
- **External dependencies**: None beyond existing `cryptography` library (already in requirements via arch-06)
53+
- **Integration points**: Trust layer integrates with `crypto_validator.py` (signature checks), `module_installer.py` (pre-install gate), `custom_registries.py` (registry trust level)
54+
- **Backward compatibility**: Fully additive; official tier path unchanged; dual-format publisher string handled
55+
- **Hard blocker**: marketplace-02 (#215) must land first (provides `custom_registries.py` trust level infrastructure that `trust/resolver.py` extends)
56+
57+
---
58+
59+
## Source Tracking
60+
61+
<!-- source_repo: nold-ai/specfact-cli -->
62+
- **GitHub Issue**: #327
63+
- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/327>
64+
- **Repository**: nold-ai/specfact-cli
65+
- **Last Synced Status**: proposed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# module-security Specification Delta
2+
3+
## Purpose
4+
5+
Delta spec extending the existing `module-security` capability (established by marketplace-01 and arch-06) to support `verified` and `community` tier branches in `validate_module()`. The `official` path defined in module-migration-02 is preserved and unchanged.
6+
7+
## MODIFIED Requirements
8+
9+
### Requirement: validate_module() dispatches by publisher tier
10+
11+
`validate_module()` in `crypto_validator.py` SHALL dispatch validation logic based on publisher tier, adding `verified` and `community` branches without replacing the existing `official` branch.
12+
13+
#### Scenario: Official module validation (existing — unchanged)
14+
15+
- **GIVEN** a module with `publisher: nold-ai` (legacy string) or `tier: official`
16+
- **WHEN** `validate_module()` is called
17+
- **THEN** SHALL execute the existing official validation path unchanged
18+
- **AND** SHALL NOT call publisher registry lookup
19+
20+
#### Scenario: Verified module validation (new)
21+
22+
- **GIVEN** a module with `tier: verified` and a structured `publisher:` block
23+
- **WHEN** `validate_module()` is called
24+
- **THEN** SHALL call `trust/publisher_registry.resolve_publisher(publisher_id, index)` to fetch publisher record
25+
- **AND** SHALL verify `publisher_signature` against the resolved public key
26+
- **AND** SHALL verify `registry_signature` (NOLD AI countersig) against the bundled root key
27+
- **AND** SHALL raise `PublisherSignatureMismatchError` if either check fails
28+
29+
#### Scenario: Community module validation (new)
30+
31+
- **GIVEN** a module with `tier: community` and a structured `publisher:` block
32+
- **WHEN** `validate_module()` is called
33+
- **THEN** SHALL call `trust/publisher_registry.resolve_publisher(publisher_id, index)`
34+
- **AND** SHALL verify `publisher_signature` (publisher key from index)
35+
- **AND** `registry_signature` check is optional for `community` tier (countersig may not be present)
36+
- **AND** SHALL raise `PublisherSignatureMismatchError` if publisher signature fails
37+
38+
#### Scenario: Unknown tier
39+
40+
- **GIVEN** a module with an unrecognised `tier` value
41+
- **WHEN** `validate_module()` is called
42+
- **THEN** SHALL raise `UnknownTierError(tier)` with a clear message
43+
44+
## Contract Requirements
45+
46+
- `validate_module(module: ModuleManifest, tier: str, publisher_index: PublisherIndex | None) -> ValidationResult``@require` tier in `{"official", "verified", "community"}`; `@ensure` result.valid is bool; `@beartype`
47+
- Existing contract on `official` path MUST remain satisfied — no regressions allowed

0 commit comments

Comments
 (0)