From 4a33cf252f6d6d92785b0cc953a0d461d024ba9d Mon Sep 17 00:00:00 2001 From: SIN-Agent Date: Fri, 17 Apr 2026 06:17:44 +0200 Subject: [PATCH 1/5] feat(opencode): add sin-box-storage MCP client for Box.com cloud storage - Added sin-box-storage MCP entry to opencode.json - MCP wrapper: python3 /Users/jeremy/dev/A2A-SIN-Box-Storage/scripts/mcp-box-storage.py - Environment: BOX_STORAGE_URL, BOX_STORAGE_API_KEY - Tools: box_upload_file, box_validate_file, box_list_public_files, box_get_health Closes: Delqhi/upgraded-opencode-stack#25 --- opencode.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/opencode.json b/opencode.json index bdab11d..3806b6b 100644 --- a/opencode.json +++ b/opencode.json @@ -497,6 +497,18 @@ ], "enabled": true }, + "sin-box-storage": { + "type": "local", + "command": [ + "python3", + "/Users/jeremy/dev/A2A-SIN-Box-Storage/scripts/mcp-box-storage.py" + ], + "environment": { + "BOX_STORAGE_URL": "http://room-09-box-storage:3000", + "BOX_STORAGE_API_KEY": "${BOX_STORAGE_API_KEY}" + }, + "enabled": true + }, "webauto-nodriver": { "type": "local", "command": [ From ea41103f521abf03967b38a3ddbd0d4b3445f3b4 Mon Sep 17 00:00:00 2001 From: SIN-Agent Date: Fri, 17 Apr 2026 06:19:51 +0200 Subject: [PATCH 2/5] governance: publish canonical repo-governance, pr-watcher, and platform-registry contract templates (#26) - templates/governance/repo-governance.template.json: concrete template with fail-closed rules - templates/governance/pr-watcher.template.json: PR review automation + credential scanning - templates/governance/platform-registry.template.json: platform intake with fail-closed default - templates/governance/README.md: usage guide for factory and backfill workflows These templates complement the existing JSON Schemas (repo-governance.schema.json, etc.) and provide copy-ready instances for the factory pipeline and manual backfill. Resolves #26 --- templates/governance/README.md | 71 +++++++++++++++++++ .../platform-registry.template.json | 33 +++++++++ templates/governance/pr-watcher.template.json | 33 +++++++++ .../governance/repo-governance.template.json | 31 ++++++++ 4 files changed, 168 insertions(+) create mode 100644 templates/governance/README.md create mode 100644 templates/governance/platform-registry.template.json create mode 100644 templates/governance/pr-watcher.template.json create mode 100644 templates/governance/repo-governance.template.json diff --git a/templates/governance/README.md b/templates/governance/README.md new file mode 100644 index 0000000..3f4b33a --- /dev/null +++ b/templates/governance/README.md @@ -0,0 +1,71 @@ +# Governance Contract Templates + +> **Canonical templates for sovereign repo governance across the OpenSIN fleet.** +> Version: 2026.04.17 | Issue: upgraded-opencode-stack#26 + +## Overview + +This directory contains **concrete template instances** that any A2A repo can copy to bootstrap its governance stack. The corresponding JSON Schemas live in the parent `templates/` directory. + +## Files + +| File | Purpose | Copy To | +|------|---------|---------| +| `repo-governance.template.json` | Branch protection, merge rules, fail-closed semantics | `governance/repo-governance.json` | +| `pr-watcher.template.json` | PR review automation, credential scanning, escalation | `governance/pr-watcher.json` | +| `platform-registry.template.json` | Platform intake registry with fail-closed rules | `platforms/registry.json` | + +## Usage + +### For New Repos (via Factory) + +The factory (`create-sin-a2a-agent.mjs`) reads `Template-SIN-Agent/required-files.manifest.json` and copies these templates automatically during repo generation, substituting `{{REPO_SLUG}}` with the actual agent slug. + +### For Existing Repos (via Backfill) + +```bash +# Copy and customize for your repo +cp templates/governance/repo-governance.template.json governance/repo-governance.json +cp templates/governance/pr-watcher.template.json governance/pr-watcher.json +cp templates/governance/platform-registry.template.json platforms/registry.json + +# Replace placeholder with actual repo slug +sed -i '' 's/{{REPO_SLUG}}/your-agent-slug/g' governance/*.json platforms/*.json +``` + +### Validation + +Validate your governance files against the schemas: + +```bash +# Using ajv-cli or similar JSON Schema validator +ajv validate -s templates/repo-governance.schema.json -d governance/repo-governance.json +ajv validate -s templates/pr-watcher.schema.json -d governance/pr-watcher.json +ajv validate -s templates/platform-registry.schema.json -d platforms/registry.json +``` + +## Schema Reference + +| Schema | Location | +|--------|----------| +| `repo-governance.schema.json` | `templates/repo-governance.schema.json` | +| `pr-watcher.schema.json` | `templates/pr-watcher.schema.json` | +| `platform-registry.schema.json` | `templates/platform-registry.schema.json` | +| `work-item.schema.json` | `templates/work-item.schema.json` | + +## Template Variables + +All templates use `{{REPO_SLUG}}` as the primary substitution variable. The factory replaces this with the agent's actual slug during generation. + +## Relationship to Template-SIN-Agent + +These templates are the **source of truth** for governance file content. `Template-SIN-Agent` contains its own copies (in `governance/`, `platforms/`) but those are generated FROM these templates. When updating governance contracts, update HERE first, then propagate to Template-SIN-Agent. + +## Fail-Closed Rules + +All governance contracts follow fail-closed semantics: +- Unknown check results → **block** (not pass) +- Missing required files → **block merge** +- Credential leaks → **block merge immediately** +- Unregistered platforms → **blocked** (no implicit access) +- CI runner timeout → **block** (not skip) diff --git a/templates/governance/platform-registry.template.json b/templates/governance/platform-registry.template.json new file mode 100644 index 0000000..83153bf --- /dev/null +++ b/templates/governance/platform-registry.template.json @@ -0,0 +1,33 @@ +{ + "$comment": "Concrete template instance — copy this file to platforms/registry.json in any A2A repo and replace {{REPO_SLUG}}.", + "$schema": "https://opencode.local/templates/platform-registry.schema.json", + "version": "2026.04.17", + "platforms": [ + { + "id": "github-issues", + "name": "GitHub Issues", + "category": "issue-tracker", + "enabled": true, + "status": "active", + "intakeMode": "webhook", + "webhookPath": "/webhooks/github", + "signatureRequired": true, + "authRef": "env:GITHUB_WEBHOOK_SECRET", + "baseUrl": "https://api.github.com", + "eventTypes": ["issues.opened", "issues.edited", "issues.labeled"], + "normalizer": "n8n-workflows/inbound-intake.json", + "dedupeKeyTemplate": "github:{{REPO_SLUG}}:issue:{{externalId}}", + "defaultRepo": "OpenSIN-AI/{{REPO_SLUG}}", + "defaultLabels": ["inbound", "github"], + "watcherRequired": true, + "watcherConfigRef": "governance/pr-watcher.json", + "riskLevel": "low", + "automationPolicy": "issue_plus_pr", + "allowedActions": ["create_issue", "update_issue", "create_branch", "create_pr"], + "forbiddenActions": ["force_push", "delete_branch_main"], + "evidenceRequired": false, + "retentionDays": 365, + "notes": "Default platform for all A2A repos. GitHub Issues are the canonical intake surface." + } + ] +} diff --git a/templates/governance/pr-watcher.template.json b/templates/governance/pr-watcher.template.json new file mode 100644 index 0000000..ddd81cf --- /dev/null +++ b/templates/governance/pr-watcher.template.json @@ -0,0 +1,33 @@ +{ + "$comment": "Concrete template instance — copy this file to governance/pr-watcher.json in any A2A repo and replace {{REPO_SLUG}}.", + "$schema": "https://opencode.local/templates/pr-watcher.schema.json", + "enabled": true, + "repo": "OpenSIN-AI/{{REPO_SLUG}}", + "prSource": "all", + "ignoreAuthors": [], + "ignoreBots": false, + "noisePrefixes": ["chore(deps):", "ci:"], + "noiseSubstrings": ["bump version", "auto-generated"], + "watcherScript": "scripts/watch-pr-feedback.sh", + "followupCommand": "gh pr review --approve", + "stateDir": ".pr-watcher-state", + "summaryFile": ".pr-watcher-state/summary.json", + "logFile": ".pr-watcher-state/watcher.log", + "reviewPolicy": { + "autoReviewEnabled": true, + "autoReviewModel": "opencode run --format json", + "credentialScanEnabled": true, + "credentialPatterns": [ + "GOOGLE_API_KEY", "OPENAI_API_KEY", "A2A_FLEET_TOKEN", + "sk-", "ghp_", "gho_", "Bearer ", "password", "secret" + ], + "requiredFilesCheckEnabled": true, + "requiredFilesManifest": "required-files.manifest.json" + }, + "escalation": { + "staleAfterHours": 48, + "abandonedAfterDays": 7, + "telegramBot": "sin-telegrambot", + "telegramChannel": "fleet-alerts" + } +} diff --git a/templates/governance/repo-governance.template.json b/templates/governance/repo-governance.template.json new file mode 100644 index 0000000..97645d1 --- /dev/null +++ b/templates/governance/repo-governance.template.json @@ -0,0 +1,31 @@ +{ + "$comment": "Concrete template instance — copy this file to governance/repo-governance.json in any A2A repo and replace {{REPO_SLUG}}.", + "$schema": "https://opencode.local/templates/repo-governance.schema.json", + "repo": "{{REPO_SLUG}}", + "issueFirstRequired": true, + "prWatcherRequired": true, + "strictDispatchMatrixRequired": false, + "platformRegistryRef": "platforms/registry.json", + "prWatcherConfigRef": "governance/pr-watcher.json", + "coderDispatchMatrixRef": null, + "workItemSchemaRef": "https://opencode.local/templates/work-item.schema.json", + "defaultLabels": ["inbound", "a2a"], + "automationFlow": [ + "1. External platform sends work via webhook/poller", + "2. n8n normalizes payload to work_item schema", + "3. GitHub issue created/updated in target repo", + "4. Agent picks up issue, creates branch + PR", + "5. PR Watcher validates: required files, credential scan, build check", + "6. On approval: squash merge, auto-delete branch", + "7. Post-merge verification: build, agent card, fleet validator" + ], + "failClosedRules": [ + "Unregistered platforms are blocked — no implicit access", + "Missing webhook auth blocks intake", + "Missing critical required files blocks merge", + "Credential leak in PR diff blocks merge immediately", + "Unknown check result blocks merge (fail-closed default)", + "CI runner timeout blocks merge" + ], + "trackingIssue": null +} From a45218c27f10946e51e977a3e3e9328d0f3384ea Mon Sep 17 00:00:00 2001 From: SIN-Agent Date: Fri, 17 Apr 2026 06:30:03 +0200 Subject: [PATCH 3/5] fix(qwen-auth): prevent concurrent sessions from burning single-use refresh tokens twice --- .../opencode-qwen-auth/dist/src/plugin.js | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/local-plugins/opencode-qwen-auth/dist/src/plugin.js b/local-plugins/opencode-qwen-auth/dist/src/plugin.js index 61c6c0d..a135198 100644 --- a/local-plugins/opencode-qwen-auth/dist/src/plugin.js +++ b/local-plugins/opencode-qwen-auth/dist/src/plugin.js @@ -331,6 +331,20 @@ export const createQwenOAuthPlugin = (providerId) => async ({ client, directory proactive: config.proactive_refresh, }); try { + // Multi-session concurrency fix: Check if another session already + // refreshed this account while we were preparing to refresh. + // If the refresh token on disk has changed, we just grab the new + // tokens and retry instead of burning a single-use token twice. + const freshStorage = await loadAccounts(); + const freshAccount = freshStorage?.accounts[accountIndex]; + if (freshAccount && freshAccount.refreshToken !== selectedAuth.refresh) { + logger.debug("Another session already refreshed this token proactively. Retrying.", { + accountIndex + }); + accountStorage = freshStorage; + continue; + } + const refreshed = await refreshAccessToken(selectedAuth, oauthOptions, client, providerId); if (!refreshed) { throw new Error("Token refresh failed"); @@ -480,6 +494,24 @@ export const createQwenOAuthPlugin = (providerId) => async ({ client, directory const canForceRefresh = !!refreshKey && !forcedRefreshTokens.has(refreshKey); if (canForceRefresh) { forcedRefreshTokens.add(refreshKey); + + // Multi-session concurrency fix: Check if another session already + // refreshed this account while we were failing. If the refresh + // token on disk has changed, we just grab the new tokens and retry + // the API call instead of burning a single-use refresh token twice. + const freshStorage = await loadAccounts(); + const freshAccount = freshStorage?.accounts[accountIndex]; + if (freshAccount && freshAccount.refreshToken !== refreshKey) { + logger.debug("Another session already refreshed this token. Retrying API with new tokens.", { + accountIndex + }); + activeAuth.refresh = freshAccount.refreshToken; + activeAuth.access = freshAccount.accessToken; + activeAuth.expires = freshAccount.expires; + accountStorage = freshStorage; + continue; + } + try { const recovered = await refreshAccessToken(activeAuth, oauthOptions, client, providerId); if (recovered) { From 84e9d5c248475f8e4d03e1f8347afaca1609ca8a Mon Sep 17 00:00:00 2001 From: SIN-Agent Date: Fri, 17 Apr 2026 07:52:59 +0200 Subject: [PATCH 4/5] BUG-OCI-001: Create my-sin-coding-agents.json team config for 6 dedicated coder repos --- my-sin-coding-agents.json | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 my-sin-coding-agents.json diff --git a/my-sin-coding-agents.json b/my-sin-coding-agents.json new file mode 100644 index 0000000..c0909ad --- /dev/null +++ b/my-sin-coding-agents.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://github.com/Delqhi/upgraded-opencode-stack/blob/main/schemas/team-config.schema.json", + "team_id": "team-coding-agents", + "name": "Team Coding Agents (6 dedicated repos)", + "description": "6 dedicated A2A Coder Agents with individual GitHub repos — split from monolithic OpenSIN-Code for isolated deployments, independent versioning, and crash-storm protection. Established after BUG-OCI-001 (OCI VM disk-full incident, 2026-04-16).", + "manager": "A2A-SIN-Zeus", + "primary_model": "google/antigravity-claude-sonnet-4-6", + "fallback_models": [ + "openai/gpt-5.4", + "google/antigravity-gemini-3.1-pro", + "qwen/coder-model" + ], + "established": "2026-04-17", + "bug_references": [ + "BUG-OCI-001" + ], + "members": { + "A2A-SIN-Code-Backend": { + "github": "https://github.com/OpenSIN-AI/A2A-SIN-Code-Backend", + "port": 7863, + "purpose": "Backend specialists — Server, OracleCloud, APIs", + "specialization": "backend-api-server" + }, + "A2A-SIN-Code-Command": { + "github": "https://github.com/OpenSIN-AI/A2A-SIN-Code-Command", + "port": 7861, + "purpose": "Command/CLI agents — shell, automation, scripting", + "specialization": "cli-automation" + }, + "A2A-SIN-Code-Frontend": { + "github": "https://github.com/OpenSIN-AI/A2A-SIN-Code-Frontend", + "port": 7865, + "purpose": "Frontend specialists — UI/UX, React, CSS", + "specialization": "frontend-ui-ux" + }, + "A2A-SIN-Code-Fullstack": { + "github": "https://github.com/OpenSIN-AI/A2A-SIN-Code-Fullstack", + "port": 7864, + "purpose": "Fullstack specialists — end-to-end implementations", + "specialization": "fullstack-end-to-end" + }, + "A2A-SIN-Code-Plugin": { + "github": "https://github.com/OpenSIN-AI/A2A-SIN-Code-Plugin", + "port": 7860, + "purpose": "Plugin developers — MCPs, integrations, auth", + "specialization": "plugins-mcps-integrations" + }, + "A2A-SIN-Code-Tool": { + "github": "https://github.com/OpenSIN-AI/A2A-SIN-Code-Tool", + "port": 7862, + "purpose": "Tool builders — CLI utilities, external integrations", + "specialization": "cli-utilities-external-tools" + } + } +} \ No newline at end of file From 63c9d76ded1625d3dfeca7215432707bb6ddbbc6 Mon Sep 17 00:00:00 2001 From: SIN-Agent Date: Sat, 18 Apr 2026 02:28:03 +0200 Subject: [PATCH 5/5] fix: replace backslash with forward slash in /generate-image command (#44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Line 190 used \\generate-image instead of /generate-image, causing the opencode CLI command to fail silently. The backslash is not a valid command prefix — opencode commands use forward-slash (e.g. /generate-image). Verified: line 190 now reads /generate-image, no backslash present. --- skills/gen-thumbnail/scripts/generate_thumbnail_pipeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/gen-thumbnail/scripts/generate_thumbnail_pipeline.js b/skills/gen-thumbnail/scripts/generate_thumbnail_pipeline.js index 72f3668..34311f5 100644 --- a/skills/gen-thumbnail/scripts/generate_thumbnail_pipeline.js +++ b/skills/gen-thumbnail/scripts/generate_thumbnail_pipeline.js @@ -187,7 +187,7 @@ function latestGeneratedImage() { } function runImage(prompt, fileName) { - const message = `\\generate-image ${prompt}; file name should be ${fileName}; save it at .opencode/generated-images`; + const message = `/generate-image ${prompt}; file name should be ${fileName}; save it at .opencode/generated-images`; execFileSync("opencode", ["run", message, "--model=google/antigravity-gemini-3-flash", "--format", "json"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"]