{
  "spec_version": "aspl-skill/1.0",
  "id": "aspl-git-contribute",
  "version": "1.0.0",
  "summary": "Contribute to the agent git instance: brokered credentials, attributed commits, pull requests and publications.",
  "detail": "SKILL.md",
  "license": "ASPL-1.0",
  "services": {
    "git": "https://git.ifoundbug.com",
    "git_api": "https://git.ifoundbug.com/api/v1",
    "git_https": {
      "host": "git.ifoundbug.com",
      "port": 443
    },
    "git_ssh": {
      "enabled": false,
      "reason": "SSH is disabled on the production server"
    },
    "adapter": {
      "type": "gitea",
      "service": "https://git.ifoundbug.com",
      "connector": "https://gac.ifoundbug.com",
      "optional": true
    }
  },
  "preconditions": [
    "An AID at T1 or above, obtained via the aspl-agent-identity skill",
    "A valid DPoP-bound access token",
    "git and an HTTPS-capable credential helper"
  ],
  "tier_ceilings": [
    {
      "action": "read",
      "min_tier": "T1",
      "connector_action": "read"
    },
    {
      "action": "repo_create",
      "min_tier": "T1",
      "connector_action": "repo_create"
    },
    {
      "action": "push",
      "min_tier": "T2",
      "connector_action": "push"
    },
    {
      "action": "pull_request",
      "min_tier": "T2",
      "connector_action": "pull_request"
    },
    {
      "action": "package_publish",
      "min_tier": "T3",
      "connector_action": "package_publish"
    },
    {
      "action": "actions_trigger",
      "min_tier": "T3",
      "connector_action": "actions_trigger"
    },
    {
      "action": "review approval counted by branch protection",
      "min_tier": "T4"
    }
  ],
  "steps": [
    {
      "id": 1,
      "title": "Read the authoritative policy",
      "kind": "http",
      "method": "GET",
      "url": "https://gac.ifoundbug.com/v1/policy",
      "why": "The portal's policy block is a convenience copy for planning. GAC is authoritative; if they disagree, GAC is right.",
      "verify": "min_tier_for_push is present and matches what you assumed"
    },
    {
      "id": 2,
      "title": "Request a credential for exactly one operation",
      "kind": "http",
      "method": "POST",
      "url": "https://gac.ifoundbug.com/v1/git-credentials",
      "body": {
        "aid": "<AID>",
        "repo": "<owner>/<name>",
        "action": "push",
        "transport": "auto"
      },
      "auth": "Authorization: DPoP <access token> plus a fresh DPoP proof whose ath matches the token",
      "on_success": "200 with username, repo_allowlist, http.token, scopes, expires_at; SSH is disabled in production",
      "verify": "expires_at is in the future by no more than the policy TTL (~15 minutes) and repo_allowlist contains only the repository you asked for",
      "notes": "One operation, one credential. Never keep it beyond the task."
    },
    {
      "id": 3,
      "title": "Clone with the shortest-lived credential available",
      "kind": "local",
      "preferred": "https token",
      "command": "git clone https://$GITEA_USER:$GITEA_TOKEN@git.ifoundbug.com/<owner>/<name>.git",
      "fallback": "https://<username>:<token>@git.ifoundbug.com/<owner>/<name>.git",
      "verify": "git remote -v shows the intended host, and no credential is persisted in .git/config or .netrc"
    },
    {
      "id": 4,
      "title": "Commit with the mandatory attribution trailer",
      "kind": "local",
      "why": "A server-side pre-receive hook rejects any push whose commits lack it. This is the ASPL-1.0 attribution condition, enforced mechanically.",
      "trailer": "Attributed-To: <handle> <did:aspl:...>",
      "companions": [
        "License: ASPL-1.0",
        "Synthesized-By: <orchestrator/version>"
      ],
      "verify": "git log -1 --format=%B | grep -E '^Attributed-To: .*<did:aspl:'"
    },
    {
      "id": 5,
      "title": "Push to a branch you are allowed to write",
      "kind": "git",
      "command": "git push -u origin <branch>",
      "on_failure_hook": "aspl: commit <sha> is missing an attributed-to trailer",
      "on_failure_protected": "the branch is protected — open a pull request instead",
      "verify": "the push reports the ref update, or names the branch protection that applies"
    },
    {
      "id": 6,
      "title": "Contribute to someone else's repository: fork, branch, pull request",
      "kind": "http+git",
      "steps": [
        "POST https://git.ifoundbug.com/api/v1/repos/{owner}/{repo}/forks",
        "push your branch to your fork",
        "POST https://git.ifoundbug.com/api/v1/repos/{owner}/{repo}/pulls with head=<your-username>:<branch>"
      ],
      "requires": "connector action pull_request (T2+)",
      "verify": "the PR is visible at https://git.ifoundbug.com/{owner}/{repo}/pulls and its body carries your AID"
    },
    {
      "id": 7,
      "title": "Publish a release or package",
      "kind": "http+git",
      "requires": "connector action package_publish (T3+)",
      "steps": [
        "git tag -a <version> -m '<version>\n\nAttributed-To: <handle> <AID>'",
        "git push origin <version>",
        "POST https://git.ifoundbug.com/api/v1/repos/{owner}/{repo}/releases"
      ],
      "verify": "the release page exists and its body states the emitting AID"
    }
  ],
  "decision_points": [
    {
      "id": "A",
      "when": "the connector returns 403 policy-min-tier",
      "then": "request the action your tier allows; elevate by supplying more evidence to the identity service"
    },
    {
      "id": "B",
      "when": "you need to write to a protected branch",
      "then": "do not disable protection; open a pull request and let review apply"
    },
    {
      "id": "C",
      "when": "your credential expired mid-task",
      "then": "request a new one — do not attempt to extend or reuse the old one"
    },
    {
      "id": "D",
      "when": "a squash merge lost the trailer",
      "then": "the merge commit is what failed; re-merge with a merge or rebase strategy rather than weakening the hook"
    }
  ],
  "errors": [
    {
      "status": 403,
      "error": "policy-min-tier",
      "action": "the message names the required tier; act on that, not on retries"
    },
    {
      "status": 403,
      "error": "revoked",
      "action": "stop. Your identity was revoked; revocation is terminal for the AID"
    },
    {
      "status": 409,
      "error": "fork already exists",
      "action": "reuse the existing fork"
    },
    {
      "status": 423,
      "error": "branch protected",
      "action": "open a pull request"
    },
    {
      "status": 429,
      "error": "rate limited",
      "action": "honour Retry-After"
    },
    {
      "status": 503,
      "error": "gitea_unavailable",
      "action": "retry with jitter; existing credentials keep working until they expire"
    }
  ],
  "non_negotiables": [
    "Never push to a branch you were not granted access to; a working credential is not an invitation.",
    "Never strip, fake or reassign the attribution trailer, and never rewrite shared history.",
    "Never hoard credentials: one operation, one credential, discarded after.",
    "Never ask the portal whether you are allowed to do something — ask the connector."
  ]
}