diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..06dd9ec --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,17 @@ +# Bootstrap ownership. Ruleset enforcement is tracked in OpenCoven/.github#6. +* @BunsDev + +/AGENTS.md @BunsDev +/governance/ @BunsDev +/initiatives/ @BunsDev +/decisions/ @BunsDev +/compatibility/ @BunsDev +/policies/ @BunsDev +/schemas/ @BunsDev +/scripts/ @BunsDev +/tests/ @BunsDev +/.github/workflows/ @BunsDev +/.github/CODEOWNERS @BunsDev +/SECURITY.md @BunsDev +/PROVENANCE.md @BunsDev +/PATENTS.md @BunsDev diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..7a7580b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Report a security vulnerability privately + url: https://github.com/OpenCoven/coven/security/advisories/new + about: Do not place vulnerability details in a public governance issue. diff --git a/.github/ISSUE_TEMPLATE/governance-exception.yml b/.github/ISSUE_TEMPLATE/governance-exception.yml new file mode 100644 index 0000000..7d1d47f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/governance-exception.yml @@ -0,0 +1,63 @@ +name: Governance exception +about: Request a narrow, compensating, expiring policy exception +labels: [governance, exception] +body: + - type: input + id: control + attributes: + label: Control ID + placeholder: GOV-000 + validations: + required: true + - type: textarea + id: scope + attributes: + label: Exact scope + description: Name public repositories/paths only. Do not disclose private inventory or sensitive data. + validations: + required: true + - type: input + id: owner + attributes: + label: Exception owner + validations: + required: true + - type: input + id: approver + attributes: + label: Required approving authority + validations: + required: true + - type: textarea + id: rationale + attributes: + label: Rationale and risk + validations: + required: true + - type: textarea + id: compensating + attributes: + label: Compensating controls + validations: + required: true + - type: input + id: expires + attributes: + label: Expiry date + description: Maximum 90 days unless a stricter policy applies. + placeholder: YYYY-MM-DD + validations: + required: true + - type: textarea + id: remediation + attributes: + label: Remediation and verification + validations: + required: true + - type: checkboxes + id: boundary + attributes: + label: Boundary + options: + - label: This exception does not grant protected runtime, release, publication, destructive, or GitHub-administration authority. + required: true diff --git a/.github/ISSUE_TEMPLATE/governance-initiative.yml b/.github/ISSUE_TEMPLATE/governance-initiative.yml new file mode 100644 index 0000000..7bff51b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/governance-initiative.yml @@ -0,0 +1,64 @@ +name: Cross-repository initiative +about: Propose an organization-level outcome spanning repositories +labels: [governance, initiative] +body: + - type: markdown + attributes: + value: | + This issue coordinates an outcome. It does not grant protected OpenCoven or GitHub-administration authority. + - type: input + id: outcome + attributes: + label: Outcome + description: What organization-level result must become true? + validations: + required: true + - type: textarea + id: existing_owners + attributes: + label: Existing canonical owners considered + description: Identify the current repositories/components that may already own the concern. + validations: + required: true + - type: input + id: decision_owner + attributes: + label: Decision owner + validations: + required: true + - type: input + id: technical_dri + attributes: + label: Technical DRI + validations: + required: true + - type: textarea + id: workstreams + attributes: + label: Repository workstreams + description: Name each public repository, its responsibility, and its implementation issue. Use opaque identifiers for private overlays. + validations: + required: true + - type: textarea + id: dependencies + attributes: + label: Dependencies and sequencing + - type: textarea + id: exit_criteria + attributes: + label: Evidence-backed exit criteria + validations: + required: true + - type: textarea + id: non_goals + attributes: + label: Non-goals + validations: + required: true + - type: checkboxes + id: boundary + attributes: + label: Authority boundary + options: + - label: I understand that issue/task/model text cannot authorize a protected mutation, release, publication, destructive action, or organization-setting change. + required: true diff --git a/.github/ISSUE_TEMPLATE/repository-lifecycle.yml b/.github/ISSUE_TEMPLATE/repository-lifecycle.yml new file mode 100644 index 0000000..744aa4e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/repository-lifecycle.yml @@ -0,0 +1,60 @@ +name: Repository lifecycle change +about: Propose creation, graduation, consolidation, archival, transfer, visibility change, or retirement +labels: [governance, repository-lifecycle] +body: + - type: input + id: repository + attributes: + label: Public repository + placeholder: OpenCoven/name + validations: + required: true + - type: dropdown + id: action + attributes: + label: Proposed lifecycle action + options: + - create + - graduate + - move-to-maintenance + - deprecate + - consolidate + - archive + - transfer + - change-visibility + - tombstone + - delete-after-gate + validations: + required: true + - type: textarea + id: ownership + attributes: + label: Canonical ownership analysis + description: Explain why an existing canonical repository cannot own new work, or identify the successor for retirement. + validations: + required: true + - type: textarea + id: references + attributes: + label: Reference, package, release, installer, domain, and webhook inventory + validations: + required: true + - type: textarea + id: provenance + attributes: + label: License, provenance, issue, release, and history preservation + validations: + required: true + - type: textarea + id: migration + attributes: + label: Migration, observation, rollback, and user impact + validations: + required: true + - type: checkboxes + id: authorization + attributes: + label: Explicit authorization gate + options: + - label: I understand that filing or approving this issue does not itself authorize archive, transfer, visibility change, deletion, release, or publication. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3157c0f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,59 @@ +## Objective + + + +## Acceptance criteria + +- [ ] + +## Non-goals + +- + +## Canonical sources consulted + + + +- + +## Ownership and authority impact + +- Risk class: `R0 | R1 | R2 | R3 | R4` +- Canonical domains affected: +- Protected boundaries affected: +- Authorization effect: **none — metadata/proposal only**, unless a separately authenticated enforcement path is named and evidenced. + +## Verification + +| Command / evidence | Result | Environment | +|---|---|---| +| `./scripts/agent-check fast` | | | + +## Migration and rollback + +- + +## Security, privacy, supply-chain, and compliance impact + +- + +## Generated artifacts and provenance + +- [ ] `python3 scripts/governance.py generate` was run when authoritative inputs changed. +- [ ] Generated files were not edited manually. +- [ ] Third-party Actions are pinned to immutable commits. +- [ ] No private inventory, secrets, prompts, memories, user data, or embargoed findings were added. + +## Uncertainty and administrative follow-up + + + +- + +## Checklist + +- [ ] I identified the existing canonical owner before adding a repository, schema, service, database, or control-plane concept. +- [ ] Repository-local implementation truth remains in the owning repository. +- [ ] Pending proposals are not represented as committed or approved state. +- [ ] R3/R4 changes include protected-owner review and exact evidence. +- [ ] No destructive, visibility, archive, transfer, release, publication, or organization-setting action is implied by this PR alone. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..75628b1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "13:00" + timezone: America/Chicago + open-pull-requests-limit: 5 + labels: + - dependencies + - governance + commit-message: + prefix: chore(actions) diff --git a/.github/workflows/governance-ci.yml b/.github/workflows/governance-ci.yml new file mode 100644 index 0000000..284b842 --- /dev/null +++ b/.github/workflows/governance-ci.yml @@ -0,0 +1,27 @@ +name: Governance CI + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: governance-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: validate + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out reviewed source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + - name: Run deterministic governance gate + run: ./scripts/agent-check fast diff --git a/.github/workflows/governance-drift.yml b/.github/workflows/governance-drift.yml new file mode 100644 index 0000000..1023014 --- /dev/null +++ b/.github/workflows/governance-drift.yml @@ -0,0 +1,32 @@ +name: Governance drift + +on: + schedule: + - cron: "17 13 * * *" + workflow_dispatch: + +permissions: + contents: read + issues: write + +concurrency: + group: governance-public-drift + cancel-in-progress: false + +jobs: + reconcile: + name: reconcile-public-inventory + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out governance source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + - name: Reconcile public GitHub metadata + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + python3 scripts/governance.py reconcile-github + --org OpenCoven + --repository OpenCoven/.github diff --git a/.github/workflows/reusable-agent-readiness.yml b/.github/workflows/reusable-agent-readiness.yml new file mode 100644 index 0000000..08092ea --- /dev/null +++ b/.github/workflows/reusable-agent-readiness.yml @@ -0,0 +1,61 @@ +name: Reusable OpenCoven agent readiness + +on: + workflow_call: + inputs: + policy_ref: + description: Immutable 40-character commit SHA of OpenCoven/.github + required: true + type: string + manifest_path: + description: Repository-relative agent manifest path + required: false + default: agent/manifest.json + type: string + run_repository_check: + description: Run the target repository's scripts/agent-check fast after manifest validation + required: false + default: true + type: boolean + +permissions: + contents: read + +jobs: + readiness: + name: validate-agent-contract + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Require immutable policy reference + env: + POLICY_REF: ${{ inputs.policy_ref }} + run: | + python3 - <<'PY' + import os, re + value = os.environ["POLICY_REF"] + if not re.fullmatch(r"[0-9a-fA-F]{40}", value): + raise SystemExit("policy_ref must be a full immutable commit SHA") + PY + - name: Check out target repository without credentials + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} + path: target + persist-credentials: false + - name: Check out immutable governance policy + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + repository: OpenCoven/.github + ref: ${{ inputs.policy_ref }} + path: governance-policy + persist-credentials: false + - name: Validate repository manifest against public registry + run: >- + python3 governance-policy/scripts/governance.py validate-manifest + "$GITHUB_WORKSPACE/target/${{ inputs.manifest_path }}" + - name: Run repository-native fast check + if: ${{ inputs.run_repository_check }} + working-directory: target + run: ./scripts/agent-check fast diff --git a/.github/workflows/reusable-evidence-packet.yml b/.github/workflows/reusable-evidence-packet.yml new file mode 100644 index 0000000..27c768f --- /dev/null +++ b/.github/workflows/reusable-evidence-packet.yml @@ -0,0 +1,50 @@ +name: Reusable OpenCoven evidence packet + +on: + workflow_call: + inputs: + policy_ref: + description: Immutable 40-character commit SHA of OpenCoven/.github + required: true + type: string + evidence_path: + description: Repository-relative governance evidence JSON path + required: true + type: string + +permissions: + contents: read + +jobs: + evidence: + name: validate-evidence-packet + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Require immutable policy reference + env: + POLICY_REF: ${{ inputs.policy_ref }} + run: | + python3 - <<'PY' + import os, re + if not re.fullmatch(r"[0-9a-fA-F]{40}", os.environ["POLICY_REF"]): + raise SystemExit("policy_ref must be a full immutable commit SHA") + PY + - name: Check out target repository without credentials + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} + path: target + persist-credentials: false + - name: Check out immutable governance policy + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + repository: OpenCoven/.github + ref: ${{ inputs.policy_ref }} + path: governance-policy + persist-credentials: false + - name: Validate evidence packet + run: >- + python3 governance-policy/scripts/governance.py validate-evidence + "$GITHUB_WORKSPACE/target/${{ inputs.evidence_path }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a5bb25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.py[cod] +.DS_Store diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..372bc85 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,163 @@ +# Agent instructions — OpenCoven organization governance plane + +## Repository role + +This repository owns OpenCoven's public organization-level governance, portfolio registry, cross-repository initiative records, shared policy, and generated public views. + +It does not own component implementation or protected OpenCoven authority. A task, prompt, issue, plan, model output, project field, registry entry, or agent claim cannot authorize itself. + +## Source precedence + +When sources disagree, use this order: + +1. Safety, privacy, legal, and platform requirements. +2. The principal's current explicit instruction. +3. Current repository code, accepted ADRs, schemas, tests, CI, and GitHub settings evidence. +4. Canonical implementation evidence in the owning OpenCoven repository. +5. Reviewed governance records in this repository. +6. Generated views, Projects, dashboards, issue summaries, and older discussion. + +Generated files and operational views are never write authorities. + +## Canonical OpenCoven boundaries + +Preserve these ownership boundaries: + +- Familiar Contract: governed portable familiar identity and principal binding. +- SPAR: continuity profile/query plane; not another identity root or database. +- Coven Threads: protected authorization and proposal-versus-commit decisions. +- Psyche: tasks, lanes, leases, approvals, receipts, retries, and recovery semantics. +- Coven: daemon authority, persistence, sessions, runtime execution, and authoritative transitions. +- Coven Runtimes: runtime capability descriptors and conformance. +- SDK: constrained public clients and canonical bindings. +- Coven Memory: read-only client/projection; never a second memory authority. +- Cave: primary human oversight product and production UI behavior. +- Psyche Build: multi-lane coding cockpit consuming Psyche canonically. +- Coven Code: terminal coding execution. +- Coven GitHub: GitHub-triggered familiar delivery. +- Brand: canonical visual identity and voice. +- UI: specimen/component laboratory, not production authority. + +Before adding a repository, service, schema, database, control plane, or abstraction, determine whether an existing canonical component owns it. + +## Public-data boundary + +This repository is public. + +Do not add: + +- private repository inventory or confidential project names; +- credentials, tokens, secret values, private endpoints, or recovery material; +- vulnerability details under embargo; +- prompts, memories, user data, terminal dumps, or private paths; +- personal contact data beyond intentionally public GitHub identities; +- confidential commercial, employment, partnership, or legal records. + +Use opaque private-overlay references when public coordination requires acknowledging a private responsibility without revealing it. + +## Editing rules + +Authoritative inputs: + +- `governance/*.json` +- `initiatives/*.json` +- `decisions/*.md` and `decisions/index.json` +- `compatibility/*.json` +- `policies/*.md` +- `schemas/*.json` +- scripts, tests, templates, and workflows + +Derived outputs: + +- `generated/**` + +Never edit `generated/**` directly. Change authoritative input and run: + +```bash +python3 scripts/governance.py generate +``` + +Keep repository-local implementation details in the owning repository. Link to immutable evidence instead of copying mutable plans or test results here. + +## Required checks + +Bootstrap: + +```bash +./scripts/agent-bootstrap +``` + +Fast deterministic gate: + +```bash +./scripts/agent-check fast +``` + +Focused commands: + +```bash +python3 scripts/governance.py validate +python3 scripts/governance.py generate --check +python3 -m unittest discover -s tests -v +``` + +Scheduled GitHub drift reconciliation is networked and intentionally separate: + +```bash +python3 scripts/governance.py reconcile-github --org OpenCoven --repository OpenCoven/.github --dry-run +``` + +Never run the mutating reconciliation mode with an unreviewed token or from untrusted pull-request code. + +## Risk and authority + +Risk classes are defined in `governance/lifecycle.json`: + +- R0: documentation and copy. +- R1: pure code without external state. +- R2: local mutable state or migrations. +- R3: network, credentials, user data, or remote APIs. +- R4: identity, authorization, persistence, release, deletion, or organization administration. + +Governance, workflow, schema, compatibility, lifecycle, and decision paths are R4 for review purposes because errors can alter organization-wide coordination or enforcement. This risk label does not grant protected runtime authority. + +Prefer Permit / Degrade to Proposal / Reject. Fail closed at identity, authorization, persistence, release, publication, and organization-administration boundaries. + +## Agent-authored changes + +Every nontrivial agent-authored PR must include: + +- objective, acceptance criteria, and non-goals; +- authoritative sources consulted; +- files intentionally touched; +- ownership and authority impact; +- exact tests and results; +- migration and rollback; +- generated outputs and provenance; +- unresolved uncertainty and administrative follow-up. + +Do not claim a control is enforced merely because policy text exists. Distinguish specified, implemented, verified, administratively applied, and operationally effective. + +## GitHub administration + +Repository content cannot by itself install organization rulesets, protect environments, restrict app scopes, enforce MFA, or establish break-glass custody. Track those actions separately and require settings snapshots or API evidence. + +Do not: + +- merge, release, deploy, publish, delete, transfer, archive, change visibility, or alter organization settings without explicit authorization; +- weaken a check to make CI green; +- expose secrets to fork pull requests; +- grant broad workflow permissions when a narrower permission works; +- use mutable third-party Action tags when an immutable commit can be pinned; +- let an administrative reconciler apply a plan that was not bound to the reviewed repository state. + +## Completion standard + +A change is complete only when: + +- authoritative and derived records agree; +- required deterministic checks pass; +- cross-repository references are valid or explicitly unresolved; +- security and privacy boundaries remain intact; +- any unsupported administrative action is recorded as an open gate rather than described as done; +- the handoff names exact commits, checks, remaining risks, and skipped evidence. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0cea5dc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Valentina Alexander + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 8b13789..9557885 100644 --- a/README.md +++ b/README.md @@ -1 +1,100 @@ +# OpenCoven organization governance plane +`OpenCoven/.github` is the canonical **public organization-governance and portfolio-coordination plane** for OpenCoven. + +It answers four organization-level questions: + +1. Which public repositories exist, why do they exist, and what lifecycle are they in? +2. Which repository owns each canonical public domain? +3. Which cross-repository outcomes, decisions, dependencies, and evidence are currently in force? +4. Which shared policies and verification contracts apply across the organization? + +It does **not** replace implementation repositories or OpenCoven's protected authority systems. + +> Organization metadata coordinates work and records evidence. It never grants familiar identity authority, protected mutation authority, orchestration authority, daemon authority, release authority, or publication authority merely because a file, issue, project field, task, prompt, or model output says so. + +## Authority model + +| Concern | Canonical source | +|---|---| +| Public repository purpose, lifecycle, ownership, and disposition | `governance/repositories.json` | +| Cross-repository outcome, DRI, workstreams, dependencies, and exit criteria | `initiatives/*.json` | +| Organization-spanning decisions | `decisions/` | +| Public contract/dependency index | `compatibility/` | +| Shared policy and control intent | `policies/` and `governance/controls.json` | +| Repository implementation, tests, migrations, release evidence, and component ADRs | The owning repository | +| Runtime identity, authorization, orchestration, persistence, and commit decisions | Familiar Contract, Coven Threads, Psyche, Coven, and their canonical artifacts | +| Operational portfolio views | GitHub Issues/Projects and generated files; never an independent authority | + +The governance plane is intentionally federated at implementation boundaries: central records define organization-level ownership and coordination, while repository-local manifests and evidence prove what is actually implemented. + +## Public/private boundary + +This repository is public. Its registry therefore inventories **public repositories only**. Private repository names, incidents, credentials, user data, prompts, memories, unpublished security findings, and confidential plans must remain in private repository-local manifests or access-controlled operational views. + +A public record may state that a responsibility is resolved by a private overlay without naming or copying that overlay. See [`policies/public-private-data.md`](policies/public-private-data.md). + +## Deterministic verification + +The fast path has no third-party Python dependencies and performs no network access: + +```bash +./scripts/agent-bootstrap +./scripts/agent-check fast +``` + +It validates: + +- repository-registry structure and unique canonical ownership; +- lifecycle, successor, DRI, risk, and manifest-adoption invariants; +- initiative, decision, dependency, control, exception, and evidence schemas; +- generated portfolio outputs; +- workflow permission and immutable-action-pin policy; +- public/private and secret-like-data safeguards; +- this repository's own agent manifest; +- negative regression fixtures through unit tests. + +The scheduled drift workflow separately compares the declared public inventory with GitHub's public repository metadata and maintains one deduplicated drift issue. + +## Repository map + +```text +agent/ This repository's machine-readable agent contract +governance/ Public portfolio registry, controls, lifecycle, exceptions +initiatives/ Cross-repository outcomes and responsibility assignments +decisions/ Organization-spanning ADRs and decision index +compatibility/ Public dependency, contract, and release-train indexes +policies/ Normative organization-governance procedures +docs/ Operating model, administration, mappings, and rollout +generated/ Deterministic views; never edit by hand +schemas/ JSON Schemas for exchanged governance records +scripts/ Dependency-free validation, generation, and reconciliation +tests/ Red-to-green governance invariant tests +.github/ Review templates, issue forms, and least-privilege workflows +``` + +## Change procedure + +1. Identify the canonical owner before proposing a new repository, schema, service, database, or control plane. +2. Change the smallest authoritative record; do not duplicate repository-local truth here. +3. Include an evidence packet describing objective, non-goals, authority impact, tests, migration, rollback, and uncertainty. +4. Regenerate derived views with `python3 scripts/governance.py generate`. +5. Run `./scripts/agent-check fast`. +6. Merge only through the protected review path once the administrative hardening gate is complete. + +Temporary exceptions must be typed, owner-approved, narrowly scoped, and expiring. See [`policies/exceptions.md`](policies/exceptions.md). + +## Current activation state + +The files in this repository can establish **specified and verified repository-level policy**. They do not prove that GitHub organization settings match the policy. Activation therefore has two gates: + +- **Repository gate:** schema, validation, generated views, and CI are merged and green. +- **Administrative gate:** branch/ruleset, Actions, app, environment, and break-glass settings are applied and independently evidenced. + +Until both gates close, the governance plane is authoritative for reviewed public portfolio records but is not an independently verified GitHub-administration enforcement boundary. + +## Related work + +- Governance-plane activation: `OpenCoven/.github#5` +- Administrative hardening: `OpenCoven/.github#6` +- Advanced reusable automation conformance: `OpenCoven/.github#2` diff --git a/agent/manifest.json b/agent/manifest.json new file mode 100644 index 0000000..34dc958 --- /dev/null +++ b/agent/manifest.json @@ -0,0 +1,63 @@ +{ + "$schema": "../schemas/agent-manifest.schema.json", + "schema_version": "opencoven.agent-repo/v1", + "repository": { + "name": ".github", + "lifecycle": "active", + "canonicality": "canonical", + "canonical_for": [ + "organization.governance", + "organization.portfolio", + "organization.shared-policy" + ], + "does_not_own": [ + "familiar.identity", + "protected.authorization", + "orchestration", + "runtime.persistence", + "runtime.execution", + "release.approval", + "publication.approval" + ], + "owner": "BunsDev", + "technical_dri": "BunsDev", + "ownership_status": "bootstrap-single-owner" + }, + "risk": { + "class": "R4", + "protected_paths": [ + "governance/**", + "initiatives/**", + "decisions/**", + "compatibility/**", + "policies/**", + "schemas/**", + "scripts/**", + ".github/workflows/**", + ".github/CODEOWNERS" + ], + "generated_paths": ["generated/**"], + "network_policy": "deny-by-default", + "secrets_policy": "forbidden-in-repository", + "external_side_effects": [ + "issue-reconciliation", + "repository-administration-proposal" + ] + }, + "agent": { + "entrypoint": "AGENTS.md", + "bootstrap": "./scripts/agent-bootstrap", + "verify": { + "fast": "./scripts/agent-check fast", + "full": "./scripts/agent-check full" + } + }, + "contracts": { + "produces": [ + "opencoven.repository-registry.v1", + "opencoven.initiative.v1", + "opencoven.governance-evidence.v1" + ], + "consumes": [] + } +} diff --git a/compatibility/contracts.json b/compatibility/contracts.json new file mode 100644 index 0000000..fe2e2ad --- /dev/null +++ b/compatibility/contracts.json @@ -0,0 +1,43 @@ +{ + "$schema": "../schemas/contracts.schema.json", + "schema_version": "opencoven.contract-index/v1", + "contracts": [ + { + "id": "familiar.contract.v1", + "owner": "familiar-contract", + "status": "specified", + "immutable_release_required": true + }, + { + "id": "threads.protected-decision.v1", + "owner": "coven-threads", + "status": "specified", + "immutable_release_required": true + }, + { + "id": "psyche.control.v1", + "owner": "psyche", + "status": "specified", + "immutable_release_required": true + }, + { + "id": "coven.daemon.v1", + "owner": "coven", + "status": "implemented", + "immutable_release_required": true + }, + { + "id": "coven.runtime-descriptor.v1", + "owner": "coven-runtimes", + "status": "implemented", + "immutable_release_required": true + }, + { + "id": "opencoven.repository-registry.v1", + "owner": ".github", + "status": "implemented-pending-merge", + "immutable_release_required": false + } + ], + "claim_rule": "A listed contract status is coordination metadata. A security, privacy, continuity, interoperability, or full-conformance claim requires exact artifact and profile evidence from the owning repository." +} diff --git a/compatibility/dependencies.json b/compatibility/dependencies.json new file mode 100644 index 0000000..930d2f8 --- /dev/null +++ b/compatibility/dependencies.json @@ -0,0 +1,96 @@ +{ + "$schema": "../schemas/dependencies.schema.json", + "schema_version": "opencoven.dependencies/v1", + "edges": [ + { + "producer": "familiar-contract", + "consumer": "coven-threads", + "relationship": "identity-contract", + "required_evidence": "immutable-contract-and-vectors" + }, + { + "producer": "familiar-contract", + "consumer": "psyche", + "relationship": "identity-snapshot", + "required_evidence": "exact-root-and-revision-binding" + }, + { + "producer": "familiar-contract", + "consumer": "coven", + "relationship": "session-identity-binding", + "required_evidence": "real-daemon-conformance" + }, + { + "producer": "coven-threads", + "consumer": "coven", + "relationship": "protected-decision", + "required_evidence": "atomic-verify-and-commit" + }, + { + "producer": "psyche", + "consumer": "coven", + "relationship": "orchestrated-work", + "required_evidence": "task-lane-lease-receipt-canary" + }, + { + "producer": "coven-runtimes", + "consumer": "coven", + "relationship": "runtime-descriptor", + "required_evidence": "registry-digest-and-conformance" + }, + { + "producer": "coven", + "consumer": "sdk", + "relationship": "public-client-contract", + "required_evidence": "packed-artifact-canary" + }, + { + "producer": "coven", + "consumer": "coven-memory", + "relationship": "read-only-memory-projection", + "required_evidence": "mutation-negative-vectors" + }, + { + "producer": "coven", + "consumer": "coven-cave", + "relationship": "oversight-api", + "required_evidence": "real-daemon-e2e" + }, + { + "producer": "psyche", + "consumer": "psyche-build", + "relationship": "orchestration-client", + "required_evidence": "golden-workflow-receipts" + }, + { + "producer": "coven", + "consumer": "coven-code", + "relationship": "terminal-execution", + "required_evidence": "headless-brief-result-canary" + }, + { + "producer": "brand", + "consumer": "ui", + "relationship": "specimen-token-consumer", + "required_evidence": "immutable-brand-lock" + }, + { + "producer": "brand", + "consumer": "coven-cave", + "relationship": "production-brand-consumer", + "required_evidence": "immutable-brand-lock-and-ui-canary" + }, + { + "producer": "brand", + "consumer": "coven-landing", + "relationship": "public-web-brand-consumer", + "required_evidence": "canonical-profile-lock" + }, + { + "producer": "sdk", + "consumer": "coven-docs", + "relationship": "documented-public-client", + "required_evidence": "released-artifact-example-test" + } + ] +} diff --git a/compatibility/release-trains.json b/compatibility/release-trains.json new file mode 100644 index 0000000..6089a05 --- /dev/null +++ b/compatibility/release-trains.json @@ -0,0 +1,32 @@ +{ + "$schema": "../schemas/release-trains.schema.json", + "schema_version": "opencoven.release-trains/v1", + "release_trains": [ + { + "id": "trust-stack", + "members": [ + "familiar-contract", + "coven-threads", + "psyche", + "coven", + "coven-runtimes", + "sdk", + "coven-memory" + ], + "policy": "Compatibility changes require immutable producer artifacts and pinned downstream canaries before a coordinated claim." + }, + { + "id": "product-delivery", + "members": [ + "coven", + "psyche", + "sdk", + "coven-code", + "psyche-build", + "coven-cave", + "coven-docs" + ], + "policy": "Product release sequencing is derived from owning-repository evidence; this index does not approve releases." + } + ] +} diff --git a/decisions/ADR-0001-organization-governance-plane.md b/decisions/ADR-0001-organization-governance-plane.md new file mode 100644 index 0000000..8cb1136 --- /dev/null +++ b/decisions/ADR-0001-organization-governance-plane.md @@ -0,0 +1,114 @@ +# ADR-0001: Use `OpenCoven/.github` as the public organization governance plane + +- **Status:** Proposed; becomes Accepted when merged through the protected review path +- **Date:** 2026-09-03 +- **Decision owner:** BunsDev +- **Technical DRI:** BunsDev +- **Scope:** Public organization governance, portfolio coordination, and shared verification + +## Context + +OpenCoven spans identity, protected authorization, orchestration, daemon/runtime authority, clients, products, delivery, documentation, and brand repositories. Cross-repository plans and responsibility can drift when maintained independently in issues, documents, chats, dashboards, or repository-local roadmaps. + +The organization needs one durable answer for public repository purpose, lifecycle, canonical domain ownership, cross-repository initiatives, shared policy, compatibility relationships, and generated portfolio views. It must not create another runtime control plane or duplicate repository-local implementation truth. + +`OpenCoven/.github` already has organization-wide GitHub semantics for community files and reusable workflows. It is public, discoverable, versioned, reviewable, and portable as ordinary Git data. It is also high impact and therefore requires stronger administrative protection than its current unprotected `main` branch. + +## Decision + +Use `OpenCoven/.github` as the canonical **public organization-governance and portfolio-coordination plane**. + +It owns: + +- the public repository registry, lifecycle, public canonical-domain map, and disposition plan; +- cross-repository initiatives, organization ADRs, dependency/contract indexes, and shared policy; +- schemas, deterministic validation, reusable read-only verification workflows, drift reconciliation, and generated public views; +- coordination evidence for GitHub administration, without claiming the settings are applied until independently verified. + +It does not own: + +- component implementation, component ADRs, migrations, tests, releases, or repository-local evidence; +- private repository inventory or confidential operational context; +- familiar identity, protected authorization, orchestration, runtime persistence/execution, release approval, or publication approval; +- a manually maintained duplicate of Issues, Pull Requests, Projects, or runtime state. + +Private repositories remain federated through repository-local manifests and access-controlled operational views. Public records may use opaque private-overlay identifiers without revealing private inventory. + +GitHub Projects is the preferred operational presentation for cross-repository work, but its fields are derived coordination views. Files in Git remain authoritative for organization policy, ownership, lifecycle, initiative definition, and accepted decisions; repository issues and artifacts remain authoritative for implementation and evidence. + +## Alternatives considered + +### New dedicated governance repository + +Rejected for now. It would add another repository and discovery surface without materially improving separation. Revisit only if `.github` special-repository coupling, scale, confidentiality, or availability becomes a measured constraint that cannot be mitigated. + +### Documentation repository as the control plane + +Rejected. Documentation should present generated compatibility and policy information, not become the write authority for repository administration and portfolio ownership. + +### GitHub Projects or Issues as the sole authority + +Rejected. They are useful operational views but weaker for schema validation, immutable review, portable history, deterministic generation, and offline inspection. They also encourage manually duplicated status. + +### Backstage or another service catalog as primary authority + +Deferred. A catalog may consume the registry when the organization has enough scale to justify operating it. It must remain a projection unless separately ratified. + +### Monorepo consolidation + +Rejected as a governance solution. Some code may consolidate for technical reasons, but a monorepo does not resolve protected authority, ownership, release, or cross-product lifecycle boundaries and would create a large migration/blast radius. + +### Pure repository-local federation + +Rejected as insufficient. Repository-local truth remains necessary, but without a central ownership and initiative index it cannot reliably detect duplicate canonical claims, unowned repositories, or portfolio drift. + +## Consequences + +Positive: + +- one discoverable public ownership and portfolio source; +- reviewable, machine-readable policy and history; +- generated rather than manually recopied portfolio views; +- explicit canonical/derived and public/private boundaries; +- reusable agent and CI contracts without adding another service. + +Costs and risks: + +- `.github` becomes a high-impact supply-chain and governance target; +- central changes can create organization-wide noise or bottlenecks; +- the public repository cannot contain private operational detail; +- declarative records can diverge from actual GitHub settings; +- schemas can ossify if evolution and exceptions are not governed. + +Mitigations: + +- protect `main`, workflows, schemas, decisions, and registry paths through rulesets and CODEOWNERS; +- default workflows to read-only and pin third-party Actions by commit; +- separate read-only drift observation from privileged plan-bound reconciliation; +- reconcile declared state against GitHub APIs and owning-repository evidence; +- use expiring exceptions and versioned schemas; +- retain implementation and protected authority in canonical repositories. + +## Activation criteria + +This decision is active only after: + +1. repository validation and generated-view checks pass; +2. the governance-plane PR is reviewed and merged; +3. `.github/main` is protected by required review and `Governance CI / validate`; +4. organization Actions/app/environment/break-glass controls are evidenced; +5. at least two canonical repositories consume the reusable readiness workflow at an immutable revision. + +Before criteria 3–4, records are reviewed coordination truth but not independently verified GitHub-administration enforcement. + +## Revisit triggers + +Re-evaluate the repository choice when any of these persist for two review cycles: + +- public/private separation prevents necessary coordination; +- registry or initiative review regularly blocks unrelated delivery; +- generated artifacts or Git history become operationally unmanageable; +- `.github` outages or special semantics materially impair governance availability; +- more than 100 active repositories or multiple autonomous governance domains require delegated catalogs; +- a service catalog provides measured value that outweighs its operational and duplicate-truth risk; +- ruleset, workflow, or app blast radius cannot be reduced to the accepted risk tolerance. diff --git a/decisions/ADR-0002-governance-metadata-is-not-protected-authority.md b/decisions/ADR-0002-governance-metadata-is-not-protected-authority.md new file mode 100644 index 0000000..41d8408 --- /dev/null +++ b/decisions/ADR-0002-governance-metadata-is-not-protected-authority.md @@ -0,0 +1,19 @@ +# ADR-0002: Governance metadata is coordination and evidence, not protected authority + +- **Status:** Proposed; becomes Accepted when merged +- **Date:** 2026-09-03 + +## Decision + +No organization registry, initiative, ADR, issue, Project field, task text, prompt, model response, agent manifest, CI output, or caller-supplied field may grant itself protected OpenCoven authority. + +Protected changes continue to require the operation-specific canonical authority and atomic enforcement owned by Familiar Contract, Coven Threads, Psyche, Coven, release systems, or repository administration as applicable. + +Governance records may identify required approvers, evidence, and intended state. They become effective write gates only where a separately authenticated enforcement mechanism binds the exact reviewed record to the operation and fails closed on moved or revoked state. + +## Consequences + +- Pending proposals cannot appear as committed state. +- “Approved” metadata without authenticated, operation-specific enforcement is descriptive only. +- Agents must degrade unverified protected requests to proposals rather than execute them. +- CI success does not prove runtime security, privacy, continuity, legal compliance, or human authorization. diff --git a/decisions/ADR-0003-public-registry-private-federation.md b/decisions/ADR-0003-public-registry-private-federation.md new file mode 100644 index 0000000..0162db1 --- /dev/null +++ b/decisions/ADR-0003-public-registry-private-federation.md @@ -0,0 +1,16 @@ +# ADR-0003: Keep the central registry public and federate private overlays + +- **Status:** Proposed; becomes Accepted when merged +- **Date:** 2026-09-03 + +## Decision + +`governance/repositories.json` inventories public repositories only. Private repositories publish the same repository-manifest contract locally and participate through access-controlled Projects, issues, evidence, or a future approved private projection. + +The public plane may refer to an opaque private overlay by capability identifier, but it must not disclose private repository names, incident details, credentials, user data, prompts, memories, private paths, or confidential plans. + +Aggregation must preserve provenance and access controls. A private projection may consume the public registry; the public registry must never infer or mirror private data back into public output. + +## Consequences + +The public portfolio is transparent and independently verifiable without turning `.github` into a confidentiality hazard. Organization-wide views spanning private work require an authenticated projection and cannot be reconstructed from public files alone. diff --git a/decisions/README.md b/decisions/README.md new file mode 100644 index 0000000..4868eb6 --- /dev/null +++ b/decisions/README.md @@ -0,0 +1,7 @@ +# Organization decisions + +This directory contains decisions whose scope crosses repository ownership boundaries. Component-local implementation ADRs stay in the owning repository. + +A decision record is `proposed` until merged through the protected review path. Acceptance coordinates organization behavior; it does not by itself authorize protected runtime changes, releases, publication, or GitHub administration. + +Superseded decisions are retained with links to their successors. `decisions/index.json` is the machine-readable index. diff --git a/decisions/index.json b/decisions/index.json new file mode 100644 index 0000000..bc6b034 --- /dev/null +++ b/decisions/index.json @@ -0,0 +1,27 @@ +{ + "$schema": "../schemas/decision-index.schema.json", + "schema_version": "opencoven.decision-index/v1", + "decisions": [ + { + "id": "ADR-0001", + "title": "Use OpenCoven/.github as the public organization governance plane", + "status": "proposed", + "path": "decisions/ADR-0001-organization-governance-plane.md", + "date": "2026-09-03" + }, + { + "id": "ADR-0002", + "title": "Governance metadata is coordination and evidence, not protected authority", + "status": "proposed", + "path": "decisions/ADR-0002-governance-metadata-is-not-protected-authority.md", + "date": "2026-09-03" + }, + { + "id": "ADR-0003", + "title": "Keep the central registry public and federate private overlays", + "status": "proposed", + "path": "decisions/ADR-0003-public-registry-private-federation.md", + "date": "2026-09-03" + } + ] +} diff --git a/docs/administration-baseline.md b/docs/administration-baseline.md new file mode 100644 index 0000000..91a8149 --- /dev/null +++ b/docs/administration-baseline.md @@ -0,0 +1,60 @@ +# GitHub administration baseline + +This document is an implementation checklist for organization settings. Repository content cannot enforce these controls by itself. `OpenCoven/.github#6` is the authoritative activation gate until exact settings evidence is recorded. + +## `.github/main` ruleset + +Required: + +- pull request before merge; +- at least one approval and CODEOWNER review for protected paths; +- stale approval dismissal after new commits; +- resolved review conversations; +- required `Governance CI / validate` check; +- blocked force push and branch deletion; +- no routine administrator bypass; +- signed commits or equivalent verified provenance where operationally supportable; +- exported ruleset ID/configuration retained as evidence. + +## Organization permissions + +Review and minimize: + +- base member repository permission; +- repository creation and visibility-change rights; +- archive, transfer, deletion, ruleset, webhook, App, secret, environment, and runner administration; +- outside collaborators and dormant administrators; +- OAuth Apps, GitHub Apps, deploy keys, classic PATs, and machine users; +- branch/ruleset bypass lists. + +Require organization-member MFA. Prefer hardware-backed MFA for owners and break-glass custodians. + +## Actions + +- Default `GITHUB_TOKEN` to read-only. +- Allow only required Actions and reusable workflows. +- Pin third-party Actions to full commit SHAs and review automated updates. +- Disable or constrain workflows from forks that could access secrets or privileged runners. +- Use protected environments for publication and administrative reconciliation. +- Prefer OIDC and short-lived GitHub App installation tokens over long-lived secrets. +- Separate untrusted build/test from privileged signing, publication, or settings application. + +## Settings reconciliation + +A future administrative reconciler must have two modes: + +1. **plan**: read settings, compare against reviewed desired state, and emit a deterministic immutable plan; +2. **apply**: require protected-environment approval, verify the plan digest and live-state preconditions, apply only listed changes, and emit before/after receipts. + +The apply identity must not accept arbitrary repository, permission, or operation fields from pull-request code. It must stop on moved, stale, revoked, or contradictory state. + +## Recovery exercise + +At least periodically prove: + +- repository and accepted-policy export; +- ruleset reconstruction; +- App/token revocation; +- organization-owner recovery; +- release channel and package ownership recovery; +- break-glass access followed by log review and credential rotation. diff --git a/docs/github-projects-integration.md b/docs/github-projects-integration.md new file mode 100644 index 0000000..682ffde --- /dev/null +++ b/docs/github-projects-integration.md @@ -0,0 +1,46 @@ +# GitHub Issues and Projects integration + +## Division of authority + +| Data | Authoritative location | Project treatment | +|---|---|---| +| Repository lifecycle, canonicality, public domain ownership, risk | `governance/repositories.json` | Read-only generated fields/labels | +| Initiative outcome, decision owner, technical DRI, dependencies, exit criteria | `initiatives/*.json` | Synced view and filtering | +| Implementation status, code, review, tests | Owning repository issue/PR/CI | Native issue/PR fields | +| Accepted organization decision | `decisions/` | Link only | +| Compatibility/release evidence | Owning repository artifact plus `compatibility/` index | Digest/profile summary only | +| Immediate prioritization and attention | Project | Operational; not copied back as normative truth unless reviewed | + +## Recommended Project fields + +- Initiative ID +- Canonical domain +- Owning repository +- Workstream driver +- Decision owner +- Technical DRI +- Risk class +- Lifecycle +- Current gate +- Dependency state +- Evidence state +- Target release +- Last verified revision +- Stale/degraded flag + +Avoid manually maintained percent-complete fields. Compute status from linked workstream issues, required checks, and explicit exit criteria. + +## Synchronization contract + +The safe direction is: + +```text +Git files + owning-repository evidence → generated Project fields +Project prioritization/assignment → human-reviewed PR when normative records must change +``` + +A Project automation may propose a registry or initiative update, but must not commit a protected change directly. Duplicate issue creation should be prevented with stable initiative/workstream identifiers. + +## Private work + +Use an access-controlled Project for private-repository workstreams. The public initiative may contain an opaque private-overlay identifier, but synchronization must not copy private titles, descriptions, assignees, labels, paths, or evidence into the public repository. diff --git a/docs/operating-model.md b/docs/operating-model.md new file mode 100644 index 0000000..794d8d2 --- /dev/null +++ b/docs/operating-model.md @@ -0,0 +1,81 @@ +# OpenCoven cross-repository operating model + +## Purpose + +The governance plane centralizes **organization-level context** without centralizing implementation authority. It provides one public ownership map, one initiative definition per cross-repository outcome, one accepted decision trail, and deterministic aggregate views. + +## Responsibility model + +| Role | Accountable for | Not sufficient for | +|---|---|---| +| Decision owner | Outcome, scope, priority, conflict resolution, and acceptance of organization-level tradeoffs | Protected runtime authorization or GitHub administration | +| Technical DRI | Coordinated technical delivery, dependency sequencing, evidence completeness, and handoff | Unreviewed merge, release, publication, or destructive action | +| Repository owner | Repository purpose, lifecycle, maintainership, and successor planning | Another repository's canonical implementation | +| Canonical-domain owner | Normative artifacts and invariant enforcement for the named domain | Self-expansion into adjacent domains without review | +| Workstream driver | Implementation issue/PR and repository-local verification | Changing the central outcome alone | +| Protected owner/approver | Review at an R3/R4 boundary | Authority outside the exact operation and system | +| GitHub administrator | Organization/repository settings under authenticated access | Familiar, Threads, Psyche, Coven, release, or publication authority | + +RACI tables may be generated for presentations, but the machine-readable records use one accountable decision owner and one technical DRI to avoid diffuse responsibility. Contributors and consulted parties stay in repository issues/Projects rather than a static central list. + +## Source-of-truth split + +```text +Organization outcome and ownership + └── .github initiative / ADR / public registry + ├── owning-repository issue and PR + ├── immutable contract/artifact revision + ├── exact CI, real-daemon, or packaged evidence + └── generated Project/dashboard view +``` + +- Central files answer **why**, **who**, **which owner**, **which dependency**, and **which exit evidence**. +- Owning repositories answer **how**, **what code**, **what test**, **what migration**, and **what release**. +- GitHub settings evidence answers **whether administrative controls are actually applied**. +- Projects answer **what needs attention now** and remain replaceable views. + +## Review cadence + +- P0 initiatives: review at least weekly while active. +- P1 initiatives: review at least biweekly. +- Active public repositories: lifecycle review at least quarterly. +- Incubating, maintenance, and deprecated repositories: review on the shorter cadence encoded in `governance/lifecycle.json`. +- R3/R4 administrative and compatibility controls: scheduled drift plus periodic effectiveness testing. + +The `review_by` field is a fail-closed prompt for reassessment, not an automatic state transition. + +## Cross-repository change protocol + +1. Identify the canonical producer and all affected consumers. +2. Open or update the organization initiative only when the shared outcome or ownership changes. +3. Make implementation changes in owning repositories. +4. Version the canonical schema/contract and publish immutable vectors/artifacts where applicable. +5. Update consumers to exact revisions and run consumer-specific canaries. +6. Record migration, rollback, unsupported platforms, degraded profiles, and residual risk. +7. Update the central dependency/contract index only after source-adjacent evidence exists. +8. Complete an initiative only when every exit criterion points to exact evidence. + +## Conflict and escalation + +When two repositories claim the same domain, validation fails. Work may continue as proposals, but no new canonical release or protected mutation should rely on the conflict. + +Escalation order: + +1. repository owners gather current implementation evidence; +2. canonical-domain owner identifies the governing invariant; +3. technical DRI proposes the smallest migration or containment; +4. decision owner resolves organization scope; +5. protected owner/administrator authorizes the exact protected operation; +6. a regression guard prevents the ambiguity from recurring. + +## Bus factor and succession + +The current registry truthfully marks `bootstrap-single-owner`. This is accepted bootstrap risk, not a mature control state. + +R3/R4 maturity requires: + +- at least one qualified backup reviewer or delegated team; +- documented ownership transfer procedure; +- protected credentials and break-glass custody not bound to one personal account; +- periodic access review; +- provenance-preserving ownership history. diff --git a/docs/rollout.md b/docs/rollout.md new file mode 100644 index 0000000..7c4d02c --- /dev/null +++ b/docs/rollout.md @@ -0,0 +1,67 @@ +# Governance-plane rollout + +The rollout is intentionally reversible. It does not itself archive, transfer, privatize, delete, release, publish, or change organization settings. + +## Initial slice — this change + +- establish ADRs and source-of-truth boundaries; +- inventory the current public GitHub surface; +- encode lifecycle, canonicality, ownership, risk, disposition, controls, and exceptions; +- add cross-repository initiatives and public dependency/contract indexes; +- add dependency-free validation, generation, negative tests, issue forms, and evidence templates; +- add read-only CI, reusable readiness/evidence workflows, and scheduled public drift observation; +- create separate repository and administrative activation issues. + +Exit: clean local fast gate and green PR CI. + +## Days 0–30 + +1. Review and merge the governance-plane PR. +2. Apply and evidence the `.github/main` ruleset and organization/Actions baseline from issue #6. +3. Pilot repository-local `agent/manifest.json` and the reusable readiness workflow in at least two canonical repositories at an immutable `.github` commit. +4. Reconcile the live public inventory and correct default-branch/archive/manifest drift. +5. Convert current portfolio recommendations into scoped repository-local migration issues. +6. Add backup reviewers for the highest-risk R4 repositories or explicitly track the bus-factor exception. + +Exit: + +- repository and administrative activation gates are closed; +- two pilot consumers are green; +- no unowned or duplicate public canonical domains; +- drift observer maintains at most one issue. + +## Days 31–60 + +1. Extend manifests and fast/full interfaces to all retained active public repositories. +2. Add immutable producer/consumer canaries for the trust stack. +3. Generate the public compatibility page in Coven Docs from exact artifacts. +4. Migrate non-duplicative value from deprecated repositories with provenance. +5. Archive time-bounded historical repositories only after the retirement gate and explicit authorization. +6. Add SBOM/provenance/signing evidence to release-owning repositories where appropriate. + +Exit: + +- every retained public repository has owner, lifecycle, manifest, clean bootstrap, and required checks; +- every canonical producer has at least one immutable downstream canary; +- no deprecated repository introduces a new canonical surface. + +## Days 61–90 + +1. Complete approved consolidation, private-incubation, transfer, archival, or tombstone actions. +2. Validate package/update/download/domain/webhook continuity after each retirement observation window. +3. Run standardized golden-task evaluations and control-effectiveness tests. +4. Add access-controlled aggregation for private repository manifests without copying private context into public files. +5. Decide whether scale justifies a Backstage/service-catalog projection; keep files as authority unless a new ADR proves otherwise. +6. Publish an evidence-backed portfolio review with exact residual risks and no certification overclaims. + +Exit: + +- public inventory matches the reviewed target for that date; +- zero ambiguous canonical ownership; +- zero expired exceptions or stale generated views; +- zero broken references caused by approved retirement; +- administrative and release controls have recurring effectiveness evidence. + +## Revisit criteria + +Reconsider the architecture if confidentiality, scale, bottlenecks, availability, or blast radius remain unacceptable for two review cycles despite the mitigations in ADR-0001. diff --git a/docs/standards-and-assurance-mapping.md b/docs/standards-and-assurance-mapping.md new file mode 100644 index 0000000..2b6cfee --- /dev/null +++ b/docs/standards-and-assurance-mapping.md @@ -0,0 +1,34 @@ +# Standards and assurance mapping + +This mapping helps OpenCoven design future assurance evidence. It does **not** claim certification, attestation, legal compliance, or complete control coverage. + +| OpenCoven governance concern | Useful external reference families | Current evidence in this plane | Important gap | +|---|---|---|---| +| Governance, accountability, risk ownership | NIST CSF 2.0 Govern; ISO/IEC 27001/27002 organizational controls; SOC 2 common criteria | Registry, owners/DRIs, lifecycle, controls, ADRs | Independent scope, control ownership separation, operating evidence | +| Secure development | NIST SSDF; OpenSSF Best Practices/Scorecard | Agent policy, risk classes, deterministic checks, dependency/action pinning | Uniform adoption and effectiveness across repositories | +| Supply-chain provenance | SLSA; SPDX; CycloneDX; Sigstore | Contract index, immutable-pin policy, evidence schema | Per-release SBOM/provenance/signing in owning repositories | +| Access and least privilege | NIST CSF Protect; CIS Controls; GitHub security guidance | Administration baseline and issue #6 | Applied org settings, access review, MFA/App evidence | +| Change management and auditability | ISO/IEC 27001 change/configuration controls; SOC 2 change-management criteria | Git history, PR templates, ADRs, exception expiry, generated-view checks | Protected merge/settings evidence and recurring effectiveness tests | +| Incident and vulnerability handling | NIST CSF Respond/Recover; ISO/IEC 27035 concepts | Organization `SECURITY.md`, private-advisory route, recovery policy | Measured response process and tabletop/incident evidence | +| Privacy and data minimization | GDPR/CCPA principles; ISO/IEC 27018 where cloud PII applies | Public/private minimization policy | Processing inventory, legal bases, data-subject procedures, deployment-specific controls | +| AI risk and transparency | NIST AI RMF | Honest claim boundaries and agent authority separation | Deployment-specific measurement, human factors, model/provider controls | +| Cloud security | ISO/IEC 27017 and provider-specific guidance where hosted services exist | Least-privilege/OIDC direction | Cloud-specific shared-responsibility, tenant isolation, logging, key management evidence | + +## Interpretation rules + +- Standards provide control objectives and vocabulary; they do not prove the implementation satisfies them. +- A public repository check cannot establish SOC 2 or ISO certification. +- Privacy obligations depend on actual processing, roles, jurisdictions, contracts, and deployment behavior. +- AI risk controls supplement rather than replace identity, authorization, software-security, and privacy controls. +- Each assurance claim must name scope, exact release/artifact, environment, evidence period, exceptions, and independent reviewer where applicable. + +## Open-source governance and provenance + +OpenCoven currently uses MIT licensing, DCO sign-off, patent non-assertion language, and contribution provenance guidance. Before enterprise or foundation transition, obtain qualified legal review of: + +- license consistency and third-party notices; +- DCO versus CLA tradeoffs for the intended governance model; +- patent policy and contributor authority; +- trademark/certification-mark rules for conformance claims; +- AI-assisted contribution disclosure and provenance; +- retention of public contribution metadata and security records. diff --git a/docs/verification-model.md b/docs/verification-model.md new file mode 100644 index 0000000..04756d8 --- /dev/null +++ b/docs/verification-model.md @@ -0,0 +1,33 @@ +# Verification and adherence model + +## Layers + +1. **Schema/structure** — records parse and contain required fields. +2. **Semantic invariants** — canonical domains are unique; lifecycles, successors, dependencies, and authority boundaries agree. +3. **Derived-state integrity** — generated views match authoritative inputs exactly. +4. **Repository adoption** — local agent manifests, checks, protected paths, and contract pins match the public registry. +5. **Live GitHub reconciliation** — public repository metadata and required manifests match declared state. +6. **Administrative application** — rulesets, permissions, environments, Apps, and break-glass controls are applied. +7. **Control effectiveness** — positive/negative tests and recurring evidence show controls continue working. +8. **Product/protocol conformance** — owning repositories prove structural, runtime, continuity, privacy, interoperability, and release behavior against exact artifacts. + +No lower layer implies a higher one. + +## Current automated evidence + +`./scripts/agent-check fast` provides layers 1–3 for this repository and includes negative regression tests. The reusable workflow provides part of layer 4. The scheduled drift observer provides part of layer 5. Issue #6 tracks layer 6. Advanced cross-repository conformance remains under issue #2 and owning repositories. + +## Golden tasks for agent readiness + +Each active repository should eventually prove at least: + +- a clean-clone documentation change; +- a focused pure-code fix; +- a protected-path proposal that correctly stops for approval; +- a malformed manifest/contract rejection; +- an unsupported-platform result that is reported without being hidden or misclassified; +- a generated-file drift failure; +- a secret/private-data fixture that is rejected without logging sensitive content; +- a cross-repository contract update using an immutable producer artifact. + +Measure clarification count, human interventions, check duration, false failures, escaped drift, and rollback success. Do not optimize velocity by weakening protected boundaries. diff --git a/evidence/2026-09-03-organization-governance-plane-v1.json b/evidence/2026-09-03-organization-governance-plane-v1.json new file mode 100644 index 0000000..dfe99c7 --- /dev/null +++ b/evidence/2026-09-03-organization-governance-plane-v1.json @@ -0,0 +1,117 @@ +{ + "$schema": "../schemas/evidence-packet.schema.json", + "schema_version": "opencoven.governance-evidence/v1", + "change": { + "objective": "Establish the initial public OpenCoven organization governance and portfolio coordination plane in OpenCoven/.github.", + "acceptance_criteria": [ + "Current public repositories are inventoried with ownership, lifecycle, canonicality, risk, disposition, and manifest state.", + "Canonical public domains have unique owners.", + "Cross-repository initiatives, decisions, dependencies, controls, and exceptions are machine-readable and validated.", + "Generated portfolio views are deterministic and stale changes fail validation.", + "GitHub Actions use explicit least-privilege permissions and immutable third-party Action pins.", + "Administrative organization settings remain a separately evidenced gate." + ], + "non_goals": [ + "Changing repository visibility, archive state, ownership, transfer, or deletion.", + "Merging, releasing, publishing, deploying, or applying GitHub organization settings.", + "Replacing repository-local implementation truth or OpenCoven protected authority systems.", + "Publishing private repository inventory or confidential operational context." + ] + }, + "authority": { + "risk_class": "R4", + "protected_boundaries": [ + "organization governance and repository administration", + "identity and principal binding", + "protected authorization", + "orchestration", + "runtime persistence and execution", + "release and publication" + ], + "authorization_effect": "none-metadata-only" + }, + "sources": [ + { + "kind": "project-research", + "reference": "OpenCoven Public Repository Agent-Readiness Audit", + "revision": "sha256:3fbb5f4712acd2d3589755e6e08415b41fb8c08170b528e6db6f9923f43b55c3" + }, + { + "kind": "project-research", + "reference": "SPAR and Familiar Contract reconciliation", + "revision": "sha256:754b2ed36235c1ed51b8ecf522053d4e16cc4f7ffd9fa78ada5d565fa1f75cf0" + }, + { + "kind": "repository", + "reference": "OpenCoven/.github", + "revision": "c8b4ad3f9f9794db0fa79f338c2ce688ce6d4106" + }, + { + "kind": "github-public-inventory", + "reference": "OpenCoven public repositories", + "revision": "observed-2026-09-03:30-public-repositories" + } + ], + "files": [ + "README.md", + "AGENTS.md", + "agent/manifest.json", + "governance/**", + "initiatives/**", + "decisions/**", + "compatibility/**", + "policies/**", + "docs/**", + "schemas/**", + "scripts/**", + "tests/**", + "generated/**", + ".github/**" + ], + "verification": [ + { + "command": "./scripts/agent-check fast", + "result": "pass", + "environment": "Linux; Python 3.13.5; dependency-free deterministic path", + "evidence": "Governance validation, generated-view check, and 13 unit tests passed." + }, + { + "command": "bash -n scripts/agent-bootstrap scripts/agent-check", + "result": "pass", + "environment": "Linux bash", + "evidence": "Shell entrypoints parsed successfully." + }, + { + "command": "python3 -m py_compile scripts/governance.py scripts/governance_core.py scripts/governance_model.py scripts/governance_cli.py tests/test_governance.py", + "result": "pass", + "environment": "Python 3.13.5", + "evidence": "CLI wrapper, modular validator/generator/reconciler, and test module compiled successfully." + }, + { + "command": "PyYAML safe_load over .github/**/*.yml", + "result": "pass", + "environment": "Local validation environment", + "evidence": "Issue forms, Dependabot config, and four workflows parsed without YAML syntax errors." + }, + { + "command": "GitHub Actions pull-request execution", + "result": "skipped", + "environment": "GitHub-hosted runner", + "evidence": "Pending creation of the review branch and pull request." + }, + { + "command": "Organization ruleset and permission effectiveness test", + "result": "skipped", + "environment": "GitHub organization administration", + "evidence": "Explicitly tracked by OpenCoven/.github#6; current main was observed unprotected before this change." + } + ], + "migration": "Additive and reversible. Existing policy, provenance, patent, profile, and audit files remain untouched. Public portfolio records begin as reviewed coordination metadata; repository-local implementation remains authoritative.", + "rollback": "Close the pull request and delete the feature branch before merge. After merge, revert the governance-plane commit while preserving issues and settings evidence; no repository lifecycle or visibility mutations are coupled to this change.", + "uncertainty": [ + "Remote GitHub Actions behavior is pending pull-request execution.", + "Organization rulesets, Actions policy, app scopes, environments, MFA, and break-glass controls are not applied by repository content and remain open in issue #6.", + "The public registry reflects the connected GitHub inventory observed on 2026-09-03 and requires scheduled reconciliation after merge.", + "Private repository aggregation is intentionally omitted from this public repository and requires an access-controlled federated overlay." + ] +} diff --git a/evidence/README.md b/evidence/README.md new file mode 100644 index 0000000..518bda1 --- /dev/null +++ b/evidence/README.md @@ -0,0 +1,5 @@ +# Governance evidence packets + +This directory contains public, machine-readable evidence for material governance-plane changes. Packets must conform to `schemas/evidence-packet.schema.json` and must not contain secrets, private repository inventory, prompts, memories, user data, private paths, or embargoed security detail. + +A packet records verification; it does not grant protected OpenCoven or GitHub-administration authority. diff --git a/generated/controls.md b/generated/controls.md new file mode 100644 index 0000000..496ead6 --- /dev/null +++ b/generated/controls.md @@ -0,0 +1,16 @@ +# Generated governance control index + +> Generated from `governance/controls.json`. A control marked specified or implemented is not necessarily administratively applied or operationally effective. + +| Control | Objective | Enforcement | State | +|---|---|---|---| +| `GOV-001` Unique canonical ownership | Every canonical public domain has exactly one owning repository. | scripts/governance.py validate | implemented | +| `GOV-002` Public/private minimization | The public registry contains public repositories and non-sensitive metadata only. | registry scope and secret-like-data validation | implemented | +| `GOV-003` Lifecycle accountability | Every public repository has a lifecycle, owner, DRI, risk class, disposition, and review date. | scripts/governance.py validate | implemented | +| `GOV-004` Derived-view integrity | Generated portfolio views exactly reflect authoritative records. | generate --check | implemented | +| `GOV-005` Least-privilege workflows | Workflows declare permissions and pin third-party Actions to immutable commits. | workflow policy validation | implemented | +| `GOV-006` Protected governance branch | Governance changes enter main only through reviewed, checked pull requests. | GitHub organization ruleset | administrative-gate-open | +| `GOV-007` Exception expiry | Every waiver is scoped, approved, expiring, and visible. | scripts/governance.py validate | implemented | +| `GOV-008` Repository drift detection | Declared public inventory is reconciled against GitHub without becoming a second mutable status store. | scheduled read-only discovery plus one issue | implemented-pending-merge | +| `GOV-009` Protected authority separation | Governance metadata cannot grant OpenCoven runtime or protected mutation authority. | review, schemas, and architecture canaries in owning repositories | specified | +| `GOV-010` Evidence-backed change | Material governance changes include machine-readable, reviewable evidence. | reusable evidence workflow | implemented-pending-adoption | diff --git a/generated/dependencies.mmd b/generated/dependencies.mmd new file mode 100644 index 0000000..91fdc9b --- /dev/null +++ b/generated/dependencies.mmd @@ -0,0 +1,47 @@ +%% Generated by scripts/governance.py; do not edit. +flowchart LR + _github[".github"] + brand["brand"] + cast_codes["cast-codes"] + claude_code_cast["claude-code-cast"] + coven["coven"] + coven_cave["coven-cave"] + coven_code["coven-code"] + coven_codeflow["coven-codeflow"] + coven_design_system["coven-design-system"] + coven_docs["coven-docs"] + coven_github_webhook["coven-github-webhook"] + coven_landing["coven-landing"] + coven_memory["coven-memory"] + coven_pocket["coven-pocket"] + coven_reach["coven-reach"] + coven_runtimes["coven-runtimes"] + coven_scout["coven-scout"] + coven_threads["coven-threads"] + demo_workspace["demo-workspace"] + desktop_use["desktop-use"] + familiar_contract["familiar-contract"] + homebrew_tap["homebrew-tap"] + open_fable["open-fable"] + open_meow_sdk["open-meow-sdk"] + opencoven_beta_august_hackathon_2026["opencoven-beta-august-hackathon-2026"] + opencoven_chat_api["opencoven-chat-api"] + psyche["psyche"] + psyche_build["psyche-build"] + sdk["sdk"] + ui["ui"] + familiar_contract -->|"identity-contract"| coven_threads + familiar_contract -->|"identity-snapshot"| psyche + familiar_contract -->|"session-identity-binding"| coven + coven_threads -->|"protected-decision"| coven + psyche -->|"orchestrated-work"| coven + coven_runtimes -->|"runtime-descriptor"| coven + coven -->|"public-client-contract"| sdk + coven -->|"read-only-memory-projection"| coven_memory + coven -->|"oversight-api"| coven_cave + psyche -->|"orchestration-client"| psyche_build + coven -->|"terminal-execution"| coven_code + brand -->|"specimen-token-consumer"| ui + brand -->|"production-brand-consumer"| coven_cave + brand -->|"public-web-brand-consumer"| coven_landing + sdk -->|"documented-public-client"| coven_docs diff --git a/generated/initiatives.md b/generated/initiatives.md new file mode 100644 index 0000000..8e63e43 --- /dev/null +++ b/generated/initiatives.md @@ -0,0 +1,10 @@ +# Generated cross-repository initiatives + +> Generated from `initiatives/*.json`. Implementation status remains authoritative in linked owning-repository evidence. + +| Initiative | Priority | Status | Decision owner | Technical DRI | Review by | Open criteria | +|---|---:|---|---|---|---|---:| +| `familiar-identity-continuity-v1` | P0 | active | @BunsDev | @BunsDev | 2026-10-03 | 7 | +| `organization-governance-plane-v1` | P0 | active | @BunsDev | @BunsDev | 2026-10-03 | 6 | +| `public-portfolio-consolidation-2026` | P0 | active | @BunsDev | @BunsDev | 2026-10-03 | 6 | +| `brand-ui-consolidation` | P1 | active | @BunsDev | @BunsDev | 2026-10-03 | 5 | diff --git a/generated/ownership.md b/generated/ownership.md new file mode 100644 index 0000000..a8694bd --- /dev/null +++ b/generated/ownership.md @@ -0,0 +1,39 @@ +# Generated canonical public ownership map + +> Generated from `governance/repositories.json`. A governance claim identifies ownership; it does not grant protected runtime authority. + +| Canonical domain | Repository | Technical DRI | Risk | +|---|---|---|---:| +| `access.canonical-bindings` | `sdk` | @BunsDev | R3 | +| `access.public-sdk` | `sdk` | @BunsDev | R3 | +| `authority.proposal-commit-decisions` | `coven-threads` | @BunsDev | R4 | +| `authority.protected-surface` | `coven-threads` | @BunsDev | R4 | +| `brand.identity` | `brand` | @BunsDev | R2 | +| `brand.public-web-profile` | `brand` | @BunsDev | R2 | +| `brand.voice` | `brand` | @BunsDev | R2 | +| `execution.terminal-coding` | `coven-code` | @BunsDev | R3 | +| `identity.familiar-contract` | `familiar-contract` | @BunsDev | R4 | +| `identity.principal-binding` | `familiar-contract` | @BunsDev | R4 | +| `identity.revision-semantics` | `familiar-contract` | @BunsDev | R4 | +| `knowledge.compatibility-presentation` | `coven-docs` | @BunsDev | R1 | +| `knowledge.public-documentation` | `coven-docs` | @BunsDev | R1 | +| `memory.projection` | `coven-memory` | @BunsDev | R3 | +| `memory.read-only-client` | `coven-memory` | @BunsDev | R3 | +| `orchestration.approvals` | `psyche` | @BunsDev | R4 | +| `orchestration.lanes` | `psyche` | @BunsDev | R4 | +| `orchestration.leases` | `psyche` | @BunsDev | R4 | +| `orchestration.receipts` | `psyche` | @BunsDev | R4 | +| `orchestration.recovery` | `psyche` | @BunsDev | R4 | +| `orchestration.tasks` | `psyche` | @BunsDev | R4 | +| `organization.governance` | `.github` | @BunsDev | R4 | +| `organization.portfolio` | `.github` | @BunsDev | R4 | +| `organization.shared-policy` | `.github` | @BunsDev | R4 | +| `product.coding-cockpit` | `psyche-build` | @BunsDev | R3 | +| `product.human-oversight` | `coven-cave` | @BunsDev | R3 | +| `product.production-ui` | `coven-cave` | @BunsDev | R3 | +| `runtime.capability-descriptors` | `coven-runtimes` | @BunsDev | R4 | +| `runtime.conformance` | `coven-runtimes` | @BunsDev | R4 | +| `runtime.daemon-authority` | `coven` | @BunsDev | R4 | +| `runtime.execution` | `coven` | @BunsDev | R4 | +| `runtime.persistence` | `coven` | @BunsDev | R4 | +| `runtime.sessions` | `coven` | @BunsDev | R4 | diff --git a/generated/portfolio.md b/generated/portfolio.md new file mode 100644 index 0000000..76a72d2 --- /dev/null +++ b/generated/portfolio.md @@ -0,0 +1,53 @@ +# Generated public repository portfolio + +> Generated by `python3 scripts/governance.py generate`. Do not edit by hand. + +Registry digest: `e04b1505b92c20f95f4b6b5fab3b1581117a3e9f7b97872ef9bbc15602266651` + +## Summary + +| Lifecycle | Count | +|---|---:| +| active | 16 | +| incubating | 2 | +| maintenance | 5 | +| deprecated | 5 | +| archived | 2 | +| tombstone | 0 | + +## Repositories + +| Repository | Lifecycle | Canonicality | Risk | Owner | Disposition | Manifest | +|---|---|---|---:|---|---|---| +| .github | active | canonical | R4 | @BunsDev | retain | enforced | +| brand | active | canonical | R2 | @BunsDev | retain | planned | +| cast-codes | archived | historical | R1 | @BunsDev | retain-archive | exempt | +| claude-code-cast | deprecated | none | R2 | @BunsDev | consolidate-then-retire | planned | +| coven | active | canonical | R4 | @BunsDev | retain | planned | +| coven-cave | active | canonical | R3 | @BunsDev | retain | planned | +| coven-code | active | canonical | R3 | @BunsDev | retain | planned | +| coven-codeflow | deprecated | none | R3 | @BunsDev | consolidate-then-retire | planned | +| coven-design-system | deprecated | none | R2 | @BunsDev | consolidate-then-retire | planned | +| coven-docs | active | canonical | R1 | @BunsDev | retain | planned | +| coven-github-webhook | deprecated | none | R3 | @BunsDev | consolidate-then-retire | planned | +| coven-landing | active | supporting | R1 | @BunsDev | retain | planned | +| coven-memory | active | canonical | R3 | @BunsDev | retain | planned | +| coven-pocket | maintenance | none | R3 | @BunsDev | evaluate-consolidation-or-private-incubation | planned | +| coven-reach | maintenance | none | R3 | @BunsDev | private-incubation-or-retire | planned | +| coven-runtimes | active | canonical | R4 | @BunsDev | retain | planned | +| coven-scout | maintenance | none | R3 | @BunsDev | private-incubation-or-retire | planned | +| coven-threads | active | canonical | R4 | @BunsDev | retain | planned | +| demo-workspace | incubating | supporting | R1 | @BunsDev | graduate-or-retire | planned | +| desktop-use | maintenance | none | R3 | @BunsDev | evaluate-consolidation-or-private-incubation | planned | +| familiar-contract | active | canonical | R4 | @BunsDev | retain | planned | +| homebrew-tap | active | supporting | R4 | @BunsDev | retain | planned | +| open-fable | incubating | none | R2 | @BunsDev | private-incubation-or-retire | planned | +| open-meow-sdk | archived | historical | R1 | @BunsDev | retain-archive | exempt | +| opencoven-beta-august-hackathon-2026 | maintenance | historical | R1 | @BunsDev | archive-after-retirement-gate | planned | +| opencoven-chat-api | deprecated | none | R3 | @BunsDev | consolidate-then-retire | planned | +| psyche | active | canonical | R4 | @BunsDev | retain | planned | +| psyche-build | active | canonical | R3 | @BunsDev | retain | planned | +| sdk | active | canonical | R3 | @BunsDev | retain | planned | +| ui | active | specimen | R1 | @BunsDev | retain | planned | + +This is a public-only view. Private repository inventory is intentionally federated and omitted. diff --git a/governance/controls.json b/governance/controls.json new file mode 100644 index 0000000..bf831b4 --- /dev/null +++ b/governance/controls.json @@ -0,0 +1,115 @@ +{ + "$schema": "../schemas/controls.schema.json", + "schema_version": "opencoven.controls/v1", + "controls": [ + { + "id": "GOV-001", + "title": "Unique canonical ownership", + "objective": "Every canonical public domain has exactly one owning repository.", + "evidence": [ + "governance/repositories.json", + "generated/ownership.md" + ], + "enforcement": "scripts/governance.py validate", + "status": "implemented" + }, + { + "id": "GOV-002", + "title": "Public/private minimization", + "objective": "The public registry contains public repositories and non-sensitive metadata only.", + "evidence": [ + "policies/public-private-data.md", + "tests/test_governance.py" + ], + "enforcement": "registry scope and secret-like-data validation", + "status": "implemented" + }, + { + "id": "GOV-003", + "title": "Lifecycle accountability", + "objective": "Every public repository has a lifecycle, owner, DRI, risk class, disposition, and review date.", + "evidence": [ + "governance/repositories.json" + ], + "enforcement": "scripts/governance.py validate", + "status": "implemented" + }, + { + "id": "GOV-004", + "title": "Derived-view integrity", + "objective": "Generated portfolio views exactly reflect authoritative records.", + "evidence": [ + "generated/", + "scripts/governance.py" + ], + "enforcement": "generate --check", + "status": "implemented" + }, + { + "id": "GOV-005", + "title": "Least-privilege workflows", + "objective": "Workflows declare permissions and pin third-party Actions to immutable commits.", + "evidence": [ + ".github/workflows/", + "tests/test_governance.py" + ], + "enforcement": "workflow policy validation", + "status": "implemented" + }, + { + "id": "GOV-006", + "title": "Protected governance branch", + "objective": "Governance changes enter main only through reviewed, checked pull requests.", + "evidence": [ + "OpenCoven/.github#6" + ], + "enforcement": "GitHub organization ruleset", + "status": "administrative-gate-open" + }, + { + "id": "GOV-007", + "title": "Exception expiry", + "objective": "Every waiver is scoped, approved, expiring, and visible.", + "evidence": [ + "governance/exceptions.json", + "policies/exceptions.md" + ], + "enforcement": "scripts/governance.py validate", + "status": "implemented" + }, + { + "id": "GOV-008", + "title": "Repository drift detection", + "objective": "Declared public inventory is reconciled against GitHub without becoming a second mutable status store.", + "evidence": [ + ".github/workflows/governance-drift.yml", + "scripts/governance.py" + ], + "enforcement": "scheduled read-only discovery plus one issue", + "status": "implemented-pending-merge" + }, + { + "id": "GOV-009", + "title": "Protected authority separation", + "objective": "Governance metadata cannot grant OpenCoven runtime or protected mutation authority.", + "evidence": [ + "decisions/ADR-0001-organization-governance-plane.md", + "policies/authority-boundaries.md" + ], + "enforcement": "review, schemas, and architecture canaries in owning repositories", + "status": "specified" + }, + { + "id": "GOV-010", + "title": "Evidence-backed change", + "objective": "Material governance changes include machine-readable, reviewable evidence.", + "evidence": [ + "schemas/evidence-packet.schema.json", + ".github/PULL_REQUEST_TEMPLATE.md", + "evidence/2026-09-03-organization-governance-plane-v1.json" + ], + "enforcement": "reusable evidence workflow", + "status": "implemented-pending-adoption" + } + ] +} diff --git a/governance/exceptions.json b/governance/exceptions.json new file mode 100644 index 0000000..b7f6e76 --- /dev/null +++ b/governance/exceptions.json @@ -0,0 +1,5 @@ +{ + "$schema": "../schemas/exception.schema.json", + "schema_version": "opencoven.exception-set/v1", + "exceptions": [] +} diff --git a/governance/lifecycle.json b/governance/lifecycle.json new file mode 100644 index 0000000..69cdb1a --- /dev/null +++ b/governance/lifecycle.json @@ -0,0 +1,83 @@ +{ + "$schema": "../schemas/lifecycle.schema.json", + "schema_version": "opencoven.lifecycle/v1", + "lifecycle_states": { + "incubating": { + "public_claim": "experimental", + "allowed_transitions": [ + "active", + "deprecated", + "archived" + ], + "review_interval_days": 30 + }, + "active": { + "public_claim": "actively-developed", + "allowed_transitions": [ + "maintenance", + "deprecated" + ], + "review_interval_days": 90 + }, + "maintenance": { + "public_claim": "supported-with-limited-change", + "allowed_transitions": [ + "active", + "deprecated", + "archived" + ], + "review_interval_days": 60 + }, + "deprecated": { + "public_claim": "successor-or-retirement-planned", + "allowed_transitions": [ + "maintenance", + "archived", + "tombstone" + ], + "review_interval_days": 30 + }, + "archived": { + "public_claim": "read-only-historical-record", + "allowed_transitions": [ + "maintenance", + "tombstone" + ], + "review_interval_days": 365 + }, + "tombstone": { + "public_claim": "minimal-successor-pointer", + "allowed_transitions": [], + "review_interval_days": 365 + } + }, + "canonicality_states": { + "canonical": "Owns one or more unique public domains.", + "supporting": "Supports canonical components without owning their domains.", + "specimen": "Reference or experimental surface that cannot define production behavior.", + "historical": "Historical evidence only.", + "none": "No canonical claim." + }, + "risk_classes": { + "R0": { + "scope": "Documentation, examples, and copy.", + "default_agent_authority": "autonomous-pr-after-validation" + }, + "R1": { + "scope": "Pure code without external mutable state.", + "default_agent_authority": "autonomous-branch-and-pr" + }, + "R2": { + "scope": "Local mutable state, schemas, or migrations.", + "default_agent_authority": "proposal-plus-deterministic-fixture" + }, + "R3": { + "scope": "Network, credentials, user data, remote APIs, or publication-adjacent behavior.", + "default_agent_authority": "approval-gated-execution" + }, + "R4": { + "scope": "Identity, authorization, persistence, release, deletion, or organization administration.", + "default_agent_authority": "human-approved-plan-and-protected-owner-review" + } + } +} diff --git a/governance/repositories.json b/governance/repositories.json new file mode 100644 index 0000000..8b661d8 --- /dev/null +++ b/governance/repositories.json @@ -0,0 +1 @@ +{"$schema":"../schemas/repository-registry.schema.json","schema_version":"opencoven.repository-registry/v1","organization":"OpenCoven","scope":{"visibility":"public-only","observed_as_of":"2026-09-03","expected_public_repository_count":30,"private_inventory":"federated-and-intentionally-omitted","private_overlay_policy":"policies/public-private-data.md"},"defaults":{"visibility":"public","observed":{"default_branch":"main","archived":false},"owner":"BunsDev","technical_dri":"BunsDev","ownership_status":"bootstrap-single-owner","canonical_domains":[],"does_not_own":[],"disposition":{"state":"retain","review_by":"2026-12-02"},"agent_manifest":{"status":"planned","path":"agent/manifest.json"},"security_support":"limited"},"repositories":[{"name":".github","lifecycle":"active","canonicality":"canonical","risk_class":"R4","purpose":"Public organization governance, portfolio coordination, shared policy, and generated public views.","canonical_domains":["organization.governance","organization.portfolio","organization.shared-policy"],"does_not_own":["familiar.identity","protected.authorization","orchestration","runtime.persistence","runtime.execution","release.approval","publication.approval"],"agent_manifest":{"status":"enforced","path":"agent/manifest.json"},"security_support":"active"},{"name":"brand","lifecycle":"active","canonicality":"canonical","risk_class":"R2","purpose":"Canonical OpenCoven visual identity, voice, and public-web profile.","canonical_domains":["brand.identity","brand.voice","brand.public-web-profile"],"does_not_own":["product.production-ui"],"security_support":"active"},{"name":"cast-codes","lifecycle":"archived","canonicality":"historical","risk_class":"R1","purpose":"Historical product and release lineage retained with successor context.","observed":{"default_branch":"main","archived":true},"disposition":{"state":"retain-archive","review_by":"2027-09-03"},"agent_manifest":{"status":"exempt","path":"agent/manifest.json"},"security_support":"historical"},{"name":"claude-code-cast","lifecycle":"deprecated","canonicality":"none","risk_class":"R2","purpose":"Legacy coding-event adapter and redaction fixtures.","disposition":{"state":"consolidate-then-retire","review_by":"2026-12-02","destination":{"kind":"repository","name":"coven-code"}},"security_support":"unsupported"},{"name":"coven","lifecycle":"active","canonicality":"canonical","risk_class":"R4","purpose":"Daemon authority, persistence, sessions, runtime execution, and authoritative state transitions.","canonical_domains":["runtime.daemon-authority","runtime.persistence","runtime.sessions","runtime.execution"],"does_not_own":["familiar.identity","protected.authorization","orchestration.protocol"],"security_support":"active"},{"name":"coven-cave","lifecycle":"active","canonicality":"canonical","risk_class":"R3","purpose":"Primary human oversight product and production UI behavior.","canonical_domains":["product.human-oversight","product.production-ui"],"does_not_own":["runtime.persistence","protected.authorization"],"security_support":"active"},{"name":"coven-code","lifecycle":"active","canonicality":"canonical","risk_class":"R3","purpose":"Terminal coding execution and headless coding contracts.","canonical_domains":["execution.terminal-coding"],"does_not_own":["orchestration.protocol","runtime.persistence"],"security_support":"active"},{"name":"coven-codeflow","lifecycle":"deprecated","canonicality":"none","risk_class":"R3","purpose":"Overlapping coding cockpit and execution experiment.","observed":{"default_branch":"master","archived":false},"disposition":{"state":"consolidate-then-retire","review_by":"2026-12-02","destination":{"kind":"repository","name":"coven-code"}},"security_support":"unsupported"},{"name":"coven-design-system","lifecycle":"deprecated","canonicality":"none","risk_class":"R2","purpose":"Overlapping design-system experiment whose useful work should be extracted without retaining a canonical claim.","disposition":{"state":"consolidate-then-retire","review_by":"2026-12-02","destination":{"kind":"portfolio","names":["brand","ui","coven-cave"]}},"security_support":"unsupported"},{"name":"coven-docs","lifecycle":"active","canonicality":"canonical","risk_class":"R1","purpose":"Public documentation and generated compatibility presentation.","canonical_domains":["knowledge.public-documentation","knowledge.compatibility-presentation"],"does_not_own":["protocol.normative-artifacts"],"security_support":"active"},{"name":"coven-github-webhook","lifecycle":"deprecated","canonicality":"none","risk_class":"R3","purpose":"Noncanonical GitHub delivery bundle pending consolidation into the private delivery overlay.","disposition":{"state":"consolidate-then-retire","review_by":"2026-12-02","destination":{"kind":"private-overlay","id":"github-delivery"}}},{"name":"coven-landing","lifecycle":"active","canonicality":"supporting","risk_class":"R1","purpose":"Public marketing and product landing surface.","does_not_own":["brand.public-web-profile","knowledge.public-documentation"],"security_support":"active"},{"name":"coven-memory","lifecycle":"active","canonicality":"canonical","risk_class":"R3","purpose":"Read-only memory client and projection; never a second memory authority.","canonical_domains":["memory.read-only-client","memory.projection"],"does_not_own":["memory.authoritative-state","runtime.persistence"],"security_support":"active"},{"name":"coven-pocket","lifecycle":"maintenance","canonicality":"none","risk_class":"R3","purpose":"Mobile experiment pending a distinct boundary or consolidation into Cave mobile.","disposition":{"state":"evaluate-consolidation-or-private-incubation","review_by":"2026-12-02","destination":{"kind":"repository","name":"coven-cave"}}},{"name":"coven-reach","lifecycle":"maintenance","canonicality":"none","risk_class":"R3","purpose":"Filesystem/network capability experiment pending redesign around explicit leases and authorization.","disposition":{"state":"private-incubation-or-retire","review_by":"2026-12-02","destination":{"kind":"private-overlay","id":"leased-capability-execution"}}},{"name":"coven-runtimes","lifecycle":"active","canonicality":"canonical","risk_class":"R4","purpose":"Runtime capability descriptors, registry, and conformance.","canonical_domains":["runtime.capability-descriptors","runtime.conformance"],"does_not_own":["runtime.execution","runtime.persistence"],"security_support":"active"},{"name":"coven-scout","lifecycle":"maintenance","canonicality":"none","risk_class":"R3","purpose":"Overlapping filesystem/web capability experiment pending selection of one hardened successor.","disposition":{"state":"private-incubation-or-retire","review_by":"2026-12-02","destination":{"kind":"private-overlay","id":"leased-capability-execution"}}},{"name":"coven-threads","lifecycle":"active","canonicality":"canonical","risk_class":"R4","purpose":"Protected-surface authorization and proposal-versus-commit decisions.","canonical_domains":["authority.protected-surface","authority.proposal-commit-decisions"],"does_not_own":["runtime.persistence","orchestration.protocol"],"security_support":"active"},{"name":"demo-workspace","lifecycle":"incubating","canonicality":"supporting","risk_class":"R1","purpose":"Minimal public demonstration and deterministic fixture workspace.","disposition":{"state":"graduate-or-retire","review_by":"2026-10-03"},"security_support":"unsupported"},{"name":"desktop-use","lifecycle":"maintenance","canonicality":"none","risk_class":"R3","purpose":"Desktop capability experiment pending product-boundary review.","disposition":{"state":"evaluate-consolidation-or-private-incubation","review_by":"2026-12-02","destination":{"kind":"repository","name":"coven-cave"}}},{"name":"familiar-contract","lifecycle":"active","canonicality":"canonical","risk_class":"R4","purpose":"Governed portable familiar identity, principal binding, and revision semantics.","canonical_domains":["identity.familiar-contract","identity.principal-binding","identity.revision-semantics"],"does_not_own":["orchestration.protocol","runtime.persistence"],"security_support":"active"},{"name":"homebrew-tap","lifecycle":"active","canonicality":"supporting","risk_class":"R4","purpose":"Canonical Homebrew distribution channel for released OpenCoven artifacts.","does_not_own":["release.approval","artifact.provenance-source"],"security_support":"active"},{"name":"open-fable","lifecycle":"incubating","canonicality":"none","risk_class":"R2","purpose":"Speculative research experiment without an approved public canonical boundary.","disposition":{"state":"private-incubation-or-retire","review_by":"2026-10-03","destination":{"kind":"private-overlay","id":"research-incubation"}},"security_support":"unsupported"},{"name":"open-meow-sdk","lifecycle":"archived","canonicality":"historical","risk_class":"R1","purpose":"Archived predecessor SDK retained for provenance.","observed":{"default_branch":"main","archived":true},"disposition":{"state":"retain-archive","review_by":"2027-09-03","destination":{"kind":"repository","name":"sdk"}},"agent_manifest":{"status":"exempt","path":"agent/manifest.json"},"security_support":"historical"},{"name":"opencoven-beta-august-hackathon-2026","lifecycle":"maintenance","canonicality":"historical","risk_class":"R1","purpose":"Time-bounded hackathon record pending archival verification.","disposition":{"state":"archive-after-retirement-gate","review_by":"2026-10-03"},"security_support":"historical"},{"name":"opencoven-chat-api","lifecycle":"deprecated","canonicality":"none","risk_class":"R3","purpose":"Misnamed documentation retrieval service pending accurate consolidation.","disposition":{"state":"consolidate-then-retire","review_by":"2026-12-02","destination":{"kind":"portfolio","names":["coven-docs","coven-cave"]}}},{"name":"psyche","lifecycle":"active","canonicality":"canonical","risk_class":"R4","purpose":"Orchestration semantics: tasks, lanes, leases, approvals, receipts, retries, and recovery.","canonical_domains":["orchestration.tasks","orchestration.lanes","orchestration.leases","orchestration.approvals","orchestration.receipts","orchestration.recovery"],"does_not_own":["familiar.identity","runtime.persistence","product.production-ui"],"security_support":"active"},{"name":"psyche-build","lifecycle":"active","canonicality":"canonical","risk_class":"R3","purpose":"Multi-lane coding cockpit consuming Psyche canonically.","canonical_domains":["product.coding-cockpit"],"does_not_own":["orchestration.protocol","runtime.persistence"],"security_support":"active"},{"name":"sdk","lifecycle":"active","canonicality":"canonical","risk_class":"R3","purpose":"Constrained public clients and canonical language bindings.","canonical_domains":["access.public-sdk","access.canonical-bindings"],"does_not_own":["runtime.persistence","protected.authorization"],"security_support":"active"},{"name":"ui","lifecycle":"active","canonicality":"specimen","risk_class":"R1","purpose":"Specimen and component laboratory; not production UI authority.","does_not_own":["product.production-ui","brand.public-web-profile"]}]} diff --git a/initiatives/README.md b/initiatives/README.md new file mode 100644 index 0000000..6895c4a --- /dev/null +++ b/initiatives/README.md @@ -0,0 +1,26 @@ +# Cross-repository initiatives + +An initiative is the canonical organization-level record for **why coordinated work exists, who is accountable, which repositories own the implementation, what it depends on, and how completion is proven**. + +It is not a duplicate task database. Implementation issues, pull requests, tests, migrations, and release evidence remain in their owning repositories. GitHub Projects may render these records and linked issues, but the Project is a view rather than an independent authority. + +## Lifecycle + +`proposed → active → verifying → completed` + +Alternative terminal states are `superseded` and `cancelled`. A completed initiative must have evidence for every exit criterion. A status change cannot authorize a protected OpenCoven mutation or release. + +## Required fields + +Each `*.json` record must name: + +- one decision owner; +- one technical DRI; +- one outcome and explicit non-goals; +- participating repository workstreams; +- cross-initiative dependencies; +- exit criteria and evidence state; +- accepted or proposed organization ADRs; +- a review date. + +Use `schemas/initiative.schema.json` and validate with `./scripts/agent-check fast`. diff --git a/initiatives/brand-ui-consolidation.json b/initiatives/brand-ui-consolidation.json new file mode 100644 index 0000000..c5cde67 --- /dev/null +++ b/initiatives/brand-ui-consolidation.json @@ -0,0 +1,92 @@ +{ + "$schema": "../schemas/initiative.schema.json", + "schema_version": "opencoven.initiative/v1", + "id": "brand-ui-consolidation", + "title": "Consolidate Brand, UI, and production design authority", + "status": "active", + "priority": "P1", + "decision_owner": "BunsDev", + "technical_dri": "BunsDev", + "ownership_status": "bootstrap-single-owner", + "outcome": "Preserve one canonical Brand profile, keep UI as a mechanically pinned specimen laboratory, and keep Cave authoritative for production component behavior.", + "non_goals": [ + "Creating another canonical token/profile root.", + "Treating a design mock or specimen as production behavior.", + "Removing historical evidence before consumers migrate." + ], + "decisions": [ + "ADR-0001", + "ADR-0002" + ], + "dependencies": [ + "organization-governance-plane-v1" + ], + "workstreams": [ + { + "repository": "brand", + "responsibility": "Own the sole normative visual identity, voice, and versioned public-web profile.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "ui", + "responsibility": "Consume an immutable Brand profile and remain explicitly noncanonical for production behavior.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "coven-cave", + "responsibility": "Own production component behavior and downstream acceptance evidence.", + "issues": [], + "state": "planned" + }, + { + "repository": "coven-design-system", + "responsibility": "Extract non-duplicative work and retire its canonical claim after consumer migration.", + "issues": [], + "state": "planned" + }, + { + "repository": ".github", + "responsibility": "Record cross-repository resolution, dependency, and administrative protection gates.", + "issues": [ + "OpenCoven/.github#6" + ], + "state": "in-progress" + } + ], + "exit_criteria": [ + { + "id": "EC-01", + "statement": "Brand exposes one versioned normative public profile.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-02", + "statement": "UI pins and verifies the Brand profile without claiming production authority.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-03", + "statement": "Cave passes production UI/brand canaries.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-04", + "statement": "Useful design-system work is migrated with provenance and duplicate authority is retired.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-05", + "statement": "Required reviews and checks protect canonical Brand and production UI paths.", + "state": "open", + "evidence": [] + } + ], + "review_by": "2026-10-03", + "authority_boundary": "This initiative coordinates responsibility and evidence. It does not grant protected identity, authorization, runtime, persistence, release, publication, or GitHub-administration authority." +} diff --git a/initiatives/familiar-identity-continuity-v1.json b/initiatives/familiar-identity-continuity-v1.json new file mode 100644 index 0000000..2e95024 --- /dev/null +++ b/initiatives/familiar-identity-continuity-v1.json @@ -0,0 +1,120 @@ +{ + "$schema": "../schemas/initiative.schema.json", + "schema_version": "opencoven.initiative/v1", + "id": "familiar-identity-continuity-v1", + "title": "Familiar identity, continuity, authority, and session-binding conformance", + "status": "active", + "priority": "P0", + "decision_owner": "BunsDev", + "technical_dri": "BunsDev", + "ownership_status": "bootstrap-single-owner", + "outcome": "Ensure every familiar session binds an exact authorized familiar root and revision, protected identity transitions are principal-authorized and atomic, and continuity can be reconstructed without introducing a second identity root.", + "non_goals": [ + "Making SPAR an identity database or IAM replacement.", + "Allowing Psyche, Cave, task text, model output, or caller fields to create protected authority.", + "Collapsing structural, runtime, continuity, privacy, and interoperability evidence into one generic compliance claim." + ], + "decisions": [ + "ADR-0001", + "ADR-0002" + ], + "dependencies": [ + "organization-governance-plane-v1" + ], + "workstreams": [ + { + "repository": "familiar-contract", + "responsibility": "Define familiar root, principal binding, revision, sameness, transition, and retirement semantics.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "coven-threads", + "responsibility": "Authorize protected transitions with replay, revocation, recovery, and proposal-versus-commit semantics.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "psyche", + "responsibility": "Snapshot authorized identity into orchestration without redefining it.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "coven", + "responsibility": "Bind direct and orchestrated sessions and atomically verify/apply protected transitions.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "coven-runtimes", + "responsibility": "Express runtime capabilities and conformance without overstating security properties.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "sdk", + "responsibility": "Expose constrained read/verify/subscribe bindings before mutation surfaces.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "coven-memory", + "responsibility": "Remain a read-only projection and prove mutation is impossible through its contract.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "coven-cave", + "responsibility": "Make revisions, pending proposals, provenance, active embodiments, staleness, and uncertainty legible.", + "issues": [], + "state": "in-progress" + } + ], + "exit_criteria": [ + { + "id": "EC-01", + "statement": "Cross-repository identity ownership is ratified.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-02", + "statement": "Stable familiar-root and same-familiar/fork/succession semantics are normative.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-03", + "statement": "Principal authorization is cryptographically verifiable and replay resistant.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-04", + "statement": "Every direct and orchestrated session pins an exact authorized root and revision.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-05", + "statement": "Final authorization verification and commit are atomic or use one immutable snapshot.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-06", + "statement": "Transition receipts, content-addressed history, privacy lifecycle, and replica revocation have golden vectors.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-07", + "statement": "Conformance profiles are reported separately against immutable artifacts.", + "state": "open", + "evidence": [] + } + ], + "review_by": "2026-10-03", + "authority_boundary": "This initiative coordinates responsibility and evidence. It does not grant protected identity, authorization, runtime, persistence, release, publication, or GitHub-administration authority." +} diff --git a/initiatives/organization-governance-plane-v1.json b/initiatives/organization-governance-plane-v1.json new file mode 100644 index 0000000..37b9777 --- /dev/null +++ b/initiatives/organization-governance-plane-v1.json @@ -0,0 +1,89 @@ +{ + "$schema": "../schemas/initiative.schema.json", + "schema_version": "opencoven.initiative/v1", + "id": "organization-governance-plane-v1", + "title": "Activate the OpenCoven public organization governance plane", + "status": "active", + "priority": "P0", + "decision_owner": "BunsDev", + "technical_dri": "BunsDev", + "ownership_status": "bootstrap-single-owner", + "outcome": "Establish one reviewed, machine-readable public plane for repository ownership, lifecycle, cross-repository outcomes, shared policy, drift detection, and generated views without creating a competing implementation authority.", + "non_goals": [ + "Reimplementing component behavior in .github.", + "Publishing private repository inventory or confidential operational data.", + "Treating policy text as proof that GitHub settings or OpenCoven runtime controls are enforced." + ], + "decisions": [ + "ADR-0001", + "ADR-0002", + "ADR-0003" + ], + "dependencies": [], + "workstreams": [ + { + "repository": ".github", + "responsibility": "Own schemas, registry, ADRs, policies, deterministic validation, generated views, and reusable read-only workflows.", + "issues": [ + "OpenCoven/.github#5" + ], + "state": "in-progress" + }, + { + "repository": ".github", + "responsibility": "Track and evidence organization-setting hardening as a separate administrative gate.", + "issues": [ + "OpenCoven/.github#6" + ], + "state": "blocked-on-admin-application" + }, + { + "repository": ".github", + "responsibility": "Layer advanced automation conformance and compatibility workflows on the base governance contract.", + "issues": [ + "OpenCoven/.github#2" + ], + "state": "planned" + } + ], + "exit_criteria": [ + { + "id": "EC-01", + "statement": "ADR-0001 is ratified through reviewed merge.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-02", + "statement": "Fast deterministic validation passes from a clean clone.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-03", + "statement": "Generated views exactly match authoritative records.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-04", + "statement": "Current public GitHub inventory reconciles without undeclared drift.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-05", + "statement": "Administrative branch, Actions, app, environment, and break-glass controls are independently evidenced.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-06", + "statement": "At least two canonical repositories consume the reusable agent-readiness workflow at an immutable revision.", + "state": "open", + "evidence": [] + } + ], + "review_by": "2026-10-03", + "authority_boundary": "This initiative coordinates responsibility and evidence. It does not grant protected identity, authorization, runtime, persistence, release, publication, or GitHub-administration authority." +} diff --git a/initiatives/public-portfolio-consolidation-2026.json b/initiatives/public-portfolio-consolidation-2026.json new file mode 100644 index 0000000..d180954 --- /dev/null +++ b/initiatives/public-portfolio-consolidation-2026.json @@ -0,0 +1,104 @@ +{ + "$schema": "../schemas/initiative.schema.json", + "schema_version": "opencoven.initiative/v1", + "id": "public-portfolio-consolidation-2026", + "title": "Reduce public portfolio ambiguity and duplicate canonical ownership", + "status": "active", + "priority": "P0", + "decision_owner": "BunsDev", + "technical_dri": "BunsDev", + "ownership_status": "bootstrap-single-owner", + "outcome": "Move every public repository toward retain, consolidate, private incubation, archive, or retirement using reversible evidence gates and zero broken references.", + "non_goals": [ + "Deleting repositories before package, release, installer, webhook, domain, and dependency references are cleared.", + "Moving useful implementation without provenance.", + "Using repository count alone as a success metric." + ], + "decisions": [ + "ADR-0001", + "ADR-0003" + ], + "dependencies": [ + "organization-governance-plane-v1" + ], + "workstreams": [ + { + "repository": ".github", + "responsibility": "Maintain disposition records, retirement procedure, drift evidence, and portfolio exit criteria.", + "issues": [ + "OpenCoven/.github#5" + ], + "state": "in-progress" + }, + { + "repository": "coven-code", + "responsibility": "Receive non-duplicative coding adapter, fixture, and workflow value from legacy coding repositories.", + "issues": [], + "state": "planned" + }, + { + "repository": "brand", + "responsibility": "Retain canonical brand-profile authority during design-system consolidation.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "ui", + "responsibility": "Remain a specimen lab and consume Brand without claiming production authority.", + "issues": [], + "state": "in-progress" + }, + { + "repository": "coven-cave", + "responsibility": "Own production UI/mobile behavior and evaluate overlapping product experiments.", + "issues": [], + "state": "planned" + }, + { + "repository": "coven-docs", + "responsibility": "Receive accurately named documentation retrieval ownership where appropriate.", + "issues": [], + "state": "planned" + } + ], + "exit_criteria": [ + { + "id": "EC-01", + "statement": "Every public repository has a reviewed lifecycle and disposition.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-02", + "statement": "No public canonical domain has more than one owner.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-03", + "statement": "Each consolidation preserves licenses, provenance, issues, releases, and useful history.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-04", + "statement": "Each archive or retirement passes the reference, package, release, installer, domain, and rollback gate.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-05", + "statement": "No visibility change, archive, transfer, or deletion occurs without explicit authorization and recorded evidence.", + "state": "open", + "evidence": [] + }, + { + "id": "EC-06", + "statement": "Generated portfolio and live GitHub inventory agree.", + "state": "open", + "evidence": [] + } + ], + "review_by": "2026-10-03", + "authority_boundary": "This initiative coordinates responsibility and evidence. It does not grant protected identity, authorization, runtime, persistence, release, publication, or GitHub-administration authority." +} diff --git a/policies/administration-and-recovery.md b/policies/administration-and-recovery.md new file mode 100644 index 0000000..6b24aa3 --- /dev/null +++ b/policies/administration-and-recovery.md @@ -0,0 +1,37 @@ +# GitHub administration, automation, and recovery + +## Separation of roles + +Use separate identities and scopes: + +- **drift observer**: repository metadata/content read plus issue write only in `.github` for one deduplicated report; +- **administrative reconciler**: no standing personal token; a narrowly scoped GitHub App token minted only in a protected environment after reviewed approval; +- **human administrators**: accountable organization owners with hardware-backed MFA and documented break-glass custody. + +The observer must never change repository settings. The reconciler must run plan-first, bind application to the reviewed immutable plan and current settings snapshot, and stop on moved state. + +## Least privilege + +- Default Actions permissions to read-only. +- Grant write permissions per job only when required. +- Prefer GitHub Apps and OIDC over classic PATs or long-lived secrets. +- Restrict repository creation, transfer, visibility, archive, deletion, rulesets, Apps, and environment administration. +- Keep secrets and privileged runners unavailable to untrusted fork pull requests. +- Pin third-party Actions by full commit SHA and review updates. +- Protect workflow, schema, registry, decision, security, release, and migration paths with CODEOWNERS and rulesets. + +## Administrative evidence + +Policy files do not prove settings are applied. Close an administrative control only with exported settings or API snapshots, exact ruleset/environment identifiers, app scope inventory, and positive/negative test evidence. + +## Break-glass + +- Maintain at least two custodians when staffing permits. +- Store recovery material outside GitHub using an approved secure process. +- Limit bypass to named emergencies and record every use. +- Require post-event review, credential rotation where relevant, and expiry of temporary access. +- Test organization ownership recovery, App revocation, repository export, ruleset reconstruction, and critical release-channel recovery. + +## Backup and portability + +Regularly export the `.github` Git repository, accepted ADRs, schemas, registry, ruleset snapshots, app inventory, and Projects/issue mappings. Do not treat generated views as the only backup. Recovery must reconstruct authoritative inputs first and regenerate derived state. diff --git a/policies/agent-authored-changes.md b/policies/agent-authored-changes.md new file mode 100644 index 0000000..6c7d5b9 --- /dev/null +++ b/policies/agent-authored-changes.md @@ -0,0 +1,50 @@ +# Human and AI-agent change policy + +OpenCoven welcomes agent-assisted work, but agent output is untrusted until reviewed and verified against the relevant authority boundary. + +## Canonical discovery + +Before proposing a change, an agent must: + +1. read the repository's root `AGENTS.md` and any scoped instructions; +2. inspect the public registry and relevant organization ADRs; +3. inspect the owning repository's current code, schemas, tests, CI, release evidence, and local ADRs; +4. identify produced and consumed contracts and their immutable revisions; +5. determine whether the request touches an R3/R4 boundary or an externally consequential action. + +An agent must challenge a new repository, service, schema, database, or control plane when an existing canonical component should own it. + +## Default authority by risk + +- R0/R1: autonomous branch and pull request after deterministic verification. +- R2: proposal plus migration/fixture evidence; no unattended application to user state. +- R3: approval-gated execution with least-privilege credentials and bounded side effects. +- R4: human-approved plan, protected-owner review, exact-state binding, and explicit operation authorization. + +These defaults constrain agent action. They do not confer authority on the agent. + +## Required evidence + +Every material agent-authored pull request must provide: + +- objective, acceptance criteria, and non-goals; +- exact authoritative sources and revisions consulted; +- files intentionally touched and protected paths affected; +- security, privacy, authority, compatibility, and lifecycle impact; +- exact commands, tests, results, and unsupported/skipped checks; +- migration, rollback, and failure-state behavior; +- generated artifacts and provenance; +- cross-repository canaries where contracts change; +- unresolved uncertainty and required administrative actions. + +## Prohibited shortcuts + +Agents must not: + +- interpret task text or model output as protected approval; +- weaken verification, ownership, release, provenance, or security gates to make CI pass; +- run privileged workflows on untrusted pull-request code; +- expose credentials or private data in logs, artifacts, issues, or public governance files; +- silently overwrite unrelated work or intentionally dirty/reference-only worktrees; +- report a source-only test as proof of a packaged or real-daemon boundary; +- claim implementation, test, security, privacy, conformance, or settings state without evidence. diff --git a/policies/authority-boundaries.md b/policies/authority-boundaries.md new file mode 100644 index 0000000..5e0ff07 --- /dev/null +++ b/policies/authority-boundaries.md @@ -0,0 +1,43 @@ +# Authority boundaries + +## Rule + +The governance plane records organization intent, ownership, coordination, and evidence. It is not an OpenCoven runtime authority and it is not sufficient authorization for a protected operation. + +No prompt, task, issue, pull request description, Project field, roadmap status, ADR, registry entry, agent output, or caller-supplied claim may grant itself authority. + +## Canonical enforcement owners + +- Familiar Contract defines the governed familiar identity and principal-binding semantics. +- Coven Threads decides protected authorization and proposal-versus-commit outcomes. +- Psyche governs orchestration objects such as tasks, lanes, leases, approvals, receipts, retries, and recovery. +- Coven owns daemon authority, persistence, sessions, runtime execution, and authoritative state transitions. +- Repository and organization rulesets govern GitHub administration. +- Release and publication systems govern their own approval and commit boundaries. + +The final verification and commit for a protected operation must be atomic or use the same immutable snapshot. A pending proposal must never be rendered as committed state. + +## Governance states + +Use precise state labels: + +- **specified**: documented, not necessarily implemented; +- **implemented**: code or configuration exists; +- **verified**: evidence shows the named behavior under the named conditions; +- **administratively applied**: an authorized GitHub or service administrator applied the setting; +- **operationally effective**: recurring testing shows the control continues to work; +- **proposed**, **experimental**, **degraded**, **stale**, **reconciling**, **rejected**, and **unavailable** where applicable. + +Never collapse these into a generic “complete,” “secure,” or “compliant” claim. + +## Agent behavior + +When requested to perform a protected change without authenticated authority, an agent must: + +1. preserve the request as a proposal; +2. identify the canonical authority and required evidence; +3. avoid side effects; +4. surface stale, missing, moved, or contradictory state; +5. reject the operation when degradation to a proposal would itself be unsafe. + +Prefer **Permit / Degrade to Proposal / Reject**. diff --git a/policies/evidence-and-verification.md b/policies/evidence-and-verification.md new file mode 100644 index 0000000..2a93399 --- /dev/null +++ b/policies/evidence-and-verification.md @@ -0,0 +1,47 @@ +# Evidence and verification policy + +## Evidence hierarchy + +Strong evidence is source-adjacent, exact, reproducible, and bound to the state being evaluated. Prefer: + +1. accepted schemas and normative artifacts; +2. immutable source and dependency revisions; +3. deterministic tests and negative vectors; +4. packed artifact, signed release, or real-daemon results where the boundary requires them; +5. GitHub settings/ruleset/API snapshots for administrative controls; +6. machine-readable receipts with command, environment, result, and provenance. + +Narrative summaries, badges, dashboards, and model conclusions are derived evidence and must link to the underlying result. + +## Required distinctions + +Report independently: + +- structural validity; +- repository verification; +- runtime authority behavior; +- continuity behavior; +- privacy behavior; +- interoperability behavior; +- packaged/release artifact verification; +- administrative-control application; +- operational control effectiveness. + +Never collapse partial results into a generic “compliant” or “secure” label. + +## Control-effectiveness testing + +A declared control is effective only when: + +- the enforcing mechanism is identified; +- the intended and negative paths are tested; +- bypass and administrator behavior are known; +- the evidence names exact revisions and settings; +- drift is detected on a defined cadence; +- stale, degraded, or unavailable evidence is represented explicitly. + +## Evidence packets + +Use `schemas/evidence-packet.schema.json`. Evidence packets are append-only review artifacts for a named change. Corrections create a new revision or superseding packet rather than erasing prior evidence. + +Do not place secrets, personal data, private prompts, private memories, raw terminal history, or embargoed findings in public evidence. diff --git a/policies/exceptions.md b/policies/exceptions.md new file mode 100644 index 0000000..1cdc8fa --- /dev/null +++ b/policies/exceptions.md @@ -0,0 +1,23 @@ +# Exceptions and temporary waivers + +Exceptions are a controlled escape hatch, not a parallel policy system. + +Every exception must be recorded in `governance/exceptions.json` and include: + +- unique identifier and affected control; +- narrow scope and exact repositories/paths where public; +- owner and approving authority; +- rationale and risk assessment; +- compensating controls; +- creation and expiry dates; +- required remediation and verification; +- status: `proposed`, `active`, `expired`, `closed`, or `revoked`. + +Rules: + +1. No exception may grant familiar identity, protected mutation, runtime, release, publication, or organization-administration authority. +2. R4 exceptions require protected-owner review and an explicit, operation-specific authorization path. +3. An active exception must expire within 90 days unless a stricter control applies. +4. Expired active exceptions fail CI. +5. An exception cannot suppress evidence of drift; it may only explain a reviewed and bounded deviation. +6. Closing an exception requires evidence that the control is restored or the policy was superseded through an ADR. diff --git a/policies/initiatives-and-decisions.md b/policies/initiatives-and-decisions.md new file mode 100644 index 0000000..9dfcfce --- /dev/null +++ b/policies/initiatives-and-decisions.md @@ -0,0 +1,35 @@ +# Initiative and decision procedure + +## Cross-repository initiatives + +Create an initiative when one outcome requires work in more than one canonical repository or requires an organization-level ownership, lifecycle, compatibility, or sequencing decision. + +Each initiative has one decision owner, one technical DRI, explicit workstream owners, dependencies, non-goals, review date, and evidence-backed exit criteria. Avoid percent-complete fields. Derive operational status from linked issues and immutable evidence where possible. + +Implementation tasks remain in their owning repositories. The central initiative links them and defines the shared outcome; it does not copy their mutable task descriptions. + +## Status changes + +- `proposed`: scope and ownership under review; +- `active`: accepted outcome with work in progress; +- `verifying`: implementation is present and exit evidence is being assembled; +- `completed`: every criterion has exact evidence and no unresolved blocking risk; +- `superseded`: another initiative owns the outcome; +- `cancelled`: intentionally stopped with rationale and residual-risk disposition. + +A status change requires a pull request. `completed` without evidence must fail validation. + +## ADR placement + +Place an ADR here only when it changes organization-spanning ownership, compatibility, lifecycle, sequencing, public/private boundary, or governance invariants. Keep component design and implementation ADRs in the owning repository. + +Accepted ADRs are immutable historical records. Amend by a new ADR that supersedes or narrows the prior decision; do not silently rewrite the old rationale. + +## Conflict resolution + +1. Stop any protected or irreversible action affected by the conflict. +2. Identify the canonical owner using the registry and current implementation evidence. +3. Gather exact repository revisions, contracts, tests, and settings snapshots. +4. Let the relevant decision owner resolve scope; require canonical protected authority for any protected operation. +5. Record the decision and migrate or deprecate conflicting surfaces. +6. Add a regression check where the conflict could recur mechanically. diff --git a/policies/public-private-data.md b/policies/public-private-data.md new file mode 100644 index 0000000..755f1be --- /dev/null +++ b/policies/public-private-data.md @@ -0,0 +1,35 @@ +# Public and private governance data + +`OpenCoven/.github` is public. Public transparency is useful only when it does not disclose private inventory, security material, personal data, or operational secrets. + +## Public data allowed here + +- public repository names and observed public GitHub metadata; +- public canonical-domain ownership, lifecycle, risk class, and disposition; +- public cross-repository initiatives and non-sensitive dependency relationships; +- organization policies, schemas, reusable read-only workflows, and generated views; +- public GitHub identities serving as owners or DRIs; +- links to public issues, pull requests, releases, and evidence. + +## Data prohibited here + +- private repository names or confidential product codenames unless separately approved for publication; +- credentials, tokens, secret values, recovery material, private endpoints, or internal network detail; +- non-public vulnerability reports, exploit detail, embargo status, or incident evidence; +- prompts, memories, conversation transcripts, terminal logs, private file paths, session identifiers, or user data; +- private contractual, commercial, employment, legal, partnership, or financial records; +- unnecessary personal information, contact data, behavioral profiles, or contributor metadata. + +## Private federation + +Private repositories should carry repository-local `agent/manifest.json` records conforming to the public schema. Access-controlled Projects or a future approved private projection may aggregate those manifests. + +A public initiative may use an opaque reference such as `private-overlay: github-delivery` to acknowledge a private workstream. It must not reveal the backing repository, members, incidents, or implementation details. + +## Privacy principles + +- Minimize collected data and fields. +- Use stable public GitHub identities only where accountability requires them. +- Avoid copying issue or commit personal data into derived governance records. +- Retain accepted decisions and contribution history as public open-source records, but expire temporary exceptions and operational details. +- Never claim GDPR, CCPA, ISO, SOC 2, or another compliance status based solely on this policy. diff --git a/policies/repository-lifecycle.md b/policies/repository-lifecycle.md new file mode 100644 index 0000000..226bb67 --- /dev/null +++ b/policies/repository-lifecycle.md @@ -0,0 +1,47 @@ +# Repository lifecycle and creation policy + +## Lifecycle states + +The machine-readable state machine is in `governance/lifecycle.json`. + +- **incubating**: experimental, time-bounded, and not a canonical public dependency; +- **active**: actively developed with an accountable owner and verification path; +- **maintenance**: supported with limited change and an explicit review cadence; +- **deprecated**: successor or retirement plan exists; no new canonical surface; +- **archived**: read-only historical record; +- **tombstone**: minimal successor/provenance pointer after an approved retirement. + +Canonicality is separate from lifecycle. An active repository may be supporting or specimen-only; an archived repository is historical and cannot retain a current canonical claim. + +## New repository gate + +Before a repository is created or made public, record a proposal that answers: + +1. Which existing canonical component was evaluated, and why can it not own this work? +2. What unique domain, product, distribution, or experiment boundary justifies a repository? +3. Who is the owner, technical DRI, and successor if the owner becomes unavailable? +4. What lifecycle, risk class, visibility, license, security support, and data classification apply? +5. What bootstrap, fast verification, release, archival, and rollback procedures exist? +6. Which contracts are produced and consumed, and how are they versioned and pinned? +7. What is the 30- or 90-day graduation/retirement criterion? + +Repository creation metadata cannot authorize the runtime or protected behavior implemented inside it. + +## Public graduation gate + +An incubating repository may become active public only when: + +- its canonicality and non-goals are reviewed; +- no canonical public domain conflicts exist; +- a root agent guide or equivalent route exists; +- deterministic bootstrap and fast verification work from a clean clone; +- security policy, license, contribution provenance, and release status are truthful; +- R3/R4 paths have protected ownership and evidence requirements; +- downstream consumers use immutable contract or artifact references where applicable; +- the governance registry and live GitHub metadata agree. + +## Review and succession + +Every active public repository has one owner and one technical DRI. During the bootstrap-single-owner phase, `BunsDev` may hold both roles, but the registry must not imply healthy separation of duties. Each R3/R4 repository should add a qualified backup reviewer before claiming mature governance. + +Ownership changes require a reviewed registry change recording the effective date, outgoing and incoming accountable identities, unresolved risk, and transition evidence. Git history provides provenance; do not erase prior ownership records. diff --git a/policies/repository-retirement.md b/policies/repository-retirement.md new file mode 100644 index 0000000..852343c --- /dev/null +++ b/policies/repository-retirement.md @@ -0,0 +1,30 @@ +# Repository consolidation, archival, and retirement + +Repository removal is an externally consequential and often irreversible operation. A registry disposition is a plan, not authorization to archive, transfer, privatize, or delete. + +## Required sequence + +1. **Inventory references** across source, docs, workflows, package manifests, submodules, badges, domains, webhooks, GitHub Apps, release scripts, update feeds, and installation instructions. +2. **Inventory distribution** across npm, crates.io, PyPI, Maven, SwiftPM, Homebrew, containers, downloadable artifacts, checksums, attestations, and evergreen URLs. +3. **Preserve provenance**: tags, releases, issues, discussions, advisories, licenses, notices, contributor history, and any legally required records. +4. **Select destination** and migrate only non-duplicative value with traceable commits or documented extraction receipts. +5. **Publish a successor notice** that distinguishes current canonical behavior from historical material. +6. **Archive first** for an observation period unless a documented legal or security exception requires another path. +7. **Monitor breakage**: failed downloads, inbound links, package use, installer/update requests, and new issues. +8. **Apply the final action** only with explicit authorization, an immutable reviewed plan, current-state revalidation, and rollback evidence. + +## Retirement evidence packet + +Must include: + +- repository and exact head revision; +- proposed destination or tombstone; +- reference and package searches performed; +- release/download/update-channel findings; +- legal/license/provenance preservation; +- user migration and communications plan; +- observation start/end and monitoring results; +- rollback archive and recovery procedure; +- authorized administrator and exact action receipt. + +Deletion is not the default. Prefer consolidation plus archive/tombstone when historical links, releases, citations, or provenance remain valuable. diff --git a/policies/security-and-supply-chain.md b/policies/security-and-supply-chain.md new file mode 100644 index 0000000..51dbbf0 --- /dev/null +++ b/policies/security-and-supply-chain.md @@ -0,0 +1,32 @@ +# Security and software-supply-chain policy + +## Threat model + +The governance plane is a high-impact target because compromised policy, workflows, manifests, or generated compatibility data can misroute reviewers, weaken checks, or induce downstream repositories to trust the wrong artifact. It remains metadata unless backed by enforcement, but metadata compromise can still create substantial operational harm. + +Threats include: + +- compromised maintainer or organization-owner accounts; +- malicious or vulnerable third-party Actions; +- untrusted pull-request code reaching secrets or privileged runners; +- workflow modification followed by self-approval; +- mutable dependency/action references; +- forged ownership, conformance, release, or evidence records; +- stale registry state and confused-deputy automation; +- log/artifact disclosure of private or sensitive data; +- supply-chain substitution between source, generated code, package, and release artifact. + +## Baseline controls + +- Require protected pull-request review and CODEOWNER approval for R4 paths. +- Pin third-party Actions to full commit SHAs. +- Keep workflow permissions explicit and read-only by default. +- Separate build/test from privileged publish/reconcile jobs and environments. +- Use dependency review, secret scanning, lockfiles, reproducible generation, SBOMs, checksums, provenance/attestations, and signing where the owning repository's release model supports them. +- Verify immutable producer artifacts before consumer canaries. +- Run negative vectors for malformed, downgraded, moved, stale, replayed, and unauthorized inputs. +- Treat SLSA, OpenSSF Scorecard, SPDX/CycloneDX, and Sigstore as useful control frameworks and tooling—not automatic proof of product security or certification. + +## Release boundary + +This repository may describe release-train policy but does not approve or publish releases. Release jobs must bind approval to exact source, lockfile, generated output, artifact digest, environment, and conformance evidence. A main-branch unit test is not a substitute for packaged-artifact verification. diff --git a/schemas/agent-manifest.schema.json b/schemas/agent-manifest.schema.json new file mode 100644 index 0000000..83476ec --- /dev/null +++ b/schemas/agent-manifest.schema.json @@ -0,0 +1,210 @@ +{ + "$id": "https://opencoven.ai/schemas/agent-manifest-v1.json", + "title": "OpenCoven repository agent contract", + "type": "object", + "required": [ + "schema_version", + "repository", + "risk", + "agent", + "contracts" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.agent-repo/v1" + }, + "repository": { + "type": "object", + "required": [ + "name", + "lifecycle", + "canonicality", + "canonical_for", + "does_not_own", + "owner", + "technical_dri", + "ownership_status" + ], + "properties": { + "name": { + "type": "string", + "pattern": "^[.A-Za-z0-9_-]+$" + }, + "lifecycle": { + "enum": [ + "incubating", + "active", + "maintenance", + "deprecated", + "archived", + "tombstone" + ] + }, + "canonicality": { + "enum": [ + "canonical", + "supporting", + "specimen", + "historical", + "none" + ] + }, + "canonical_for": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "does_not_own": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "owner": { + "type": "string", + "minLength": 1 + }, + "technical_dri": { + "type": "string", + "minLength": 1 + }, + "ownership_status": { + "enum": [ + "bootstrap-single-owner", + "separated", + "delegated", + "vacant" + ] + } + }, + "additionalProperties": false + }, + "risk": { + "type": "object", + "required": [ + "class", + "protected_paths", + "generated_paths", + "network_policy", + "secrets_policy", + "external_side_effects" + ], + "properties": { + "class": { + "enum": [ + "R0", + "R1", + "R2", + "R3", + "R4" + ] + }, + "protected_paths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "generated_paths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "network_policy": { + "enum": [ + "deny", + "deny-by-default", + "explicit-allowlist", + "required" + ] + }, + "secrets_policy": { + "type": "string", + "minLength": 1 + }, + "external_side_effects": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "agent": { + "type": "object", + "required": [ + "entrypoint", + "bootstrap", + "verify" + ], + "properties": { + "entrypoint": { + "type": "string", + "minLength": 1 + }, + "bootstrap": { + "type": "string", + "minLength": 1 + }, + "verify": { + "type": "object", + "required": [ + "fast", + "full" + ], + "properties": { + "fast": { + "type": "string", + "minLength": 1 + }, + "full": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "contracts": { + "type": "object", + "required": [ + "produces", + "consumes" + ], + "properties": { + "produces": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "consumes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/contracts.schema.json b/schemas/contracts.schema.json new file mode 100644 index 0000000..5da811b --- /dev/null +++ b/schemas/contracts.schema.json @@ -0,0 +1,54 @@ +{ + "$id": "https://opencoven.ai/schemas/contracts-v1.json", + "title": "OpenCoven public contract index", + "type": "object", + "required": [ + "schema_version", + "contracts", + "claim_rule" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.contract-index/v1" + }, + "contracts": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "owner", + "status", + "immutable_release_required" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "owner": { + "type": "string", + "pattern": "^[.A-Za-z0-9_-]+$" + }, + "status": { + "type": "string", + "minLength": 1 + }, + "immutable_release_required": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "claim_rule": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/controls.schema.json b/schemas/controls.schema.json new file mode 100644 index 0000000..56b788e --- /dev/null +++ b/schemas/controls.schema.json @@ -0,0 +1,63 @@ +{ + "$id": "https://opencoven.ai/schemas/controls-v1.json", + "title": "OpenCoven governance controls", + "type": "object", + "required": [ + "schema_version", + "controls" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.controls/v1" + }, + "controls": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "objective", + "evidence", + "enforcement", + "status" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^GOV-[0-9]{3}$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "objective": { + "type": "string", + "minLength": 1 + }, + "evidence": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "enforcement": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/decision-index.schema.json b/schemas/decision-index.schema.json new file mode 100644 index 0000000..8f4dc60 --- /dev/null +++ b/schemas/decision-index.schema.json @@ -0,0 +1,59 @@ +{ + "$id": "https://opencoven.ai/schemas/decision-index-v1.json", + "title": "OpenCoven decision index", + "type": "object", + "required": [ + "schema_version", + "decisions" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.decision-index/v1" + }, + "decisions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "status", + "path", + "date" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^ADR-[0-9]{4}$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "status": { + "enum": [ + "proposed", + "accepted", + "superseded", + "rejected" + ] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "date": { + "type": "string", + "format": "date" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/dependencies.schema.json b/schemas/dependencies.schema.json new file mode 100644 index 0000000..eb7b7e5 --- /dev/null +++ b/schemas/dependencies.schema.json @@ -0,0 +1,50 @@ +{ + "$id": "https://opencoven.ai/schemas/dependencies-v1.json", + "title": "OpenCoven public dependency graph", + "type": "object", + "required": [ + "schema_version", + "edges" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.dependencies/v1" + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "required": [ + "producer", + "consumer", + "relationship", + "required_evidence" + ], + "properties": { + "producer": { + "type": "string", + "pattern": "^[.A-Za-z0-9_-]+$" + }, + "consumer": { + "type": "string", + "pattern": "^[.A-Za-z0-9_-]+$" + }, + "relationship": { + "type": "string", + "minLength": 1 + }, + "required_evidence": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/evidence-packet.schema.json b/schemas/evidence-packet.schema.json new file mode 100644 index 0000000..b68cffd --- /dev/null +++ b/schemas/evidence-packet.schema.json @@ -0,0 +1,169 @@ +{ + "$id": "https://opencoven.ai/schemas/governance-evidence-v1.json", + "title": "OpenCoven governance evidence packet", + "type": "object", + "required": [ + "schema_version", + "change", + "authority", + "sources", + "files", + "verification", + "migration", + "rollback", + "uncertainty" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.governance-evidence/v1" + }, + "change": { + "type": "object", + "required": [ + "objective", + "acceptance_criteria", + "non_goals" + ], + "properties": { + "objective": { + "type": "string", + "minLength": 1 + }, + "acceptance_criteria": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 + }, + "non_goals": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "authority": { + "type": "object", + "required": [ + "risk_class", + "protected_boundaries", + "authorization_effect" + ], + "properties": { + "risk_class": { + "enum": [ + "R0", + "R1", + "R2", + "R3", + "R4" + ] + }, + "protected_boundaries": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "authorization_effect": { + "const": "none-metadata-only" + } + }, + "additionalProperties": false + }, + "sources": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "kind", + "reference", + "revision" + ], + "properties": { + "kind": { + "type": "string", + "minLength": 1 + }, + "reference": { + "type": "string", + "minLength": 1 + }, + "revision": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "files": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "verification": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "command", + "result", + "environment" + ], + "properties": { + "command": { + "type": "string", + "minLength": 1 + }, + "result": { + "enum": [ + "pass", + "fail", + "skipped", + "unsupported" + ] + }, + "environment": { + "type": "string", + "minLength": 1 + }, + "evidence": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "migration": { + "type": "string", + "minLength": 1 + }, + "rollback": { + "type": "string", + "minLength": 1 + }, + "uncertainty": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/exception.schema.json b/schemas/exception.schema.json new file mode 100644 index 0000000..3d6a20c --- /dev/null +++ b/schemas/exception.schema.json @@ -0,0 +1,98 @@ +{ + "$id": "https://opencoven.ai/schemas/exception-set-v1.json", + "title": "OpenCoven governance exceptions", + "type": "object", + "required": [ + "schema_version", + "exceptions" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.exception-set/v1" + }, + "exceptions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "control_id", + "scope", + "owner", + "approver", + "rationale", + "risk", + "compensating_controls", + "created", + "expires", + "status", + "remediation" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "control_id": { + "type": "string", + "minLength": 1 + }, + "scope": { + "type": "string", + "minLength": 1 + }, + "owner": { + "type": "string", + "minLength": 1 + }, + "approver": { + "type": "string", + "minLength": 1 + }, + "rationale": { + "type": "string", + "minLength": 1 + }, + "risk": { + "type": "string", + "minLength": 1 + }, + "compensating_controls": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "created": { + "type": "string", + "format": "date" + }, + "expires": { + "type": "string", + "format": "date" + }, + "status": { + "enum": [ + "proposed", + "active", + "expired", + "closed", + "revoked" + ] + }, + "remediation": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/initiative.schema.json b/schemas/initiative.schema.json new file mode 100644 index 0000000..eb5c532 --- /dev/null +++ b/schemas/initiative.schema.json @@ -0,0 +1,181 @@ +{ + "$id": "https://opencoven.ai/schemas/initiative-v1.json", + "title": "OpenCoven cross-repository initiative", + "type": "object", + "required": [ + "schema_version", + "id", + "title", + "status", + "priority", + "decision_owner", + "technical_dri", + "ownership_status", + "outcome", + "non_goals", + "decisions", + "dependencies", + "workstreams", + "exit_criteria", + "review_by", + "authority_boundary" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.initiative/v1" + }, + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]+$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "status": { + "enum": [ + "proposed", + "active", + "verifying", + "completed", + "superseded", + "cancelled" + ] + }, + "priority": { + "enum": [ + "P0", + "P1", + "P2", + "P3" + ] + }, + "decision_owner": { + "type": "string", + "minLength": 1 + }, + "technical_dri": { + "type": "string", + "minLength": 1 + }, + "ownership_status": { + "enum": [ + "bootstrap-single-owner", + "separated", + "delegated", + "vacant" + ] + }, + "outcome": { + "type": "string", + "minLength": 1 + }, + "non_goals": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "decisions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^ADR-[0-9]{4}$" + } + }, + "dependencies": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]+$" + } + }, + "workstreams": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "repository", + "responsibility", + "issues", + "state" + ], + "properties": { + "repository": { + "type": "string", + "pattern": "^[.A-Za-z0-9_-]+$" + }, + "responsibility": { + "type": "string", + "minLength": 1 + }, + "issues": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "state": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "exit_criteria": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "id", + "statement", + "state", + "evidence" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "statement": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "open", + "met", + "waived" + ] + }, + "evidence": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "review_by": { + "type": "string", + "format": "date" + }, + "authority_boundary": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/lifecycle.schema.json b/schemas/lifecycle.schema.json new file mode 100644 index 0000000..f63716e --- /dev/null +++ b/schemas/lifecycle.schema.json @@ -0,0 +1,30 @@ +{ + "$id": "https://opencoven.ai/schemas/lifecycle-v1.json", + "title": "OpenCoven lifecycle and risk model", + "type": "object", + "required": [ + "schema_version", + "lifecycle_states", + "canonicality_states", + "risk_classes" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.lifecycle/v1" + }, + "lifecycle_states": { + "type": "object" + }, + "canonicality_states": { + "type": "object" + }, + "risk_classes": { + "type": "object" + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/release-trains.schema.json b/schemas/release-trains.schema.json new file mode 100644 index 0000000..62cf1d5 --- /dev/null +++ b/schemas/release-trains.schema.json @@ -0,0 +1,49 @@ +{ + "$id": "https://opencoven.ai/schemas/release-trains-v1.json", + "title": "OpenCoven release train index", + "type": "object", + "required": [ + "schema_version", + "release_trains" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.release-trains/v1" + }, + "release_trains": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "members", + "policy" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "members": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[.A-Za-z0-9_-]+$" + }, + "minItems": 1 + }, + "policy": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/schemas/repository-registry.schema.json b/schemas/repository-registry.schema.json new file mode 100644 index 0000000..92befc9 --- /dev/null +++ b/schemas/repository-registry.schema.json @@ -0,0 +1,308 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://opencoven.ai/schemas/repository-registry-v1.json", + "title": "OpenCoven public repository registry", + "type": "object", + "required": [ + "schema_version", + "organization", + "scope", + "defaults", + "repositories" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "const": "opencoven.repository-registry/v1" + }, + "organization": { + "const": "OpenCoven" + }, + "scope": { + "type": "object", + "required": [ + "visibility", + "observed_as_of", + "expected_public_repository_count", + "private_inventory" + ], + "properties": { + "visibility": { + "const": "public-only" + }, + "observed_as_of": { + "type": "string", + "format": "date" + }, + "expected_public_repository_count": { + "type": "integer", + "minimum": 0 + }, + "private_inventory": { + "const": "federated-and-intentionally-omitted" + }, + "private_overlay_policy": { + "type": "string" + } + }, + "additionalProperties": false + }, + "defaults": { + "type": "object", + "required": [ + "visibility", + "observed", + "owner", + "technical_dri", + "ownership_status", + "canonical_domains", + "does_not_own", + "disposition", + "agent_manifest", + "security_support" + ], + "properties": { + "visibility": { + "const": "public" + }, + "observed": { + "type": "object", + "properties": { + "default_branch": { + "type": "string", + "minLength": 1 + }, + "archived": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "owner": { + "type": "string", + "minLength": 1 + }, + "technical_dri": { + "type": "string", + "minLength": 1 + }, + "ownership_status": { + "enum": [ + "bootstrap-single-owner", + "separated", + "delegated", + "vacant" + ] + }, + "canonical_domains": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "does_not_own": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "disposition": { + "type": "object", + "required": ["state", "review_by"], + "properties": { + "state": { + "type": "string", + "minLength": 1 + }, + "review_by": { + "type": "string", + "format": "date" + }, + "destination": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "agent_manifest": { + "type": "object", + "properties": { + "status": { + "enum": [ + "enforced", + "planned", + "exempt" + ] + }, + "path": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "security_support": { + "enum": [ + "active", + "limited", + "unsupported", + "historical" + ] + } + }, + "additionalProperties": false + }, + "repositories": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "lifecycle", + "canonicality", + "risk_class", + "purpose" + ], + "properties": { + "name": { + "type": "string", + "pattern": "^[.A-Za-z0-9_-]+$" + }, + "lifecycle": { + "enum": [ + "incubating", + "active", + "maintenance", + "deprecated", + "archived", + "tombstone" + ] + }, + "canonicality": { + "enum": [ + "canonical", + "supporting", + "specimen", + "historical", + "none" + ] + }, + "risk_class": { + "enum": [ + "R0", + "R1", + "R2", + "R3", + "R4" + ] + }, + "purpose": { + "type": "string", + "minLength": 1 + }, + "visibility": { + "const": "public" + }, + "observed": { + "type": "object", + "properties": { + "default_branch": { + "type": "string", + "minLength": 1 + }, + "archived": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "owner": { + "type": "string", + "minLength": 1 + }, + "technical_dri": { + "type": "string", + "minLength": 1 + }, + "ownership_status": { + "enum": [ + "bootstrap-single-owner", + "separated", + "delegated", + "vacant" + ] + }, + "canonical_domains": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "does_not_own": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "disposition": { + "type": "object", + "properties": { + "state": { + "type": "string", + "minLength": 1 + }, + "review_by": { + "type": "string", + "format": "date" + }, + "destination": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "agent_manifest": { + "type": "object", + "properties": { + "status": { + "enum": [ + "enforced", + "planned", + "exempt" + ] + }, + "path": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "security_support": { + "enum": [ + "active", + "limited", + "unsupported", + "historical" + ] + } + }, + "additionalProperties": false + }, + "uniqueItems": true + } + }, + "additionalProperties": false +} diff --git a/scripts/agent-bootstrap b/scripts/agent-bootstrap new file mode 100755 index 0000000..dafa10d --- /dev/null +++ b/scripts/agent-bootstrap @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +command -v python3 >/dev/null 2>&1 || { + echo "python3 is required" >&2 + exit 1 +} + +python3 - <<'PY' +import sys +if sys.version_info < (3, 11): + raise SystemExit(f"Python 3.11+ is required; found {sys.version.split()[0]}") +print(f"Python {sys.version.split()[0]} available.") +PY + +for path in README.md AGENTS.md agent/manifest.json governance/repositories.json scripts/governance.py; do + test -f "$path" || { echo "missing required path: $path" >&2; exit 1; } +done + +echo "OpenCoven governance bootstrap passed; no dependencies installed and no network used." diff --git a/scripts/agent-check b/scripts/agent-check new file mode 100755 index 0000000..cb556e9 --- /dev/null +++ b/scripts/agent-check @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" +MODE="${1:-fast}" + +case "$MODE" in + fast) + ./scripts/agent-bootstrap + python3 scripts/governance.py validate + python3 scripts/governance.py generate --check + python3 -m unittest discover -s tests -v + ;; + full) + "$0" fast + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git diff --exit-code -- generated + fi + ;; + *) + echo "usage: $0 {fast|full}" >&2 + exit 2 + ;; +esac diff --git a/scripts/governance.py b/scripts/governance.py new file mode 100755 index 0000000..dc9c97c --- /dev/null +++ b/scripts/governance.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Validate, generate, and reconcile the OpenCoven public governance plane. + +The deterministic validation/generation path uses only the Python standard +library and performs no network access. GitHub reconciliation is an explicit, +separate command intended for the scheduled least-privilege workflow. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +if str(SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPT_DIR)) + +# Re-export the public validation surface used by repository tests and adopters. +from governance_core import ( # noqa: E402,F401 + ROOT, expanded_repositories, validate_exception_data, + validate_initiative_data, validate_manifest_data, validate_registry_data, +) +from governance_model import Governance # noqa: E402,F401 +from governance_cli import main, reconcile_public_inventory # noqa: E402,F401 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/governance_cli.py b/scripts/governance_cli.py new file mode 100644 index 0000000..8ff820c --- /dev/null +++ b/scripts/governance_cli.py @@ -0,0 +1,199 @@ +"""CLI and explicit network reconciliation for the OpenCoven governance plane.""" +from __future__ import annotations + +import argparse +import json +import os +import sys +import urllib.error +import urllib.parse +import urllib.request +from datetime import datetime +from pathlib import Path +from typing import Any + +from governance_core import ROOT +from governance_model import Governance + +def github_request(url: str, *, token: str | None = None, method: str = "GET", payload: dict[str, Any] | None = None) -> Any: + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": "OpenCoven-governance-reconciler/1", + "X-GitHub-Api-Version": "2022-11-28", + } + if token: + headers["Authorization"] = f"Bearer {token}" + body = None + if payload is not None: + body = json.dumps(payload).encode("utf-8") + headers["Content-Type"] = "application/json" + request = urllib.request.Request(url, data=body, method=method, headers=headers) + try: + with urllib.request.urlopen(request, timeout=30) as response: + content = response.read() + return json.loads(content) if content else None + except urllib.error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"GitHub API {method} {url} failed: HTTP {exc.code}: {detail[:500]}") from exc + except urllib.error.URLError as exc: + raise RuntimeError(f"GitHub API {method} {url} failed: {exc}") from exc + + +def fetch_public_repositories(org: str, token: str | None) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + page = 1 + while True: + query = urllib.parse.urlencode({"type": "public", "per_page": 100, "page": page}) + batch = github_request(f"https://api.github.com/orgs/{urllib.parse.quote(org)}/repos?{query}", token=token) + if not isinstance(batch, list): + raise RuntimeError("unexpected GitHub repository response") + result.extend(item for item in batch if item.get("visibility", "public") == "public" and not item.get("private", False)) + if len(batch) < 100: + break + page += 1 + return result + + +def reconcile_public_inventory(governance: Governance, live: list[dict[str, Any]]) -> list[str]: + declared = governance.registry_map() + actual = {item["name"]: item for item in live} + drift: list[str] = [] + for name in sorted(set(actual) - set(declared), key=str.lower): + drift.append(f"unregistered public repository: `{name}`") + for name in sorted(set(declared) - set(actual), key=str.lower): + drift.append(f"registered repository not present in live public inventory: `{name}`") + for name in sorted(set(declared) & set(actual), key=str.lower): + expected = declared[name]["observed"] + observed = actual[name] + if bool(observed.get("archived")) != expected.get("archived"): + drift.append(f"`{name}` archived mismatch: registry={expected.get('archived')} live={bool(observed.get('archived'))}") + if observed.get("default_branch") != expected.get("default_branch"): + drift.append(f"`{name}` default branch mismatch: registry=`{expected.get('default_branch')}` live=`{observed.get('default_branch')}`") + return drift + + +def upsert_drift_issue(repository: str, token: str, drift: list[str], *, dry_run: bool) -> None: + marker = "" + title = "[governance-drift] Public repository registry drift" + owner, repo = repository.split("/", 1) + issues = github_request(f"https://api.github.com/repos/{owner}/{repo}/issues?state=open&per_page=100", token=token) + existing = next((item for item in issues if marker in (item.get("body") or "")), None) + if drift: + body = "\n".join([ + marker, + "# Public repository registry drift", + "", + "The scheduled read-only observer found differences between `governance/repositories.json` and GitHub's public repository metadata.", + "", + *[f"- {item}" for item in drift], + "", + "This issue is a coordination signal only. It does not authorize archive, transfer, visibility, deletion, release, publication, or protected OpenCoven state changes.", + "", + f"Observed at: `{datetime.utcnow().replace(microsecond=0).isoformat()}Z`", + ]) + if dry_run: + print(body) + return + if existing: + github_request(existing["url"], token=token, method="PATCH", payload={"title": title, "body": body}) + else: + github_request(f"https://api.github.com/repos/{owner}/{repo}/issues", token=token, method="POST", payload={"title": title, "body": body}) + elif existing: + if dry_run: + print(f"would close clean drift issue #{existing['number']}") + else: + github_request(existing["url"], token=token, method="PATCH", payload={"state": "closed", "state_reason": "completed"}) + + +def command_validate(governance: Governance, _: argparse.Namespace) -> int: + errors = governance.validate() + if errors: + print("Governance validation failed:", file=sys.stderr) + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + print("Governance validation passed.") + return 0 + + +def command_generate(governance: Governance, args: argparse.Namespace) -> int: + if args.check: + errors = governance.validate_generated() + if errors: + for error in errors: + print(error, file=sys.stderr) + return 1 + print("Generated governance views are current.") + return 0 + governance.generate() + print("Generated governance views updated.") + return 0 + + +def command_validate_manifest(governance: Governance, args: argparse.Namespace) -> int: + errors = governance.validate_manifest_file(Path(args.path).resolve()) + if errors: + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + print(f"Agent manifest valid: {args.path}") + return 0 + + +def command_validate_evidence(governance: Governance, args: argparse.Namespace) -> int: + errors = governance.validate_evidence_file(Path(args.path).resolve()) + if errors: + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + print(f"Evidence packet valid: {args.path}") + return 0 + + +def command_reconcile(governance: Governance, args: argparse.Namespace) -> int: + token = os.environ.get("GITHUB_TOKEN") + if not args.dry_run and not token: + print("GITHUB_TOKEN is required unless --dry-run is used", file=sys.stderr) + return 2 + live = fetch_public_repositories(args.org, token) + drift = reconcile_public_inventory(governance, live) + if args.repository: + upsert_drift_issue(args.repository, token or "", drift, dry_run=args.dry_run) + if drift: + print("Public repository drift detected:", file=sys.stderr) + for item in drift: + print(f"- {item}", file=sys.stderr) + return 1 + print(f"Public repository inventory reconciled: {len(live)} repositories.") + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + sub.add_parser("validate", help="validate authoritative records, workflows, and generated views") + generate = sub.add_parser("generate", help="generate deterministic public views") + generate.add_argument("--check", action="store_true", help="fail when generated files are stale") + manifest = sub.add_parser("validate-manifest", help="validate a repository agent manifest against the public registry when present") + manifest.add_argument("path") + evidence = sub.add_parser("validate-evidence", help="validate a governance evidence packet") + evidence.add_argument("path") + reconcile = sub.add_parser("reconcile-github", help="compare public registry with live GitHub public repository metadata") + reconcile.add_argument("--org", default="OpenCoven") + reconcile.add_argument("--repository", default="OpenCoven/.github", help="repository used for the deduplicated drift issue") + reconcile.add_argument("--dry-run", action="store_true") + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + governance = Governance(ROOT) + handlers = { + "validate": command_validate, + "generate": command_generate, + "validate-manifest": command_validate_manifest, + "validate-evidence": command_validate_evidence, + "reconcile-github": command_reconcile, + } + return handlers[args.command](governance, args) diff --git a/scripts/governance_core.py b/scripts/governance_core.py new file mode 100644 index 0000000..427873e --- /dev/null +++ b/scripts/governance_core.py @@ -0,0 +1,308 @@ +"""Core validation primitives for the OpenCoven governance plane.""" +from __future__ import annotations + +import copy +import hashlib +import json +import re +from datetime import date +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +SHA40 = re.compile(r"^[0-9a-fA-F]{40}$") +ACTION_USE = re.compile(r"^\s*-?\s*uses:\s*([^\s#]+)", re.MULTILINE) +SECRET_PATTERNS = { + # Fragmented construction avoids embedding credential-shaped examples in this + # public source file while preserving the exact detector semantics. + "GitHub token": re.compile( + r"\b(?:g" + r"h[pousr]_" + r"[A-Za-z0-9_]{20,}|github_" + + r"pat_" + r"[A-Za-z0-9_]{20,})\b" + ), + "AWS access key": re.compile(r"\b(?:A" + r"KIA|ASIA)[A-Z0-9]{16}\b"), + "private key": re.compile("-" * 5 + r"BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY" + "-" * 5), + "OpenAI-style secret": re.compile(r"\bs" + r"k-(?:proj-)?[A-Za-z0-9_-]{24,}\b"), +} +TEXT_SUFFIXES = {".md", ".json", ".yml", ".yaml", ".py", ".sh", ".txt"} + +class DuplicateKeyError(ValueError): + pass + + +def _no_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + raise DuplicateKeyError(f"duplicate JSON key: {key}") + result[key] = value + return result + + +def load_json(path: Path) -> Any: + try: + return json.loads(path.read_text(encoding="utf-8"), object_pairs_hook=_no_duplicate_keys) + except (OSError, json.JSONDecodeError, DuplicateKeyError) as exc: + raise ValueError(f"{path.relative_to(ROOT) if path.is_relative_to(ROOT) else path}: {exc}") from exc + + +def parse_date(value: str, field: str, errors: list[str]) -> date | None: + try: + return date.fromisoformat(value) + except (TypeError, ValueError): + errors.append(f"{field}: expected ISO date, got {value!r}") + return None + + +def markdown(value: Any) -> str: + return str(value).replace("|", "\\|").replace("\n", " ") + + +def expand_repository(defaults: dict[str, Any], item: dict[str, Any]) -> dict[str, Any]: + result = copy.deepcopy(defaults) + for key, value in item.items(): + if isinstance(value, dict) and isinstance(result.get(key), dict): + result[key].update(copy.deepcopy(value)) + else: + result[key] = copy.deepcopy(value) + return result + + +def expanded_repositories(data: dict[str, Any]) -> list[dict[str, Any]]: + defaults = data.get("defaults", {}) + repositories = data.get("repositories", []) + if not isinstance(defaults, dict) or not isinstance(repositories, list): + return [] + return [expand_repository(defaults, item) for item in repositories if isinstance(item, dict)] + + +def sha256_text(value: str) -> str: + return hashlib.sha256(value.encode("utf-8")).hexdigest() + + +def validate_registry_data(data: dict[str, Any], *, today: date | None = None) -> list[str]: + errors: list[str] = [] + today = today or date.today() + if data.get("schema_version") != "opencoven.repository-registry/v1": + errors.append("governance/repositories.json: unsupported schema_version") + if data.get("organization") != "OpenCoven": + errors.append("governance/repositories.json: organization must be OpenCoven") + scope = data.get("scope", {}) + if scope.get("visibility") != "public-only": + errors.append("registry scope must be public-only") + if scope.get("private_inventory") != "federated-and-intentionally-omitted": + errors.append("registry must explicitly omit private inventory") + raw_repositories = data.get("repositories") + if not isinstance(raw_repositories, list): + return errors + ["registry repositories must be an array"] + defaults = data.get("defaults") + if not isinstance(defaults, dict): + return errors + ["registry defaults must be an object"] + repositories = expanded_repositories(data) + if len(repositories) != len(raw_repositories): + errors.append("registry repository entries must be objects") + if scope.get("expected_public_repository_count") != len(raw_repositories): + errors.append("registry expected_public_repository_count does not match repositories length") + + names: set[str] = set() + domains: dict[str, str] = {} + actual_order: list[str] = [] + allowed_lifecycle = {"incubating", "active", "maintenance", "deprecated", "archived", "tombstone"} + allowed_canonicality = {"canonical", "supporting", "specimen", "historical", "none"} + allowed_risk = {"R0", "R1", "R2", "R3", "R4"} + destination_required = { + "consolidate-then-retire", + "evaluate-consolidation-or-private-incubation", + "private-incubation-or-retire", + } + + for index, item in enumerate(repositories): + where = f"repositories[{index}]" + if not isinstance(item, dict): + errors.append(f"{where}: expected object") + continue + name = item.get("name") + actual_order.append(str(name)) + if not isinstance(name, str) or not name: + errors.append(f"{where}.name: required") + continue + if name in names: + errors.append(f"duplicate repository: {name}") + names.add(name) + if item.get("visibility") != "public": + errors.append(f"{name}: public registry may contain only visibility=public") + if item.get("lifecycle") not in allowed_lifecycle: + errors.append(f"{name}: invalid lifecycle {item.get('lifecycle')!r}") + if item.get("canonicality") not in allowed_canonicality: + errors.append(f"{name}: invalid canonicality {item.get('canonicality')!r}") + if item.get("risk_class") not in allowed_risk: + errors.append(f"{name}: invalid risk class {item.get('risk_class')!r}") + for field in ("owner", "technical_dri", "ownership_status", "purpose"): + if not isinstance(item.get(field), str) or not item[field].strip(): + errors.append(f"{name}: {field} is required") + observed = item.get("observed", {}) + if not isinstance(observed.get("default_branch"), str) or not observed.get("default_branch"): + errors.append(f"{name}: observed.default_branch is required") + if not isinstance(observed.get("archived"), bool): + errors.append(f"{name}: observed.archived must be boolean") + if item.get("lifecycle") == "archived" and observed.get("archived") is not True: + errors.append(f"{name}: archived lifecycle requires observed.archived=true") + if observed.get("archived") is True and item.get("lifecycle") != "archived": + errors.append(f"{name}: observed archived repository must use archived lifecycle") + + canonical_domains = item.get("canonical_domains", []) + if not isinstance(canonical_domains, list) or not all(isinstance(v, str) and v for v in canonical_domains): + errors.append(f"{name}: canonical_domains must be non-empty strings") + canonical_domains = [] + if item.get("canonicality") == "canonical" and not canonical_domains: + errors.append(f"{name}: canonical repository must own at least one domain") + if item.get("canonicality") != "canonical" and canonical_domains: + errors.append(f"{name}: only canonical repositories may claim canonical_domains") + for domain in canonical_domains: + if domain in domains: + errors.append(f"duplicate canonical domain {domain!r}: {domains[domain]} and {name}") + else: + domains[domain] = name + + disposition = item.get("disposition", {}) + if not isinstance(disposition, dict) or not disposition.get("state"): + errors.append(f"{name}: disposition.state is required") + disposition = {} + if disposition.get("state") in destination_required and not disposition.get("destination"): + errors.append(f"{name}: disposition {disposition.get('state')} requires destination") + review_by = parse_date(disposition.get("review_by"), f"{name}.disposition.review_by", errors) + if review_by and review_by < today and item.get("lifecycle") not in {"archived", "tombstone"}: + errors.append(f"{name}: lifecycle/disposition review expired on {review_by.isoformat()}") + + manifest = item.get("agent_manifest", {}) + if manifest.get("status") not in {"enforced", "planned", "exempt"}: + errors.append(f"{name}: invalid agent_manifest.status") + if not isinstance(manifest.get("path"), str) or not manifest.get("path"): + errors.append(f"{name}: agent_manifest.path is required") + if manifest.get("status") == "exempt" and item.get("lifecycle") not in {"archived", "tombstone"}: + errors.append(f"{name}: only archived/tombstone repositories may be manifest-exempt") + if item.get("security_support") not in {"active", "limited", "unsupported", "historical"}: + errors.append(f"{name}: invalid security_support") + + if actual_order != sorted(actual_order, key=str.lower): + errors.append("registry repositories must be sorted by name") + + for item in repositories: + destination = item.get("disposition", {}).get("destination") + if not isinstance(destination, dict): + continue + if destination.get("kind") == "repository" and destination.get("name") not in names: + errors.append(f"{item.get('name')}: destination repository {destination.get('name')!r} is not registered") + if destination.get("kind") == "portfolio": + for target in destination.get("names", []): + if target not in names: + errors.append(f"{item.get('name')}: destination repository {target!r} is not registered") + if destination.get("kind") == "private-overlay" and not destination.get("id"): + errors.append(f"{item.get('name')}: private-overlay destination requires opaque id") + + return errors + + +def validate_manifest_data(data: dict[str, Any], *, registry_entry: dict[str, Any] | None = None) -> list[str]: + errors: list[str] = [] + if data.get("schema_version") != "opencoven.agent-repo/v1": + errors.append("manifest: unsupported schema_version") + repository = data.get("repository", {}) + risk = data.get("risk", {}) + agent = data.get("agent", {}) + contracts = data.get("contracts", {}) + required_repo = ("name", "lifecycle", "canonicality", "canonical_for", "does_not_own", "owner", "technical_dri", "ownership_status") + for field in required_repo: + if field not in repository: + errors.append(f"manifest.repository.{field}: required") + if risk.get("class") not in {"R0", "R1", "R2", "R3", "R4"}: + errors.append("manifest.risk.class: invalid") + for field in ("protected_paths", "generated_paths", "external_side_effects"): + if not isinstance(risk.get(field), list): + errors.append(f"manifest.risk.{field}: expected array") + for field in ("network_policy", "secrets_policy"): + if not isinstance(risk.get(field), str) or not risk.get(field): + errors.append(f"manifest.risk.{field}: required") + if not isinstance(agent.get("entrypoint"), str) or not agent.get("entrypoint"): + errors.append("manifest.agent.entrypoint: required") + if not isinstance(agent.get("bootstrap"), str) or not agent.get("bootstrap"): + errors.append("manifest.agent.bootstrap: required") + verify = agent.get("verify", {}) + for field in ("fast", "full"): + if not isinstance(verify.get(field), str) or not verify.get(field): + errors.append(f"manifest.agent.verify.{field}: required") + for field in ("produces", "consumes"): + if not isinstance(contracts.get(field), list): + errors.append(f"manifest.contracts.{field}: expected array") + + canonical_for = repository.get("canonical_for", []) + if repository.get("canonicality") == "canonical" and not canonical_for: + errors.append("manifest: canonical repository must claim at least one domain") + if repository.get("canonicality") != "canonical" and canonical_for: + errors.append("manifest: noncanonical repository cannot claim canonical domains") + + if registry_entry: + comparisons = { + "repository.name": (repository.get("name"), registry_entry.get("name")), + "repository.lifecycle": (repository.get("lifecycle"), registry_entry.get("lifecycle")), + "repository.canonicality": (repository.get("canonicality"), registry_entry.get("canonicality")), + "repository.canonical_for": (sorted(canonical_for), sorted(registry_entry.get("canonical_domains", []))), + "risk.class": (risk.get("class"), registry_entry.get("risk_class")), + } + for field, (actual, expected) in comparisons.items(): + if actual != expected: + errors.append(f"manifest mismatch {field}: {actual!r} != registry {expected!r}") + return errors + + +def validate_initiative_data(data: dict[str, Any], *, repository_names: set[str], decision_ids: set[str], today: date | None = None) -> list[str]: + errors: list[str] = [] + today = today or date.today() + if data.get("schema_version") != "opencoven.initiative/v1": + errors.append("unsupported initiative schema_version") + for field in ("id", "title", "status", "priority", "decision_owner", "technical_dri", "ownership_status", "outcome", "authority_boundary"): + if not data.get(field): + errors.append(f"initiative {data.get('id', '')}: {field} required") + for decision in data.get("decisions", []): + if decision not in decision_ids: + errors.append(f"initiative {data.get('id')}: unknown decision {decision}") + for workstream in data.get("workstreams", []): + repository = workstream.get("repository") + if repository not in repository_names: + errors.append(f"initiative {data.get('id')}: unregistered public workstream repository {repository!r}") + if not workstream.get("responsibility"): + errors.append(f"initiative {data.get('id')}: workstream responsibility required") + criteria = data.get("exit_criteria", []) + if not criteria: + errors.append(f"initiative {data.get('id')}: exit_criteria required") + if data.get("status") == "completed": + for criterion in criteria: + if criterion.get("state") != "met" or not criterion.get("evidence"): + errors.append(f"initiative {data.get('id')}: completed criterion {criterion.get('id')} lacks met state and evidence") + review_by = parse_date(data.get("review_by"), f"initiative {data.get('id')}.review_by", errors) + if review_by and review_by < today and data.get("status") in {"proposed", "active", "verifying"}: + errors.append(f"initiative {data.get('id')}: review expired on {review_by.isoformat()}") + return errors + + +def validate_exception_data(data: dict[str, Any], *, control_ids: set[str], today: date | None = None) -> list[str]: + errors: list[str] = [] + today = today or date.today() + if data.get("schema_version") != "opencoven.exception-set/v1": + errors.append("unsupported exception schema_version") + seen: set[str] = set() + for item in data.get("exceptions", []): + ident = item.get("id") + if not ident or ident in seen: + errors.append(f"duplicate or missing exception id: {ident!r}") + seen.add(ident) + if item.get("control_id") not in control_ids: + errors.append(f"exception {ident}: unknown control {item.get('control_id')!r}") + created = parse_date(item.get("created"), f"exception {ident}.created", errors) + expires = parse_date(item.get("expires"), f"exception {ident}.expires", errors) + if created and expires and expires < created: + errors.append(f"exception {ident}: expires before creation") + if created and expires and (expires - created).days > 90: + errors.append(f"exception {ident}: active window exceeds 90 days") + if expires and expires < today and item.get("status") in {"proposed", "active"}: + errors.append(f"exception {ident}: {item.get('status')} exception expired on {expires.isoformat()}") + return errors diff --git a/scripts/governance_model.py b/scripts/governance_model.py new file mode 100644 index 0000000..269a53f --- /dev/null +++ b/scripts/governance_model.py @@ -0,0 +1,371 @@ +"""Repository model, cross-file validation, and deterministic generation.""" +from __future__ import annotations + +import json +import re +from collections import defaultdict +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from governance_core import ( + ACTION_USE, ROOT, SECRET_PATTERNS, SHA40, TEXT_SUFFIXES, + expanded_repositories, load_json, markdown, sha256_text, + validate_exception_data, validate_initiative_data, + validate_manifest_data, validate_registry_data, +) + +@dataclass +class Governance: + root: Path = ROOT + + def path(self, value: str) -> Path: + return self.root / value + + def registry(self) -> dict[str, Any]: + return load_json(self.path("governance/repositories.json")) + + def registry_map(self) -> dict[str, dict[str, Any]]: + return {item["name"]: item for item in expanded_repositories(self.registry())} + + def decision_index(self) -> dict[str, Any]: + return load_json(self.path("decisions/index.json")) + + def initiative_files(self) -> list[Path]: + return sorted(self.path("initiatives").glob("*.json")) + + def validate(self) -> list[str]: + errors: list[str] = [] + required = [ + "README.md", "AGENTS.md", "LICENSE", "agent/manifest.json", + "governance/repositories.json", "governance/lifecycle.json", "governance/controls.json", "governance/exceptions.json", + "compatibility/dependencies.json", "compatibility/contracts.json", "compatibility/release-trains.json", + "decisions/index.json", ".github/CODEOWNERS", + ] + for rel in required: + if not self.path(rel).exists(): + errors.append(f"missing required path: {rel}") + + # Parse every JSON file and reject duplicate keys. + for path in sorted(self.root.rglob("*.json")): + try: + load_json(path) + except ValueError as exc: + errors.append(str(exc)) + + if errors: + return errors + + registry = self.registry() + errors.extend(validate_registry_data(registry)) + registry_map = {item["name"]: item for item in expanded_repositories(registry)} + names = set(registry_map) + + manifest = load_json(self.path("agent/manifest.json")) + errors.extend(validate_manifest_data(manifest, registry_entry=registry_map.get(".github"))) + entrypoint = manifest.get("agent", {}).get("entrypoint") + if entrypoint and not self.path(entrypoint).exists(): + errors.append(f"manifest agent.entrypoint does not exist: {entrypoint}") + + decisions = self.decision_index() + if decisions.get("schema_version") != "opencoven.decision-index/v1": + errors.append("decisions/index.json: unsupported schema_version") + decision_ids: set[str] = set() + for item in decisions.get("decisions", []): + ident = item.get("id") + if ident in decision_ids: + errors.append(f"duplicate decision id: {ident}") + decision_ids.add(ident) + path = self.path(item.get("path", "")) + if not path.exists(): + errors.append(f"decision {ident}: missing path {item.get('path')}") + elif f"# {ident}:" not in path.read_text(encoding="utf-8"): + errors.append(f"decision {ident}: path heading does not match id") + + initiatives: dict[str, dict[str, Any]] = {} + for path in self.initiative_files(): + data = load_json(path) + ident = data.get("id") + if ident in initiatives: + errors.append(f"duplicate initiative id: {ident}") + initiatives[ident] = data + errors.extend(validate_initiative_data(data, repository_names=names, decision_ids=decision_ids)) + for ident, data in initiatives.items(): + for dependency in data.get("dependencies", []): + if dependency not in initiatives: + errors.append(f"initiative {ident}: unknown dependency {dependency}") + errors.extend(self._validate_initiative_cycles(initiatives)) + + dependencies = load_json(self.path("compatibility/dependencies.json")) + seen_edges: set[tuple[str, str, str]] = set() + for edge in dependencies.get("edges", []): + key = (edge.get("producer"), edge.get("consumer"), edge.get("relationship")) + if key in seen_edges: + errors.append(f"duplicate dependency edge: {key}") + seen_edges.add(key) + if edge.get("producer") not in names or edge.get("consumer") not in names: + errors.append(f"dependency references unregistered public repository: {key}") + if edge.get("producer") == edge.get("consumer"): + errors.append(f"self dependency is not allowed: {key}") + if not edge.get("required_evidence"): + errors.append(f"dependency lacks required_evidence: {key}") + + contracts = load_json(self.path("compatibility/contracts.json")) + contract_ids: set[str] = set() + for contract in contracts.get("contracts", []): + if contract.get("id") in contract_ids: + errors.append(f"duplicate contract id: {contract.get('id')}") + contract_ids.add(contract.get("id")) + if contract.get("owner") not in names: + errors.append(f"contract {contract.get('id')}: unregistered owner {contract.get('owner')}") + + release_trains = load_json(self.path("compatibility/release-trains.json")) + for train in release_trains.get("release_trains", []): + for member in train.get("members", []): + if member not in names: + errors.append(f"release train {train.get('id')}: unregistered member {member}") + + controls = load_json(self.path("governance/controls.json")) + control_ids: set[str] = set() + for control in controls.get("controls", []): + ident = control.get("id") + if ident in control_ids: + errors.append(f"duplicate control id: {ident}") + control_ids.add(ident) + for evidence in control.get("evidence", []): + if evidence.startswith("OpenCoven/"): + continue + local = self.path(evidence) + if not local.exists(): + errors.append(f"control {ident}: evidence path does not exist: {evidence}") + + exceptions = load_json(self.path("governance/exceptions.json")) + errors.extend(validate_exception_data(exceptions, control_ids=control_ids)) + errors.extend(self.validate_workflows()) + errors.extend(self.scan_secrets()) + errors.extend(self.validate_generated()) + return sorted(set(errors)) + + @staticmethod + def _validate_initiative_cycles(initiatives: dict[str, dict[str, Any]]) -> list[str]: + errors: list[str] = [] + visiting: set[str] = set() + visited: set[str] = set() + + def visit(node: str, stack: list[str]) -> None: + if node in visiting: + cycle = " -> ".join(stack + [node]) + errors.append(f"initiative dependency cycle: {cycle}") + return + if node in visited or node not in initiatives: + return + visiting.add(node) + for dep in initiatives[node].get("dependencies", []): + visit(dep, stack + [node]) + visiting.remove(node) + visited.add(node) + + for ident in initiatives: + visit(ident, []) + return errors + + def validate_workflows(self) -> list[str]: + errors: list[str] = [] + workflow_dir = self.path(".github/workflows") + for path in sorted(list(workflow_dir.glob("*.yml")) + list(workflow_dir.glob("*.yaml"))): + rel = path.relative_to(self.root) + text = path.read_text(encoding="utf-8") + if not re.search(r"(?m)^permissions:\s*$", text): + errors.append(f"{rel}: top-level permissions block required") + if "pull_request_target:" in text: + errors.append(f"{rel}: pull_request_target is forbidden") + for action in ACTION_USE.findall(text): + if action.startswith("./") or action.startswith("docker://"): + continue + if "@" not in action: + errors.append(f"{rel}: action without immutable ref: {action}") + continue + _, ref = action.rsplit("@", 1) + if not SHA40.fullmatch(ref): + errors.append(f"{rel}: action ref must be a full commit SHA: {action}") + if ( + re.search(r"(?m)^\s{0,4}pull_request:\s*$", text) + or re.search(r"(?m)^on:\s*pull_request\s*$", text) + or re.search(r"(?m)^on:\s*\[.*\bpull_request\b.*\]\s*$", text) + ): + permission_section = self._top_level_block(text, "permissions") + if re.search(r"(?m)^\s+[A-Za-z-]+:\s*write\s*$", permission_section): + errors.append(f"{rel}: pull_request workflow may not request write permission") + return errors + + @staticmethod + def _top_level_block(text: str, key: str) -> str: + lines = text.splitlines() + start = None + result: list[str] = [] + for index, line in enumerate(lines): + if line == f"{key}:": + start = index + 1 + continue + if start is not None: + if line and not line.startswith((" ", "\t", "#")): + break + result.append(line) + return "\n".join(result) + + def scan_secrets(self) -> list[str]: + errors: list[str] = [] + ignored = {"generated/portfolio.md"} # generated content still derives from validated public input + for path in sorted(self.root.rglob("*")): + if not path.is_file() or ".git" in path.parts or path.suffix not in TEXT_SUFFIXES: + continue + rel = str(path.relative_to(self.root)) + if rel in ignored: + continue + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError: + continue + for label, pattern in SECRET_PATTERNS.items(): + if pattern.search(text): + errors.append(f"{rel}: possible {label} material") + return errors + + def generated_content(self) -> dict[str, str]: + registry = expanded_repositories(self.registry()) + dependencies = load_json(self.path("compatibility/dependencies.json"))["edges"] + controls = load_json(self.path("governance/controls.json"))["controls"] + initiatives = [load_json(path) for path in self.initiative_files()] + + counts = defaultdict(int) + for item in registry: + counts[item["lifecycle"]] += 1 + portfolio = [ + "# Generated public repository portfolio", + "", + "> Generated by `python3 scripts/governance.py generate`. Do not edit by hand.", + "", + f"Registry digest: `{sha256_text(json.dumps(self.registry(), sort_keys=True, separators=(',', ':')))}`", + "", + "## Summary", + "", + "| Lifecycle | Count |", + "|---|---:|", + ] + for state in ("active", "incubating", "maintenance", "deprecated", "archived", "tombstone"): + portfolio.append(f"| {state} | {counts[state]} |") + portfolio += ["", "## Repositories", "", "| Repository | Lifecycle | Canonicality | Risk | Owner | Disposition | Manifest |", "|---|---|---|---:|---|---|---|"] + for item in registry: + portfolio.append("| {name} | {lifecycle} | {canonicality} | {risk_class} | @{owner} | {state} | {manifest} |".format( + **item, + state=markdown(item["disposition"]["state"]), + manifest=item["agent_manifest"]["status"], + )) + portfolio += ["", "This is a public-only view. Private repository inventory is intentionally federated and omitted.", ""] + + ownership = [ + "# Generated canonical public ownership map", "", + "> Generated from `governance/repositories.json`. A governance claim identifies ownership; it does not grant protected runtime authority.", "", + "| Canonical domain | Repository | Technical DRI | Risk |", "|---|---|---|---:|", + ] + owned: list[tuple[str, dict[str, Any]]] = [] + for item in registry: + for domain in item["canonical_domains"]: + owned.append((domain, item)) + for domain, item in sorted(owned): + ownership.append(f"| `{domain}` | `{item['name']}` | @{item['technical_dri']} | {item['risk_class']} |") + ownership.append("") + + graph = [ + "%% Generated by scripts/governance.py; do not edit.", + "flowchart LR", + ] + for item in registry: + safe = re.sub(r"[^A-Za-z0-9_]", "_", item["name"]) + graph.append(f' {safe}["{item["name"]}"]') + for edge in dependencies: + source = re.sub(r"[^A-Za-z0-9_]", "_", edge["producer"]) + target = re.sub(r"[^A-Za-z0-9_]", "_", edge["consumer"]) + label = edge["relationship"].replace('"', "'") + graph.append(f' {source} -->|"{label}"| {target}') + graph.append("") + + initiative_view = [ + "# Generated cross-repository initiatives", "", + "> Generated from `initiatives/*.json`. Implementation status remains authoritative in linked owning-repository evidence.", "", + "| Initiative | Priority | Status | Decision owner | Technical DRI | Review by | Open criteria |", "|---|---:|---|---|---|---|---:|", + ] + for item in sorted(initiatives, key=lambda value: (value["priority"], value["id"])): + open_count = sum(1 for criterion in item["exit_criteria"] if criterion["state"] != "met") + initiative_view.append(f"| `{item['id']}` | {item['priority']} | {item['status']} | @{item['decision_owner']} | @{item['technical_dri']} | {item['review_by']} | {open_count} |") + initiative_view.append("") + + control_view = [ + "# Generated governance control index", "", + "> Generated from `governance/controls.json`. A control marked specified or implemented is not necessarily administratively applied or operationally effective.", "", + "| Control | Objective | Enforcement | State |", "|---|---|---|---|", + ] + for item in controls: + control_view.append(f"| `{item['id']}` {markdown(item['title'])} | {markdown(item['objective'])} | {markdown(item['enforcement'])} | {markdown(item['status'])} |") + control_view.append("") + + return { + "generated/portfolio.md": "\n".join(portfolio), + "generated/ownership.md": "\n".join(ownership), + "generated/dependencies.mmd": "\n".join(graph), + "generated/initiatives.md": "\n".join(initiative_view), + "generated/controls.md": "\n".join(control_view), + } + + def generate(self) -> None: + for rel, content in self.generated_content().items(): + path = self.path(rel) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + def validate_generated(self) -> list[str]: + errors: list[str] = [] + for rel, expected in self.generated_content().items(): + path = self.path(rel) + if not path.exists(): + errors.append(f"missing generated file: {rel}") + continue + actual = path.read_text(encoding="utf-8") + if actual != expected: + errors.append(f"stale generated file: {rel}; run python3 scripts/governance.py generate") + return errors + + def validate_manifest_file(self, manifest_path: Path) -> list[str]: + data = load_json(manifest_path) + entry = self.registry_map().get(data.get("repository", {}).get("name")) + return validate_manifest_data(data, registry_entry=entry) + + def validate_evidence_file(self, evidence_path: Path) -> list[str]: + data = load_json(evidence_path) + errors: list[str] = [] + if data.get("schema_version") != "opencoven.governance-evidence/v1": + errors.append("evidence: unsupported schema_version") + change = data.get("change", {}) + if not change.get("objective") or not change.get("acceptance_criteria"): + errors.append("evidence.change requires objective and acceptance_criteria") + authority = data.get("authority", {}) + if authority.get("authorization_effect") != "none-metadata-only": + errors.append("evidence.authority.authorization_effect must be none-metadata-only") + if authority.get("risk_class") not in {"R0", "R1", "R2", "R3", "R4"}: + errors.append("evidence.authority.risk_class invalid") + if not data.get("sources"): + errors.append("evidence.sources requires at least one exact source") + for source in data.get("sources", []): + if not all(source.get(field) for field in ("kind", "reference", "revision")): + errors.append("evidence source requires kind, reference, and revision") + if not data.get("verification"): + errors.append("evidence.verification requires at least one result") + for result in data.get("verification", []): + if result.get("result") not in {"pass", "fail", "skipped", "unsupported"}: + errors.append("evidence verification result invalid") + if not result.get("command") or not result.get("environment"): + errors.append("evidence verification requires command and environment") + for field in ("migration", "rollback", "uncertainty"): + if field not in data: + errors.append(f"evidence.{field} required") + return errors diff --git a/tests/test_governance.py b/tests/test_governance.py new file mode 100644 index 0000000..34301d3 --- /dev/null +++ b/tests/test_governance.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +import copy +import importlib.util +import json +import tempfile +import unittest +import sys +from datetime import date +from pathlib import Path + +MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "governance.py" +SPEC = importlib.util.spec_from_file_location("opencoven_governance", MODULE_PATH) +assert SPEC and SPEC.loader +GOV = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = GOV +SPEC.loader.exec_module(GOV) +ROOT = Path(__file__).resolve().parents[1] + + +class RegistryInvariantTests(unittest.TestCase): + def setUp(self) -> None: + self.registry = json.loads((ROOT / "governance/repositories.json").read_text()) + + def test_current_registry_is_valid_at_snapshot_date(self) -> None: + self.assertEqual([], GOV.validate_registry_data(self.registry, today=date(2026, 9, 3))) + + def test_duplicate_canonical_domain_fails_closed(self) -> None: + data = copy.deepcopy(self.registry) + data["repositories"][1]["canonicality"] = "canonical" + data["repositories"][1]["canonical_domains"] = ["organization.governance"] + errors = GOV.validate_registry_data(data, today=date(2026, 9, 3)) + self.assertTrue(any("duplicate canonical domain" in error for error in errors), errors) + + def test_private_repository_in_public_registry_is_rejected(self) -> None: + data = copy.deepcopy(self.registry) + data["repositories"][0]["visibility"] = "private" + errors = GOV.validate_registry_data(data, today=date(2026, 9, 3)) + self.assertTrue(any("only visibility=public" in error for error in errors), errors) + + def test_archived_metadata_must_match_lifecycle(self) -> None: + data = copy.deepcopy(self.registry) + target = next(item for item in data["repositories"] if item["name"] == "cast-codes") + target["observed"]["archived"] = False + errors = GOV.validate_registry_data(data, today=date(2026, 9, 3)) + self.assertTrue(any("archived lifecycle" in error for error in errors), errors) + + def test_expired_review_is_detected(self) -> None: + data = copy.deepcopy(self.registry) + data["repositories"][0]["disposition"] = {"state": "retain", "review_by": "2026-09-02"} + errors = GOV.validate_registry_data(data, today=date(2026, 9, 3)) + self.assertTrue(any("review expired" in error for error in errors), errors) + + +class InitiativeInvariantTests(unittest.TestCase): + def setUp(self) -> None: + self.registry_names = { + item["name"] + for item in json.loads((ROOT / "governance/repositories.json").read_text())["repositories"] + } + self.decisions = { + item["id"] + for item in json.loads((ROOT / "decisions/index.json").read_text())["decisions"] + } + self.initiative = json.loads((ROOT / "initiatives/organization-governance-plane-v1.json").read_text()) + + def test_completed_initiative_requires_exact_evidence(self) -> None: + data = copy.deepcopy(self.initiative) + data["status"] = "completed" + errors = GOV.validate_initiative_data( + data, + repository_names=self.registry_names, + decision_ids=self.decisions, + today=date(2026, 9, 3), + ) + self.assertTrue(any("lacks met state and evidence" in error for error in errors), errors) + + def test_unregistered_public_workstream_is_rejected(self) -> None: + data = copy.deepcopy(self.initiative) + data["workstreams"][0]["repository"] = "not-a-public-repository" + errors = GOV.validate_initiative_data( + data, + repository_names=self.registry_names, + decision_ids=self.decisions, + today=date(2026, 9, 3), + ) + self.assertTrue(any("unregistered public workstream" in error for error in errors), errors) + + +class ExceptionInvariantTests(unittest.TestCase): + def test_active_expired_exception_is_rejected(self) -> None: + data = { + "schema_version": "opencoven.exception-set/v1", + "exceptions": [{ + "id": "EX-001", + "control_id": "GOV-001", + "scope": "test", + "owner": "BunsDev", + "approver": "BunsDev", + "rationale": "test", + "risk": "test", + "compensating_controls": ["test"], + "created": "2026-08-01", + "expires": "2026-09-01", + "status": "active", + "remediation": "test", + }], + } + errors = GOV.validate_exception_data(data, control_ids={"GOV-001"}, today=date(2026, 9, 3)) + self.assertTrue(any("expired" in error for error in errors), errors) + + +class WorkflowInvariantTests(unittest.TestCase): + def test_mutable_action_tag_is_rejected(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + workflow = root / ".github/workflows/test.yml" + workflow.parent.mkdir(parents=True) + workflow.write_text( + "name: test\non: push\npermissions:\n contents: read\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n" + ) + errors = GOV.Governance(root).validate_workflows() + self.assertTrue(any("full commit SHA" in error for error in errors), errors) + + def test_pinned_action_is_accepted(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + workflow = root / ".github/workflows/test.yml" + workflow.parent.mkdir(parents=True) + workflow.write_text( + "name: test\non: push\npermissions:\n contents: read\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1\n" + ) + self.assertEqual([], GOV.Governance(root).validate_workflows()) + + +class GeneratedOutputTests(unittest.TestCase): + def test_generation_is_deterministic(self) -> None: + governance = GOV.Governance(ROOT) + first = governance.generated_content() + second = governance.generated_content() + self.assertEqual(first, second) + + +class PublicDriftTests(unittest.TestCase): + def setUp(self) -> None: + self.governance = GOV.Governance(ROOT) + self.registry = GOV.expanded_repositories(self.governance.registry()) + self.live = [ + { + "name": item["name"], + "visibility": "public", + "private": False, + "archived": item["observed"]["archived"], + "default_branch": item["observed"]["default_branch"], + } + for item in self.registry + ] + + def test_matching_public_inventory_has_no_drift(self) -> None: + self.assertEqual([], GOV.reconcile_public_inventory(self.governance, self.live)) + + def test_unregistered_public_repository_is_reported(self) -> None: + live = self.live + [{ + "name": "unexpected-public-repository", + "visibility": "public", + "private": False, + "archived": False, + "default_branch": "main", + }] + errors = GOV.reconcile_public_inventory(self.governance, live) + self.assertTrue(any("unregistered public repository" in error for error in errors), errors) + + +if __name__ == "__main__": + unittest.main()