From afbaa7ca5058df689a2ecc1d990ed3a63fdbf727 Mon Sep 17 00:00:00 2001 From: Paul Schifferer Date: Tue, 21 Jul 2026 19:46:37 -0700 Subject: [PATCH 1/3] feat(manifest): add developer and build_date metadata fields Two optional, display-only manifest fields so hosts can show who built a plugin and when, without affecting schema/interface compatibility checks. Additive - existing manifests keep parsing unchanged. Closes #13 --- CHANGELOG.md | 2 + .../add-plugin-build-metadata/tasks.md | 24 ++--- src/manifest.rs | 102 ++++++++++++++++++ 3 files changed, 116 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75cf673..f454e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ distinct from `id` (hosts must not derive a display name from `id`) - Add an optional `[names]` table for locale-keyed display names, and `Manifest::localized_name` to look one up with fallback to `name` +- Add optional `developer` and `build_date` display-only fields to `Manifest`, for a + plugin's author/publisher name and build timestamp ## [0.1.1] - 2026-07-20 diff --git a/openspec/changes/add-plugin-build-metadata/tasks.md b/openspec/changes/add-plugin-build-metadata/tasks.md index 754d9e3..078e910 100644 --- a/openspec/changes/add-plugin-build-metadata/tasks.md +++ b/openspec/changes/add-plugin-build-metadata/tasks.md @@ -1,33 +1,33 @@ ## 1. Manifest Schema -- [ ] 1.1 Add `developer: Option` and `build_date: Option` to `Manifest` in +- [x] 1.1 Add `developer: Option` and `build_date: Option` to `Manifest` in `src/manifest.rs`, each with a doc comment noting `build_date` is conventionally RFC 3339 but unvalidated (matching `Fixture.kickoff`'s treatment) -- [ ] 1.2 Add the corresponding optional fields to `RawManifest` -- [ ] 1.3 Add `ManifestField::Developer` and `ManifestField::BuildDate` variants, including their +- [x] 1.2 Add the corresponding optional fields to `RawManifest` +- [x] 1.3 Add `ManifestField::Developer` and `ManifestField::BuildDate` variants, including their `Display` impl arm ## 2. Parsing and Validation -- [ ] 2.1 In `Manifest::parse`, thread both new fields through as `Option`, defaulting to +- [x] 2.1 In `Manifest::parse`, thread both new fields through as `Option`, defaulting to `None` when absent -- [ ] 2.2 Reject a present-but-empty/whitespace-only `developer` or `build_date` with +- [x] 2.2 Reject a present-but-empty/whitespace-only `developer` or `build_date` with `ManifestError::InvalidField`, reusing (or extracting into a shared helper alongside) `network_hosts`'s existing empty-entry check ## 3. Tests -- [ ] 3.1 Unit test: manifest omitting both fields parses successfully with both `None` -- [ ] 3.2 Unit test: manifest declaring both fields parses successfully and exposes them +- [x] 3.1 Unit test: manifest omitting both fields parses successfully with both `None` +- [x] 3.2 Unit test: manifest declaring both fields parses successfully and exposes them unchanged -- [ ] 3.3 Unit test: empty `developer` field is rejected with +- [x] 3.3 Unit test: empty `developer` field is rejected with `ManifestField::Developer` -- [ ] 3.4 Unit test: empty `build_date` field is rejected with `ManifestField::BuildDate` -- [ ] 3.5 Update the crate-level doc example in `src/lib.rs` and/or `README.md` if either shows a +- [x] 3.4 Unit test: empty `build_date` field is rejected with `ManifestField::BuildDate` +- [x] 3.5 Update the crate-level doc example in `src/lib.rs` and/or `README.md` if either shows a full manifest, so they stay accurate (additive fields, no required change, but worth checking) ## 4. Release -- [ ] 4.1 Update `CHANGELOG.md`'s `[Unreleased]` section describing the additive manifest change -- [ ] 4.2 Confirm `RELEASING.md`'s process results in a minor version bump (additive manifest +- [x] 4.1 Update `CHANGELOG.md`'s `[Unreleased]` section describing the additive manifest change +- [x] 4.2 Confirm `RELEASING.md`'s process results in a minor version bump (additive manifest field), not a patch or major diff --git a/src/manifest.rs b/src/manifest.rs index f0e75a4..998cb9c 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -61,6 +61,14 @@ pub struct Manifest { pub interface_version: Version, /// Network hosts this plugin requires access to. pub network_hosts: Vec, + /// Display name/identifier for the plugin's author or publisher. Display-only; + /// does not affect schema/interface compatibility checks. + pub developer: Option, + /// Timestamp for when the plugin was built, conventionally RFC 3339 (matching + /// `Fixture.kickoff`'s convention). Not parsed or validated as a timestamp by + /// this crate. Display-only; does not affect schema/interface compatibility + /// checks. + pub build_date: Option, } impl Manifest { @@ -91,6 +99,10 @@ pub enum ManifestField { NetworkHosts, /// The `[names]` table. LocalizedNames, + /// The `developer` field. + Developer, + /// The `build_date` field. + BuildDate, } impl core::fmt::Display for ManifestField { @@ -103,6 +115,8 @@ impl core::fmt::Display for ManifestField { Self::InterfaceVersion => "interface_version", Self::NetworkHosts => "network_hosts", Self::LocalizedNames => "names", + Self::Developer => "developer", + Self::BuildDate => "build_date", }; f.write_str(name) } @@ -136,6 +150,8 @@ struct RawManifest { schema_version: Option, interface_version: Option, network_hosts: Option>, + developer: Option, + build_date: Option, } impl Manifest { @@ -192,6 +208,9 @@ impl Manifest { }); } + let developer = reject_if_empty(raw.developer, ManifestField::Developer)?; + let build_date = reject_if_empty(raw.build_date, ManifestField::BuildDate)?; + Ok(Self { id, name, @@ -200,6 +219,8 @@ impl Manifest { schema_version, interface_version, network_hosts, + developer, + build_date, }) } } @@ -211,6 +232,21 @@ fn required(value: Option, field: ManifestField) -> Result, + field: ManifestField, +) -> Result, ManifestError> { + match value { + Some(v) if v.trim().is_empty() => Err(ManifestError::InvalidField { + field, + reason: format!("{field} must not be empty"), + }), + other => Ok(other), + } +} + fn parse_version(value: Option, field: ManifestField) -> Result { let raw = required(value, field)?; raw.parse().map_err(|_| ManifestError::InvalidField { @@ -409,6 +445,72 @@ mod tests { assert!(crate::INTERFACE_VERSION.accepts(manifest.interface_version)); } + #[test] + fn parses_a_manifest_omitting_developer_and_build_date() { + let manifest = Manifest::parse(valid_toml()).unwrap(); + assert_eq!(manifest.developer, None); + assert_eq!(manifest.build_date, None); + } + + #[test] + fn parses_a_manifest_declaring_developer_and_build_date() { + let toml = r#" + id = "bundesliga" + name = "Bundesliga" + version = "0.1.0" + schema_version = "1.0" + interface_version = "1.0" + network_hosts = ["api.openligadb.de"] + developer = "Jane Plugin Author" + build_date = "2026-07-21T00:00:00Z" + "#; + let manifest = Manifest::parse(toml).unwrap(); + assert_eq!(manifest.developer.as_deref(), Some("Jane Plugin Author")); + assert_eq!(manifest.build_date.as_deref(), Some("2026-07-21T00:00:00Z")); + } + + #[test] + fn rejects_empty_developer_field() { + let toml = r#" + id = "bundesliga" + name = "Bundesliga" + version = "0.1.0" + schema_version = "1.0" + interface_version = "1.0" + network_hosts = ["api.openligadb.de"] + developer = " " + "#; + let err = Manifest::parse(toml).unwrap_err(); + assert!(matches!( + err, + ManifestError::InvalidField { + field: ManifestField::Developer, + .. + } + )); + } + + #[test] + fn rejects_empty_build_date_field() { + let toml = r#" + id = "bundesliga" + name = "Bundesliga" + version = "0.1.0" + schema_version = "1.0" + interface_version = "1.0" + network_hosts = ["api.openligadb.de"] + build_date = " " + "#; + let err = Manifest::parse(toml).unwrap_err(); + assert!(matches!( + err, + ManifestError::InvalidField { + field: ManifestField::BuildDate, + .. + } + )); + } + #[test] fn interface_version_1_0_is_rejected_after_the_host_fetch_major_bump() { // A plugin built before `host.fetch` existed declares interface_version 1.0; the From fced4baf18a8ddb3efbb6157b51d1341eb7107a5 Mon Sep 17 00:00:00 2001 From: Paul Schifferer Date: Tue, 21 Jul 2026 19:49:31 -0700 Subject: [PATCH 2/3] chore(openspec): archive add-plugin-build-metadata Sync the Plugin Build Metadata requirement into the main plugin-manifest-format spec and archive the completed change. --- .../.openspec.yaml | 2 + .../design.md | 83 +++++++++++++++++++ .../proposal.md | 45 ++++++++++ .../specs/plugin-manifest-format/spec.md | 26 ++++++ .../tasks.md | 33 ++++++++ openspec/specs/plugin-manifest-format/spec.md | 25 ++++++ 6 files changed, 214 insertions(+) create mode 100644 openspec/changes/archive/2026-07-21-add-plugin-build-metadata/.openspec.yaml create mode 100644 openspec/changes/archive/2026-07-21-add-plugin-build-metadata/design.md create mode 100644 openspec/changes/archive/2026-07-21-add-plugin-build-metadata/proposal.md create mode 100644 openspec/changes/archive/2026-07-21-add-plugin-build-metadata/specs/plugin-manifest-format/spec.md create mode 100644 openspec/changes/archive/2026-07-21-add-plugin-build-metadata/tasks.md diff --git a/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/.openspec.yaml b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/.openspec.yaml new file mode 100644 index 0000000..c0a8162 --- /dev/null +++ b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-21 diff --git a/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/design.md b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/design.md new file mode 100644 index 0000000..9fb9d0d --- /dev/null +++ b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/design.md @@ -0,0 +1,83 @@ +## Context + +The plugin manifest (`src/manifest.rs`) currently carries `id`, `version`, `schema_version`, +`interface_version`, and `network_hosts` — enough for the host to load and version-check a +plugin, but nothing to show a human which developer/publisher built it or when. `Apps/rust`'s +Plugins management screen (`openspec/changes/plugin-host-runtime`, already implemented there) +lists `id` and `version` only, for exactly this reason. + +Two prior manifest fields established a validation precedent worth following here: +`schema_version`/`interface_version` are parsed into a typed [`Version`] because the host +actively compares them for compatibility. `network_hosts` entries get only a "must not be empty" +check because they're used as-is (string equality against a request's host). This change's two +new fields are purely informational — nothing compares or parses them — so they follow the +`network_hosts` precedent, not the `Version` one. + +## Goals / Non-Goals + +**Goals:** +- Let a plugin manifest optionally declare a developer/publisher display name and a build + timestamp. +- Keep every existing manifest (in particular `Plugins/Bundesliga`'s) parsing unchanged with no + edits required — an additive, minor-version change per this crate's own versioning policy. + +**Non-Goals:** +- Validating `build_date` as a well-formed timestamp. This crate never validates `Fixture.kickoff` + (also documented as RFC 3339) either; adding parsing here would be inconsistent and would pull + in a date/time dependency (`time` or `chrono`) this crate has never needed, bloating every + plugin's compiled `wasm32` component for a display-only field. +- Any host-side or UI-side consumption of these fields. Surfacing them in `Apps/rust`'s Plugins + screen is a separate, follow-up change in that repo. +- Making either field required. That would be a breaking, major-version change forcing every + existing plugin (starting with `Plugins/Bundesliga`) to update its manifest before it could be + loaded by a host built against the new version. + +## Decisions + +**Both fields are `Option`, not a new typed wrapper.** `developer` is a free-form display +string (no format to validate beyond non-empty). `build_date` is documented as RFC 3339 but stored +and returned as the raw string, exactly like `Fixture.kickoff` — this crate parses neither. +Alternative considered: a `Version`-style typed date wrapper with parse validation, rejected per +the Non-Goals above (inconsistent with `kickoff`, needless dependency, no consumer that needs a +parsed value yet). + +**Both fields are optional, not required.** Alternative considered: required fields, rejected +because it forces a major version bump and breaks every existing manifest, for two fields whose +absence is a completely reasonable state (a plugin author who hasn't set up a build-date stamping +step yet, or doesn't want to disclose a developer name). + +**Validation mirrors `network_hosts`, not `schema_version`.** When present, each field must be a +non-empty string after trimming (same rule `network_hosts` entries already use) — not a schema +compatibility concern, so no `ManifestField` variant needs special version-parsing logic, just the +same "field is present but empty" rejection path `network_hosts` already has. + +## Risks / Trade-offs + +- [A future need to actually parse `build_date` (e.g. to sort plugins by recency) would require + revisiting the no-validation decision] → Acceptable now: no consumer needs a parsed value yet, + and adding validation later is itself another additive, non-breaking change (tightening an + `Option` to reject previously-accepted malformed strings would be the only breaking + edge case, and is deferred to if/when it's actually needed). +- [`developer` has no format constraint at all, so two plugins could declare visually-identical or + confusingly-similar developer names] → Out of scope: this crate validates manifest structure, + not developer identity or trust — matching its existing stated non-goal for `network_hosts` + ("this crate validates manifest format only"). + +## Migration Plan + +1. Add both fields to `RawManifest` and `Manifest`, both `Option`, with the non-empty + check applied only when present. +2. Bump `Cargo.toml`'s version per this being an additive/minor change (handled by the normal + `git-cliff`-driven release process in `RELEASING.md`, not a manual step here). +3. No manifest anywhere needs to change for this to ship — `Plugins/Bundesliga`'s current + `manifest.toml` keeps parsing exactly as it does today, with both new fields resolving to + `None`. + +Rollback: revert the two-field addition; no data migration exists since nothing is persisted by +this crate itself. + +## Open Questions + +- Should `Apps/rust`'s Plugins screen surface these fields once available? Deferred to a + follow-up change in that repo, coordinated after this one ships and a new `fulltime-plugin-api` + version is cut. diff --git a/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/proposal.md b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/proposal.md new file mode 100644 index 0000000..4bf9484 --- /dev/null +++ b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/proposal.md @@ -0,0 +1,45 @@ +## Why + +The plugin manifest currently has no field for who built a plugin or when. `fulltime-core`'s +Plugins management screen (`openspec/changes/plugin-host-runtime` in `Apps/rust`) lists each +plugin's `id` and `version` only, because that's all the manifest carries — there's nowhere to +show a developer/publisher name or a build timestamp to help a user tell plugins apart or judge +how current one is. + +## What Changes + +- Add two optional manifest fields: `developer` (a display name/identifier for the plugin's + author or publisher) and `build_date` (an RFC 3339 timestamp for when the plugin was built). + Optional, not required, so existing manifests (e.g. `Plugins/Bundesliga`'s) keep parsing + without changes — an additive, minor-version manifest schema change under this crate's own + versioning policy (see `RELEASING.md`/`src/version.rs`'s doc comments). +- `Manifest::parse` accepts and exposes both fields when present, and treats their absence as + `None` rather than a parse error. Neither field affects host/plugin compatibility checks — both + are display-only metadata, unlike `schema_version`/`interface_version`. +- `build_date` is documented as RFC 3339 (matching the existing `Fixture.kickoff` convention in + `wit/data-provider.wit`) but is not parsed/validated by this crate — same treatment as + `kickoff`, which this crate also never validates. A non-empty check only, matching + `network_hosts` entries. + +## Capabilities + +### New Capabilities + +(none) + +### Modified Capabilities + +- `plugin-manifest-format`: the manifest schema gains two optional fields, `developer` and + `build_date`, each exposed on the parsed `Manifest` and validated at parse time when present. + +## Impact + +- **`src/manifest.rs`**: `Manifest` struct gains `developer: Option` and + `build_date: Option` (or a parsed timestamp type — see `design.md`), `RawManifest` + gains the corresponding optional fields, and `Manifest::parse` validates `build_date`'s format + when present. +- **Downstream plugins** (`Plugins/Bundesliga`, future plugins): unaffected unless they choose to + add the new fields to their own `manifest.toml`. +- **`Apps/rust`'s plugin management UI** (`openspec/changes/plugin-host-runtime`, a separate, + already-implemented change in that repo): a follow-up change there would surface these fields + in the Plugins screen once this manifest change ships — out of scope here. diff --git a/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/specs/plugin-manifest-format/spec.md b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/specs/plugin-manifest-format/spec.md new file mode 100644 index 0000000..cd1293a --- /dev/null +++ b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/specs/plugin-manifest-format/spec.md @@ -0,0 +1,26 @@ +## ADDED Requirements + +### Requirement: Plugin Build Metadata +The manifest schema SHALL support two optional display-only fields: `developer` (a +display name/identifier for the plugin's author or publisher) and `build_date` (a +timestamp, conventionally RFC 3339, for when the plugin was built). Neither field SHALL be +required, and neither SHALL affect schema/interface compatibility checks. + +#### Scenario: Manifest omits both fields +- **WHEN** a manifest has no `developer` or `build_date` field +- **THEN** parsing succeeds and the parsed manifest exposes both as absent, not as an error + +#### Scenario: Manifest declares a developer name +- **WHEN** a manifest includes a non-empty `developer` field +- **THEN** the parsed manifest exposes that value unchanged + +#### Scenario: Manifest declares a build date +- **WHEN** a manifest includes a non-empty `build_date` field +- **THEN** the parsed manifest exposes that value unchanged, without being parsed or validated + as a timestamp + +#### Scenario: Empty developer or build_date field is rejected +- **WHEN** a manifest includes a `developer` or `build_date` field present but empty (or + whitespace-only) +- **THEN** parsing fails with a structured error identifying the invalid field, the same way an + empty `network_hosts` entry is rejected diff --git a/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/tasks.md b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/tasks.md new file mode 100644 index 0000000..078e910 --- /dev/null +++ b/openspec/changes/archive/2026-07-21-add-plugin-build-metadata/tasks.md @@ -0,0 +1,33 @@ +## 1. Manifest Schema + +- [x] 1.1 Add `developer: Option` and `build_date: Option` to `Manifest` in + `src/manifest.rs`, each with a doc comment noting `build_date` is conventionally RFC 3339 but + unvalidated (matching `Fixture.kickoff`'s treatment) +- [x] 1.2 Add the corresponding optional fields to `RawManifest` +- [x] 1.3 Add `ManifestField::Developer` and `ManifestField::BuildDate` variants, including their + `Display` impl arm + +## 2. Parsing and Validation + +- [x] 2.1 In `Manifest::parse`, thread both new fields through as `Option`, defaulting to + `None` when absent +- [x] 2.2 Reject a present-but-empty/whitespace-only `developer` or `build_date` with + `ManifestError::InvalidField`, reusing (or extracting into a shared helper alongside) + `network_hosts`'s existing empty-entry check + +## 3. Tests + +- [x] 3.1 Unit test: manifest omitting both fields parses successfully with both `None` +- [x] 3.2 Unit test: manifest declaring both fields parses successfully and exposes them + unchanged +- [x] 3.3 Unit test: empty `developer` field is rejected with + `ManifestField::Developer` +- [x] 3.4 Unit test: empty `build_date` field is rejected with `ManifestField::BuildDate` +- [x] 3.5 Update the crate-level doc example in `src/lib.rs` and/or `README.md` if either shows a + full manifest, so they stay accurate (additive fields, no required change, but worth checking) + +## 4. Release + +- [x] 4.1 Update `CHANGELOG.md`'s `[Unreleased]` section describing the additive manifest change +- [x] 4.2 Confirm `RELEASING.md`'s process results in a minor version bump (additive manifest + field), not a patch or major diff --git a/openspec/specs/plugin-manifest-format/spec.md b/openspec/specs/plugin-manifest-format/spec.md index c8a8143..cca3a68 100644 --- a/openspec/specs/plugin-manifest-format/spec.md +++ b/openspec/specs/plugin-manifest-format/spec.md @@ -28,3 +28,28 @@ enable/disable state) — those belong to the plugin host runtime. will later reject for policy reasons - **THEN** this crate parses the manifest successfully; the runtime enforcement decision happens outside this crate + +### Requirement: Plugin Build Metadata +The manifest schema SHALL support two optional display-only fields: `developer` (a +display name/identifier for the plugin's author or publisher) and `build_date` (a +timestamp, conventionally RFC 3339, for when the plugin was built). Neither field SHALL be +required, and neither SHALL affect schema/interface compatibility checks. + +#### Scenario: Manifest omits both fields +- **WHEN** a manifest has no `developer` or `build_date` field +- **THEN** parsing succeeds and the parsed manifest exposes both as absent, not as an error + +#### Scenario: Manifest declares a developer name +- **WHEN** a manifest includes a non-empty `developer` field +- **THEN** the parsed manifest exposes that value unchanged + +#### Scenario: Manifest declares a build date +- **WHEN** a manifest includes a non-empty `build_date` field +- **THEN** the parsed manifest exposes that value unchanged, without being parsed or validated + as a timestamp + +#### Scenario: Empty developer or build_date field is rejected +- **WHEN** a manifest includes a `developer` or `build_date` field present but empty (or + whitespace-only) +- **THEN** parsing fails with a structured error identifying the invalid field, the same way an + empty `network_hosts` entry is rejected From 588e1bbb5af6824566fec2cad042ee002c9b7b3d Mon Sep 17 00:00:00 2001 From: Paul Schifferer Date: Tue, 21 Jul 2026 19:49:39 -0700 Subject: [PATCH 3/3] chore(openspec): remove archived change source directory Follow-up to fced4ba: the change was copied into openspec/changes/archive/ via a plain mv, which git recorded as untracked additions rather than renames; this commit stages the corresponding deletions. --- .../add-plugin-build-metadata/.openspec.yaml | 2 - .../add-plugin-build-metadata/design.md | 83 ------------------- .../add-plugin-build-metadata/proposal.md | 45 ---------- .../specs/plugin-manifest-format/spec.md | 26 ------ .../add-plugin-build-metadata/tasks.md | 33 -------- 5 files changed, 189 deletions(-) delete mode 100644 openspec/changes/add-plugin-build-metadata/.openspec.yaml delete mode 100644 openspec/changes/add-plugin-build-metadata/design.md delete mode 100644 openspec/changes/add-plugin-build-metadata/proposal.md delete mode 100644 openspec/changes/add-plugin-build-metadata/specs/plugin-manifest-format/spec.md delete mode 100644 openspec/changes/add-plugin-build-metadata/tasks.md diff --git a/openspec/changes/add-plugin-build-metadata/.openspec.yaml b/openspec/changes/add-plugin-build-metadata/.openspec.yaml deleted file mode 100644 index c0a8162..0000000 --- a/openspec/changes/add-plugin-build-metadata/.openspec.yaml +++ /dev/null @@ -1,2 +0,0 @@ -schema: spec-driven -created: 2026-07-21 diff --git a/openspec/changes/add-plugin-build-metadata/design.md b/openspec/changes/add-plugin-build-metadata/design.md deleted file mode 100644 index 9fb9d0d..0000000 --- a/openspec/changes/add-plugin-build-metadata/design.md +++ /dev/null @@ -1,83 +0,0 @@ -## Context - -The plugin manifest (`src/manifest.rs`) currently carries `id`, `version`, `schema_version`, -`interface_version`, and `network_hosts` — enough for the host to load and version-check a -plugin, but nothing to show a human which developer/publisher built it or when. `Apps/rust`'s -Plugins management screen (`openspec/changes/plugin-host-runtime`, already implemented there) -lists `id` and `version` only, for exactly this reason. - -Two prior manifest fields established a validation precedent worth following here: -`schema_version`/`interface_version` are parsed into a typed [`Version`] because the host -actively compares them for compatibility. `network_hosts` entries get only a "must not be empty" -check because they're used as-is (string equality against a request's host). This change's two -new fields are purely informational — nothing compares or parses them — so they follow the -`network_hosts` precedent, not the `Version` one. - -## Goals / Non-Goals - -**Goals:** -- Let a plugin manifest optionally declare a developer/publisher display name and a build - timestamp. -- Keep every existing manifest (in particular `Plugins/Bundesliga`'s) parsing unchanged with no - edits required — an additive, minor-version change per this crate's own versioning policy. - -**Non-Goals:** -- Validating `build_date` as a well-formed timestamp. This crate never validates `Fixture.kickoff` - (also documented as RFC 3339) either; adding parsing here would be inconsistent and would pull - in a date/time dependency (`time` or `chrono`) this crate has never needed, bloating every - plugin's compiled `wasm32` component for a display-only field. -- Any host-side or UI-side consumption of these fields. Surfacing them in `Apps/rust`'s Plugins - screen is a separate, follow-up change in that repo. -- Making either field required. That would be a breaking, major-version change forcing every - existing plugin (starting with `Plugins/Bundesliga`) to update its manifest before it could be - loaded by a host built against the new version. - -## Decisions - -**Both fields are `Option`, not a new typed wrapper.** `developer` is a free-form display -string (no format to validate beyond non-empty). `build_date` is documented as RFC 3339 but stored -and returned as the raw string, exactly like `Fixture.kickoff` — this crate parses neither. -Alternative considered: a `Version`-style typed date wrapper with parse validation, rejected per -the Non-Goals above (inconsistent with `kickoff`, needless dependency, no consumer that needs a -parsed value yet). - -**Both fields are optional, not required.** Alternative considered: required fields, rejected -because it forces a major version bump and breaks every existing manifest, for two fields whose -absence is a completely reasonable state (a plugin author who hasn't set up a build-date stamping -step yet, or doesn't want to disclose a developer name). - -**Validation mirrors `network_hosts`, not `schema_version`.** When present, each field must be a -non-empty string after trimming (same rule `network_hosts` entries already use) — not a schema -compatibility concern, so no `ManifestField` variant needs special version-parsing logic, just the -same "field is present but empty" rejection path `network_hosts` already has. - -## Risks / Trade-offs - -- [A future need to actually parse `build_date` (e.g. to sort plugins by recency) would require - revisiting the no-validation decision] → Acceptable now: no consumer needs a parsed value yet, - and adding validation later is itself another additive, non-breaking change (tightening an - `Option` to reject previously-accepted malformed strings would be the only breaking - edge case, and is deferred to if/when it's actually needed). -- [`developer` has no format constraint at all, so two plugins could declare visually-identical or - confusingly-similar developer names] → Out of scope: this crate validates manifest structure, - not developer identity or trust — matching its existing stated non-goal for `network_hosts` - ("this crate validates manifest format only"). - -## Migration Plan - -1. Add both fields to `RawManifest` and `Manifest`, both `Option`, with the non-empty - check applied only when present. -2. Bump `Cargo.toml`'s version per this being an additive/minor change (handled by the normal - `git-cliff`-driven release process in `RELEASING.md`, not a manual step here). -3. No manifest anywhere needs to change for this to ship — `Plugins/Bundesliga`'s current - `manifest.toml` keeps parsing exactly as it does today, with both new fields resolving to - `None`. - -Rollback: revert the two-field addition; no data migration exists since nothing is persisted by -this crate itself. - -## Open Questions - -- Should `Apps/rust`'s Plugins screen surface these fields once available? Deferred to a - follow-up change in that repo, coordinated after this one ships and a new `fulltime-plugin-api` - version is cut. diff --git a/openspec/changes/add-plugin-build-metadata/proposal.md b/openspec/changes/add-plugin-build-metadata/proposal.md deleted file mode 100644 index 4bf9484..0000000 --- a/openspec/changes/add-plugin-build-metadata/proposal.md +++ /dev/null @@ -1,45 +0,0 @@ -## Why - -The plugin manifest currently has no field for who built a plugin or when. `fulltime-core`'s -Plugins management screen (`openspec/changes/plugin-host-runtime` in `Apps/rust`) lists each -plugin's `id` and `version` only, because that's all the manifest carries — there's nowhere to -show a developer/publisher name or a build timestamp to help a user tell plugins apart or judge -how current one is. - -## What Changes - -- Add two optional manifest fields: `developer` (a display name/identifier for the plugin's - author or publisher) and `build_date` (an RFC 3339 timestamp for when the plugin was built). - Optional, not required, so existing manifests (e.g. `Plugins/Bundesliga`'s) keep parsing - without changes — an additive, minor-version manifest schema change under this crate's own - versioning policy (see `RELEASING.md`/`src/version.rs`'s doc comments). -- `Manifest::parse` accepts and exposes both fields when present, and treats their absence as - `None` rather than a parse error. Neither field affects host/plugin compatibility checks — both - are display-only metadata, unlike `schema_version`/`interface_version`. -- `build_date` is documented as RFC 3339 (matching the existing `Fixture.kickoff` convention in - `wit/data-provider.wit`) but is not parsed/validated by this crate — same treatment as - `kickoff`, which this crate also never validates. A non-empty check only, matching - `network_hosts` entries. - -## Capabilities - -### New Capabilities - -(none) - -### Modified Capabilities - -- `plugin-manifest-format`: the manifest schema gains two optional fields, `developer` and - `build_date`, each exposed on the parsed `Manifest` and validated at parse time when present. - -## Impact - -- **`src/manifest.rs`**: `Manifest` struct gains `developer: Option` and - `build_date: Option` (or a parsed timestamp type — see `design.md`), `RawManifest` - gains the corresponding optional fields, and `Manifest::parse` validates `build_date`'s format - when present. -- **Downstream plugins** (`Plugins/Bundesliga`, future plugins): unaffected unless they choose to - add the new fields to their own `manifest.toml`. -- **`Apps/rust`'s plugin management UI** (`openspec/changes/plugin-host-runtime`, a separate, - already-implemented change in that repo): a follow-up change there would surface these fields - in the Plugins screen once this manifest change ships — out of scope here. diff --git a/openspec/changes/add-plugin-build-metadata/specs/plugin-manifest-format/spec.md b/openspec/changes/add-plugin-build-metadata/specs/plugin-manifest-format/spec.md deleted file mode 100644 index cd1293a..0000000 --- a/openspec/changes/add-plugin-build-metadata/specs/plugin-manifest-format/spec.md +++ /dev/null @@ -1,26 +0,0 @@ -## ADDED Requirements - -### Requirement: Plugin Build Metadata -The manifest schema SHALL support two optional display-only fields: `developer` (a -display name/identifier for the plugin's author or publisher) and `build_date` (a -timestamp, conventionally RFC 3339, for when the plugin was built). Neither field SHALL be -required, and neither SHALL affect schema/interface compatibility checks. - -#### Scenario: Manifest omits both fields -- **WHEN** a manifest has no `developer` or `build_date` field -- **THEN** parsing succeeds and the parsed manifest exposes both as absent, not as an error - -#### Scenario: Manifest declares a developer name -- **WHEN** a manifest includes a non-empty `developer` field -- **THEN** the parsed manifest exposes that value unchanged - -#### Scenario: Manifest declares a build date -- **WHEN** a manifest includes a non-empty `build_date` field -- **THEN** the parsed manifest exposes that value unchanged, without being parsed or validated - as a timestamp - -#### Scenario: Empty developer or build_date field is rejected -- **WHEN** a manifest includes a `developer` or `build_date` field present but empty (or - whitespace-only) -- **THEN** parsing fails with a structured error identifying the invalid field, the same way an - empty `network_hosts` entry is rejected diff --git a/openspec/changes/add-plugin-build-metadata/tasks.md b/openspec/changes/add-plugin-build-metadata/tasks.md deleted file mode 100644 index 078e910..0000000 --- a/openspec/changes/add-plugin-build-metadata/tasks.md +++ /dev/null @@ -1,33 +0,0 @@ -## 1. Manifest Schema - -- [x] 1.1 Add `developer: Option` and `build_date: Option` to `Manifest` in - `src/manifest.rs`, each with a doc comment noting `build_date` is conventionally RFC 3339 but - unvalidated (matching `Fixture.kickoff`'s treatment) -- [x] 1.2 Add the corresponding optional fields to `RawManifest` -- [x] 1.3 Add `ManifestField::Developer` and `ManifestField::BuildDate` variants, including their - `Display` impl arm - -## 2. Parsing and Validation - -- [x] 2.1 In `Manifest::parse`, thread both new fields through as `Option`, defaulting to - `None` when absent -- [x] 2.2 Reject a present-but-empty/whitespace-only `developer` or `build_date` with - `ManifestError::InvalidField`, reusing (or extracting into a shared helper alongside) - `network_hosts`'s existing empty-entry check - -## 3. Tests - -- [x] 3.1 Unit test: manifest omitting both fields parses successfully with both `None` -- [x] 3.2 Unit test: manifest declaring both fields parses successfully and exposes them - unchanged -- [x] 3.3 Unit test: empty `developer` field is rejected with - `ManifestField::Developer` -- [x] 3.4 Unit test: empty `build_date` field is rejected with `ManifestField::BuildDate` -- [x] 3.5 Update the crate-level doc example in `src/lib.rs` and/or `README.md` if either shows a - full manifest, so they stay accurate (additive fields, no required change, but worth checking) - -## 4. Release - -- [x] 4.1 Update `CHANGELOG.md`'s `[Unreleased]` section describing the additive manifest change -- [x] 4.2 Confirm `RELEASING.md`'s process results in a minor version bump (additive manifest - field), not a patch or major