diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9f1cc5d..a4b0076 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -10,7 +10,7 @@ body: id: version attributes: label: MARGINAL version - placeholder: "0.3.0" + placeholder: "0.3.1" validations: required: true - type: input diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab22095..a59151e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,8 @@ jobs: - run: mypy src/marginal - run: pytest -q - run: python -m build - - run: python -m twine check dist/* + - run: python -m twine check dist/*.whl dist/*.tar.gz + - run: python scripts/build_codex_plugin_submission.py --output-dir dist - name: Create release when the version is new env: GH_TOKEN: ${{ github.token }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 70793d8..50be3a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to MARGINAL are documented here. The project follows Semanti ## [Unreleased] +## [0.3.1] - 2026-08-13 + +### Added + +- production square logo, public listing metadata and three starter prompts for the universal + Plugins Directory; +- deterministic, single-root Plugins Directory ZIP with the native skill, hooks, local runtime and + assets, published alongside every release. + +### Changed + +- the submission packet now records the exact OpenAI Platform access and identity prerequisites + separately from completed package, release and public-URL gates. + ## [0.3.0] - 2026-08-13 ### Added diff --git a/CITATION.cff b/CITATION.cff index 15f9291..b8020b3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: "MARGINAL: Economically Disciplined Compute Allocation for AI Agents" type: software authors: - name: SignalLayer Labs -version: 0.3.0 -date-released: 2026-08-06 +version: 0.3.1 +date-released: 2026-08-13 license: Apache-2.0 repository-code: "https://github.com/SignalLayerLabs/Marginal" url: "https://github.com/SignalLayerLabs/Marginal" diff --git a/README.md b/README.md index 9c24648..5b73112 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ marginal install codex Current tagged library install target: ```bash -pip install "marginal-ai @ git+https://github.com/SignalLayerLabs/Marginal.git@v0.3.0" +pip install "marginal-ai @ git+https://github.com/SignalLayerLabs/Marginal.git@v0.3.1" ``` Development checkout: diff --git a/codemeta.json b/codemeta.json index 68a33c7..ec8015b 100644 --- a/codemeta.json +++ b/codemeta.json @@ -6,7 +6,7 @@ "codeRepository": "https://github.com/SignalLayerLabs/Marginal", "issueTracker": "https://github.com/SignalLayerLabs/Marginal/issues", "license": "https://spdx.org/licenses/Apache-2.0", - "version": "0.3.0", + "version": "0.3.1", "datePublished": "2026-08-06", "programmingLanguage": "Python", "runtimePlatform": "Python 3.10-3.13", diff --git a/docs/operations/codex-plugin-submission.md b/docs/operations/codex-plugin-submission.md index 4f4e893..232c3af 100644 --- a/docs/operations/codex-plugin-submission.md +++ b/docs/operations/codex-plugin-submission.md @@ -3,8 +3,10 @@ ```text status: not_submitted status_date: 2026-08-13 -plugin_version: 0.3.0 +plugin_version: 0.3.1 marketplace_selector: marginal@marginal +portal_submission_type: skills_only_zip +portal_access: authentication_required ``` ## Submission identity @@ -19,6 +21,24 @@ marketplace_selector: marginal@marginal - Authentication: none; local plugin runtime only - Network access: none - Data region: local user device +- Availability: every country or region offered by the submission portal + +## Directory listing copy + +**Short description:** Earned enforcement for agent compute. + +**Long description:** MARGINAL measures repeated Codex tool work locally, starts in Shadow Mode, +and permits repository Tool Enforcement only after a versioned evidence gate proves coverage, +reviewed false stops, and governance overhead. It never reads prompts, source, raw commands, raw +outputs, transcripts, or Codex credentials for evidence. + +**Release notes:** First universal-directory candidate. Adds MARGINAL's local-first Shadow Mode, +review workflow, Earned Enforcement gates, fail-open demotion, native Codex lifecycle package, +production logo, eight reviewer cases, and reproducible v0.3.1 archive. + +Starter prompts are the three `interface.defaultPrompt` entries in +`plugins/marginal/.codex-plugin/plugin.json`. The production logo is +`plugins/marginal/assets/marginal-logo.png`. ## Review description @@ -30,8 +50,9 @@ coverage or identity changes. ## Reviewer setup -1. Add this repository as a local marketplace. -2. Install `marginal@marginal`. +1. Add the public repository marketplace with + `codex plugin marketplace add SignalLayerLabs/Marginal --ref main`. +2. Install `marginal@marginal` with `codex plugin add marginal@marginal`. 3. Review the exact commands through `/hooks`; do not bypass hook trust. 4. Run the five positive and three negative cases in `codex-plugin-test-cases.json`. 5. Confirm Shadow Mode emits no deny, evidence contains no raw marker, demotion fails open, and @@ -46,12 +67,26 @@ coverage or identity changes. - [x] Secret-marker scan returns zero persisted occurrences. - [x] Reproducible smoke evidence is committed with runtime SHA-256 provenance. - [x] Privacy, terms, support, and eight reviewer cases exist. -- [ ] Canonical main contains the final bundle and public Pages URLs resolve. +- [x] Canonical main contains v0.3.1 after merge and every public Pages URL resolves. +- [x] A deterministic single-root ZIP builder covers the manifest, skill, hooks, runtime, and logo. - [ ] SignalLayer Labs identity and Apps Management write permission are confirmed in Platform. -- [ ] Final archive is uploaded and external review is started. +- [ ] The v0.3.1 archive is uploaded in Platform and external review is started. + +Build the exact upload artifact with: + +```bash +python scripts/build_codex_plugin_submission.py --output-dir dist +``` + +The release workflow attaches that same ZIP to the matching GitHub release. The package contains no +MCP or app configuration, so the portal submission type is **Skills only**. The archive also carries +the native Codex hook runtime tested by the public marketplace path; OpenAI's ingestion and review +remain authoritative for the components exposed by the universal directory. -The status above changes only after the external portal accepts the final submission. No external -identifier, credential, or reviewer correspondence belongs in this repository. +The status above changes only after the external portal accepts the final submission. Creating or +submitting a draft requires an authenticated Platform session, Apps Management write access, and a +verified SignalLayer Labs identity; none can be inferred from repository or GitHub credentials. No +external identifier, credential, or reviewer correspondence belongs in this repository. Acceptance evidence: -[`codex-plugin-smoke-2026-08-13.json`](evidence/codex-plugin-smoke-2026-08-13.json). +[`codex-plugin-smoke-2026-08-13-v0.3.1.json`](evidence/codex-plugin-smoke-2026-08-13-v0.3.1.json). diff --git a/docs/operations/codex-plugin-test-cases.json b/docs/operations/codex-plugin-test-cases.json index 47bd0ef..65246d7 100644 --- a/docs/operations/codex-plugin-test-cases.json +++ b/docs/operations/codex-plugin-test-cases.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "plugin_version": "0.3.0", + "plugin_version": "0.3.1", "positive": [ { "id": "positive-shadow-status", @@ -46,4 +46,3 @@ } ] } - diff --git a/docs/operations/evidence/codex-plugin-smoke-2026-08-13-v0.3.1.json b/docs/operations/evidence/codex-plugin-smoke-2026-08-13-v0.3.1.json new file mode 100644 index 0000000..c07433f --- /dev/null +++ b/docs/operations/evidence/codex-plugin-smoke-2026-08-13-v0.3.1.json @@ -0,0 +1,14 @@ +{ + "codex_version": "codex-cli 0.147.0", + "completed_sessions": 1, + "directory_archive_sha256": "f43231057fab50a35fed14a55f95a2452e0c0c64f3b9b1f0a1a602e24af19582", + "evidence_records": 4, + "hook_coverage": 1.0, + "installed": true, + "marketplace_selector": "marginal@marginal", + "plugin_version": "0.3.1", + "raw_secret_occurrences": 0, + "removed": true, + "runtime_sha256": "98dc5a49e77864fd529c647cc5e81181b9f5ebda234561135a80d4a05823f06d", + "shadow_block_count": 0 +} diff --git a/plugins/marginal/.codex-plugin/plugin.json b/plugins/marginal/.codex-plugin/plugin.json index b5b6a66..8eeb77a 100644 --- a/plugins/marginal/.codex-plugin/plugin.json +++ b/plugins/marginal/.codex-plugin/plugin.json @@ -1,11 +1,20 @@ { "name": "marginal", - "version": "0.3.0", + "version": "0.3.1", "description": "Evidence-gated compute governance for Codex, local-first and Shadow Mode by default.", "author": { "name": "SignalLayer Labs", "url": "https://github.com/SignalLayerLabs/Marginal" }, + "homepage": "https://signallayerlabs.github.io/Marginal/", + "repository": "https://github.com/SignalLayerLabs/Marginal", + "license": "Apache-2.0", + "keywords": [ + "agent-compute", + "compute-governance", + "codex", + "token-efficiency" + ], "skills": "./skills/", "interface": { "displayName": "Marginal", @@ -14,6 +23,16 @@ "developerName": "SignalLayer Labs", "category": "Productivity", "capabilities": [], - "defaultPrompt": "Use $marginal to inspect this repository's Shadow Mode evidence and explain whether Tool Enforcement is ready." + "websiteURL": "https://signallayerlabs.github.io/Marginal/", + "privacyPolicyURL": "https://signallayerlabs.github.io/Marginal/privacy.html", + "termsOfServiceURL": "https://signallayerlabs.github.io/Marginal/terms.html", + "defaultPrompt": [ + "Use $marginal to inspect this repository's Shadow Mode evidence and explain whether Tool Enforcement is ready.", + "Use $marginal to diagnose this Codex installation without reading credentials or transcripts.", + "Use $marginal to review repeated-work candidates and explain which evidence gates remain." + ], + "brandColor": "#22D3EE", + "composerIcon": "./assets/marginal-logo.png", + "logo": "./assets/marginal-logo.png" } } diff --git a/plugins/marginal/assets/marginal-logo.png b/plugins/marginal/assets/marginal-logo.png new file mode 100644 index 0000000..f9d0174 Binary files /dev/null and b/plugins/marginal/assets/marginal-logo.png differ diff --git a/plugins/marginal/runtime/marginal_runtime.pyz b/plugins/marginal/runtime/marginal_runtime.pyz index acff7a2..3e065f2 100644 Binary files a/plugins/marginal/runtime/marginal_runtime.pyz and b/plugins/marginal/runtime/marginal_runtime.pyz differ diff --git a/plugins/marginal/runtime/provenance.json b/plugins/marginal/runtime/provenance.json index 662644f..61410dd 100644 --- a/plugins/marginal/runtime/provenance.json +++ b/plugins/marginal/runtime/provenance.json @@ -1 +1 @@ -{"builder":"scripts/build_codex_plugin.py","python_requires":">=3.10","schema_version":1,"sha256":"f148d51c8e00323637225479a2b26b75ea79255ae850ba3e3df0c65e41ecc630","source_hash":"908d1090899c672c8a19bf55268e9583ca3c3486b1f4b9dbc58815bf0027fa63"} +{"builder":"scripts/build_codex_plugin.py","python_requires":">=3.10","schema_version":1,"sha256":"98dc5a49e77864fd529c647cc5e81181b9f5ebda234561135a80d4a05823f06d","source_hash":"c0fd650bfd08f8e8550cb88fb1d5b1649203bd44367dcd450f78c9928b8ce6dd"} diff --git a/pyproject.toml b/pyproject.toml index b97bcbe..471b731 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "marginal-ai" -version = "0.3.0" +version = "0.3.1" description = "Universal learning-loop and compute-governance foundation for economically disciplined AI agents." readme = "README.md" requires-python = ">=3.10" diff --git a/scripts/build_codex_plugin_submission.py b/scripts/build_codex_plugin_submission.py new file mode 100644 index 0000000..48f8cf9 --- /dev/null +++ b/scripts/build_codex_plugin_submission.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Build the deterministic ZIP uploaded to the OpenAI Plugins Directory.""" + +from __future__ import annotations + +import argparse +import json +import stat +import zipfile +from pathlib import Path + +REPRODUCIBLE_TIMESTAMP = (1980, 1, 1, 0, 0, 0) +EXCLUDED_PARTS = {"__pycache__", ".DS_Store"} + + +def _included_files(plugin_root: Path) -> list[Path]: + files: list[Path] = [] + for path in plugin_root.rglob("*"): + relative = path.relative_to(plugin_root) + if any(part in EXCLUDED_PARTS for part in relative.parts): + continue + if path.is_symlink(): + raise ValueError(f"submission archive cannot contain symlinks: {relative}") + if path.is_file() and path.suffix != ".pyc": + files.append(path) + return sorted(files, key=lambda path: path.relative_to(plugin_root).as_posix()) + + +def build_submission_archive(repo: Path, *, output_dir: Path) -> Path: + """Package one plugin root at the archive root using stable metadata.""" + + repo = repo.resolve() + plugin_root = repo / "plugins" / "marginal" + manifest = json.loads( + (plugin_root / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8") + ) + version = manifest["version"] + output_dir.mkdir(parents=True, exist_ok=True) + archive_path = output_dir / f"marginal-plugin-directory-{version}.zip" + + with zipfile.ZipFile( + archive_path, + "w", + compression=zipfile.ZIP_DEFLATED, + compresslevel=9, + ) as archive: + for path in _included_files(plugin_root): + relative = path.relative_to(plugin_root).as_posix() + info = zipfile.ZipInfo(relative, date_time=REPRODUCIBLE_TIMESTAMP) + info.compress_type = zipfile.ZIP_DEFLATED + info.create_system = 3 + mode = 0o755 if path.stat().st_mode & stat.S_IXUSR else 0o644 + info.external_attr = (stat.S_IFREG | mode) << 16 + archive.writestr(info, path.read_bytes(), compresslevel=9) + + return archive_path + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Build the deterministic MARGINAL Plugins Directory ZIP." + ) + parser.add_argument( + "--output-dir", + type=Path, + default=Path("dist"), + help="Archive destination (default: dist)", + ) + args = parser.parse_args() + repo = Path(__file__).resolve().parents[1] + print(build_submission_archive(repo, output_dir=args.output_dir)) + + +if __name__ == "__main__": + main() diff --git a/src/marginal/__init__.py b/src/marginal/__init__.py index f9fe4f1..904dc46 100644 --- a/src/marginal/__init__.py +++ b/src/marginal/__init__.py @@ -137,4 +137,4 @@ "validate_safe_telemetry_record", ] -__version__ = "0.3.0" +__version__ = "0.3.1" diff --git a/src/marginal/integrations/codex/identity.py b/src/marginal/integrations/codex/identity.py index 9fd27be..1118c68 100644 --- a/src/marginal/integrations/codex/identity.py +++ b/src/marginal/integrations/codex/identity.py @@ -10,7 +10,7 @@ from .installer import CommandRunner, SubprocessRunner, inspect_codex from .promotion import PromotionIdentity -PLUGIN_VERSION = "0.3.0" +PLUGIN_VERSION = "0.3.1" ADAPTER_VERSION = "1" POLICY_HASH = hashlib.sha256(b"marginal:no-progress:v1:max-same-evidence=2").hexdigest() DEFAULT_HOOK_HASH = "46b7a85a3a542957d055c615ab501f9fee284bb3193ac9ecfbe8951cce5a9942" diff --git a/tests/plugin/test_codex_plugin.py b/tests/plugin/test_codex_plugin.py index 4997332..e791a25 100644 --- a/tests/plugin/test_codex_plugin.py +++ b/tests/plugin/test_codex_plugin.py @@ -4,10 +4,12 @@ import json import subprocess import sys +import zipfile from pathlib import Path import pytest from scripts.build_codex_plugin import build_plugin_runtime +from scripts.build_codex_plugin_submission import build_submission_archive REPO = Path(__file__).resolve().parents[2] PLUGIN = REPO / "plugins" / "marginal" @@ -45,6 +47,54 @@ def test_manifest_is_validator_clean() -> None: assert completed.returncode == 0, completed.stdout + completed.stderr +def test_manifest_has_directory_publication_metadata() -> None: + manifest = json.loads((PLUGIN / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8")) + + assert manifest["homepage"] == "https://signallayerlabs.github.io/Marginal/" + assert manifest["repository"] == "https://github.com/SignalLayerLabs/Marginal" + assert manifest["license"] == "Apache-2.0" + assert {"compute-governance", "codex", "token-efficiency"} <= set(manifest["keywords"]) + + interface = manifest["interface"] + assert interface["websiteURL"] == manifest["homepage"] + assert interface["privacyPolicyURL"].endswith("/privacy.html") + assert interface["termsOfServiceURL"].endswith("/terms.html") + assert interface["brandColor"] == "#22D3EE" + assert interface["composerIcon"] == "./assets/marginal-logo.png" + assert interface["logo"] == "./assets/marginal-logo.png" + assert 1 <= len(interface["defaultPrompt"]) <= 3 + assert all(len(prompt) <= 128 for prompt in interface["defaultPrompt"]) + + +def test_directory_logo_is_square_png() -> None: + logo = PLUGIN / "assets" / "marginal-logo.png" + data = logo.read_bytes() + + assert data[:8] == b"\x89PNG\r\n\x1a\n" + width = int.from_bytes(data[16:20], "big") + height = int.from_bytes(data[20:24], "big") + assert width == height + assert width >= 512 + + +def test_directory_submission_archive_is_reproducible_and_complete(tmp_path: Path) -> None: + first = build_submission_archive(REPO, output_dir=tmp_path / "first") + second = build_submission_archive(REPO, output_dir=tmp_path / "second") + + assert _sha256(first) == _sha256(second) + with zipfile.ZipFile(first) as archive: + names = set(archive.namelist()) + assert { + ".codex-plugin/plugin.json", + "skills/marginal/SKILL.md", + "skills/marginal/agents/openai.yaml", + "hooks/hooks.json", + "runtime/marginal_runtime.pyz", + "assets/marginal-logo.png", + } <= names + assert not any("__pycache__" in name or name.endswith(".pyc") for name in names) + + def test_hooks_cover_exact_supported_lifecycle() -> None: hooks = json.loads((PLUGIN / "hooks" / "hooks.json").read_text(encoding="utf-8")) diff --git a/tests/test_public_api_v2.py b/tests/test_public_api_v2.py index 27f7dd6..4166e25 100644 --- a/tests/test_public_api_v2.py +++ b/tests/test_public_api_v2.py @@ -23,7 +23,7 @@ def test_v02_public_exports_and_version() -> None: "ValueEstimate", } assert expected.issubset(set(marginal.__all__)) - assert marginal.__version__ == "0.3.0" + assert marginal.__version__ == "0.3.1" def test_json_schemas_exist_and_are_valid() -> None: diff --git a/tests/test_repository_consistency_v2.py b/tests/test_repository_consistency_v2.py index b465423..d1ea34b 100644 --- a/tests/test_repository_consistency_v2.py +++ b/tests/test_repository_consistency_v2.py @@ -20,7 +20,7 @@ def test_public_repository_identity_is_consistent() -> None: assert "SignalLayer Labs" in text or "SignalLayerLabs" in text, path codemeta = json.loads((ROOT / "codemeta.json").read_text(encoding="utf-8")) - assert codemeta["version"] == "0.3.0" + assert codemeta["version"] == "0.3.1" assert codemeta["codeRepository"] == "https://github.com/SignalLayerLabs/Marginal" assert codemeta["issueTracker"] == "https://github.com/SignalLayerLabs/Marginal/issues" assert codemeta["author"]["name"] == "SignalLayer Labs"