Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/package-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ Initial permissions:

Use the narrowest permission set that fits the package.

## Package settings

Packages can declare user-configurable settings in the manifest. Firmware stores values outside the package archive in
`/.marginalia/package-state/<package-id>.json`, so upgrades can replace package files without resetting user choices.

Supported setting types in manifest v1:

- `boolean`: toggled on the package settings screen
- `enum`: cycles through a fixed list of string options

Example:

```json
{
"settings": [
{
"id": "invertScreen",
"label": "Invert screen",
"type": "boolean",
"default": true
}
]
}
```

Setting ids must be stable. If a package removes or renames a setting, firmware may preserve the old value in package
state, but runtime hosts should only read setting ids they understand.

## Lifecycle entrypoints

Package entrypoints are declared by string name in the manifest and mapped by the firmware host.
Expand Down
63 changes: 63 additions & 0 deletions schema/manifest.v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,69 @@
"enum": ["display", "input", "storage", "network", "reader_state", "sleep_state", "settings"]
}
},
"settings": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": ["id", "label", "type"],
"properties": {
Comment on lines +102 to +103

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the schema file
fd -t f "manifest.v1.schema.json" | head -1

Repository: marginalia-os/marginalia-sdk

Length of output: 103


🏁 Script executed:

# Read the schema file to understand its structure and size
wc -l schema/manifest.v1.schema.json

Repository: marginalia-os/marginalia-sdk

Length of output: 107


🏁 Script executed:

# Read the relevant section (lines 90-160 to get full context)
sed -n '90,160p' schema/manifest.v1.schema.json | cat -n

Repository: marginalia-os/marginalia-sdk

Length of output: 2484


🏁 Script executed:

# Check if validate_manifest.py exists
fd -t f "validate_manifest.py"

Repository: marginalia-os/marginalia-sdk

Length of output: 99


🏁 Script executed:

# Check the validate_manifest.py file size and read it
wc -l tools/validate_manifest.py

Repository: marginalia-os/marginalia-sdk

Length of output: 103


🏁 Script executed:

# Read the validation file to understand current enforcement
cat -n tools/validate_manifest.py

Repository: marginalia-os/marginalia-sdk

Length of output: 6029


Require setting defaults and validate enum defaults against declared options.

default is currently optional for both setting variants, and enum default is only constrained as a non-empty string. The validation tool performs only schema-level checks with no custom logic to enforce or verify these constraints. This allows manifests with missing or invalid initial values to pass validation.

Add default to the required array for both variants. For enum settings, validate that the default value is present in the options array (either through JSON Schema constraints or custom validation in tools/validate_manifest.py).

Suggested schema changes
           {
             "type": "object",
             "additionalProperties": false,
-            "required": ["id", "label", "type"],
+            "required": ["id", "label", "type", "default"],
             "properties": {
               "id": {
                 "type": "string",
                 "maxLength": 48,
                 "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
           {
             "type": "object",
             "additionalProperties": false,
-            "required": ["id", "label", "type", "options"],
+            "required": ["id", "label", "type", "options", "default"],
             "properties": {
               "id": {
                 "type": "string",
                 "maxLength": 48,
                 "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schema/manifest.v1.schema.json` around lines 102 - 103, Update the manifest
schema so both setting variants include "default" in their "required" arrays
(i.e., add "default" alongside "id","label","type") and enforce for enum
settings that the declared default appears in the "options" list; implement this
either by adding a JSON Schema constraint that validates the enum default
against the "options" array or by adding a check in tools/validate_manifest.py
that verifies for each enum-type setting the default value is present in its
options array. Ensure you reference the manifest schema's "required" array, the
"default" property, and the enum "options" property when making the changes.

"id": {
"type": "string",
"maxLength": 48,
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
},
"label": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string",
"const": "boolean"
},
"default": {
"type": "boolean"
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["id", "label", "type", "options"],
"properties": {
"id": {
"type": "string",
"maxLength": 48,
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
},
"label": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string",
"const": "enum"
},
"options": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1,
"uniqueItems": true
},
"default": {
"type": "string",
"minLength": 1
}
}
}
]
}
},
"dependencies": {
"type": "array",
"items": {
Expand Down